(function () { 'use strict'; function initBattleEvent() { var root = document.getElementById('bakusaiMap_02'); if (!root) { return; } var wrap = root.querySelector('.chara-img-wrap'); if (!wrap) { return; } var CHARA_SELECTORS = { bakusai: '.battle-chara--bakusai', tanuki: '.battle-chara--tanuki', open2ch: '.battle-chara--open2ch', machi: '.battle-chara--machi', '5ch': '.battle-chara--5ch', shitaraba: '.battle-chara--shitaraba' }; var ARROW_SELECTORS = { right: '.battle-arrow-right', left: '.battle-arrow-left' }; var DEVICE_CONFIG = { sp: { defaultPhase: { idle: 720, walk: 760, follow: 220, attack: 540, back: 760, cool: 920 }, characters: { bakusai: { face: 'right', startDelay: 0, move: { x: 24, y: -1 }, follow: { x: 6, y: 0 }, phase: { attack: 500 } }, tanuki: { face: 'right', startDelay: 0, move: { x: -22, y: 1 }, follow: { x: -5, y: 0 }, phase: { attack: 620 } }, open2ch: { face: 'right', startDelay: 980, move: { x: 22, y: 0 }, follow: { x: 4, y: 1 }, phase: { attack: 520 } }, machi: { face: 'right', startDelay: 980, move: { x: -20, y: 0 }, follow: { x: -4, y: -1 }, phase: { attack: 600 } }, '5ch': { face: 'right', startDelay: 1880, move: { x: 21, y: 1 }, follow: { x: 4, y: 0 }, phase: { attack: 500 } }, shitaraba: { face: 'right', startDelay: 1880, move: { x: -22, y: -1 }, follow: { x: -5, y: 0 }, phase: { attack: 620 } } }, arrows: { right: { direction: 'right', startX: -120, endX: 120, startY: 54, heightCandidates: [48, 84, 120], arcPeakMin: 10, arcPeakMax: 18, durationMin: 1050, durationMax: 1320, delayMin: 5000, delayMax: 8600, startDelay: 2200, startDelayJitter: 1000, opacityStart: 0, opacityEnd: 0.7, fadeInDuration: 100, fadeOutDuration: 180, yJitter: 8, burstChance: 0.1, burstCountMin: 2, burstCountMax: 3, burstIntervalMin: 260, burstIntervalMax: 460 }, left: { direction: 'left', startX: 120, endX: -120, startY: 72, heightCandidates: [62, 98, 136], arcPeakMin: 10, arcPeakMax: 18, durationMin: 1080, durationMax: 1360, delayMin: 5600, delayMax: 9200, startDelay: 5600, startDelayJitter: 1200, opacityStart: 0, opacityEnd: 0.7, fadeInDuration: 100, fadeOutDuration: 190, yJitter: 8, burstChance: 0.08, burstCountMin: 2, burstCountMax: 3, burstIntervalMin: 280, burstIntervalMax: 500 } }, tagChase: { waypointList: [ { xRate: 0.08, yRate: 0.14 }, { xRate: 0.48, yRate: 0.10 }, { xRate: 0.82, yRate: 0.20 }, { xRate: 0.66, yRate: 0.44 }, { xRate: 0.20, yRate: 0.68 } ], spriteWidth: 56, spriteHeight: 56, runner: { startWaypoint: 0, moveDurationMin: 960, moveDurationMax: 1280, stopMin: 120, stopMax: 260, startDelay: 900 }, chaser: { startWaypoint: 2, moveDurationMin: 860, moveDurationMax: 1160, stopMin: 70, stopMax: 180, startDelay: 1380 }, collision: { distance: 40, checkInterval: 120, knockbackRunner: 30, knockbackChaser: 24, knockbackDuration: 210, stunPause: 160, cooldown: 1800, resumeRunnerDelay: 180, resumeChaserDelay: 220 } } }, pc: { defaultPhase: { idle: 760, walk: 860, follow: 260, attack: 620, back: 840, cool: 980 }, characters: { bakusai: { face: 'right', startDelay: 0, move: { x: 44, y: -2 }, follow: { x: 8, y: 0 }, phase: { attack: 560 } }, tanuki: { face: 'right', startDelay: 0, move: { x: -40, y: 2 }, follow: { x: -8, y: 0 }, phase: { attack: 680 } }, open2ch: { face: 'right', startDelay: 1020, move: { x: 40, y: 0 }, follow: { x: 7, y: 2 }, phase: { attack: 560 } }, machi: { face: 'right', startDelay: 1020, move: { x: -38, y: -1 }, follow: { x: -7, y: -1 }, phase: { attack: 680 } }, '5ch': { face: 'right', startDelay: 1960, move: { x: 42, y: 1 }, follow: { x: 8, y: 1 }, phase: { attack: 560 } }, shitaraba: { face: 'right', startDelay: 1960, move: { x: -41, y: -2 }, follow: { x: -8, y: -1 }, phase: { attack: 680 } } }, arrows: { right: { direction: 'right', startX: -180, endX: 180, startY: 64, heightCandidates: [52, 112, 168], arcPeakMin: 16, arcPeakMax: 28, durationMin: 1200, durationMax: 1560, delayMin: 3600, delayMax: 6800, startDelay: 1600, startDelayJitter: 900, opacityStart: 0, opacityEnd: 0.78, fadeInDuration: 120, fadeOutDuration: 200, yJitter: 10, burstChance: 0.14, burstCountMin: 2, burstCountMax: 3, burstIntervalMin: 220, burstIntervalMax: 420 }, left: { direction: 'left', startX: 180, endX: -180, startY: 84, heightCandidates: [68, 132, 192], arcPeakMin: 16, arcPeakMax: 28, durationMin: 1240, durationMax: 1600, delayMin: 4200, delayMax: 7600, startDelay: 4200, startDelayJitter: 1200, opacityStart: 0, opacityEnd: 0.78, fadeInDuration: 120, fadeOutDuration: 210, yJitter: 10, burstChance: 0.12, burstCountMin: 2, burstCountMax: 3, burstIntervalMin: 240, burstIntervalMax: 440 } }, tagChase: { waypointList: [ { xRate: 0.08, yRate: 0.10 }, { xRate: 0.36, yRate: 0.08 }, { xRate: 0.74, yRate: 0.11 }, { xRate: 0.62, yRate: 0.40 }, { xRate: 0.15, yRate: 0.66 }, { xRate: 0.80, yRate: 0.74 } ], spriteWidth: 88, spriteHeight: 88, runner: { startWaypoint: 0, moveDurationMin: 1020, moveDurationMax: 1360, stopMin: 130, stopMax: 280, startDelay: 720 }, chaser: { startWaypoint: 3, moveDurationMin: 900, moveDurationMax: 1220, stopMin: 80, stopMax: 190, startDelay: 1180 }, collision: { distance: 56, checkInterval: 110, knockbackRunner: 42, knockbackChaser: 34, knockbackDuration: 230, stunPause: 180, cooldown: 1600, resumeRunnerDelay: 160, resumeChaserDelay: 200 } } } }; var STATE_CLASSES = ['is-idle', 'is-walk', 'is-attack']; var GIF_SWAP_DELAY_MS = 2000; var activeArrow = false; function hasSPSheet() { return !!document.querySelector('link[href*="/css/bakusai/sp/index-event.css"]'); } function prefersReducedMotion() { return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); } function randomBetween(min, max) { var minValue = Math.floor(min); var maxValue = Math.floor(max); var swap; if (maxValue < minValue) { swap = minValue; minValue = maxValue; maxValue = swap; } if (maxValue === minValue) { return minValue; } return Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue; } function mergePhase(base, override) { var result = {}; var key; for (key in base) { if (Object.prototype.hasOwnProperty.call(base, key)) { result[key] = base[key]; } } if (!override) { return result; } for (key in override) { if (Object.prototype.hasOwnProperty.call(override, key)) { result[key] = override[key]; } } return result; } function deriveStateSrc(idleSrc, state) { if (!idleSrc || state === 'idle') { return idleSrc; } var match = idleSrc.match(/^(.*)_still\d+(\.[a-zA-Z0-9]+)(\?.*)?$/); if (!match) { return idleSrc; } var stem = match[1]; var ext = match[2]; var query = match[3] || ''; var suffix = state === 'walk' ? '_walk01' : '_attack01'; return stem + suffix + ext + query; } function resolveSources(el) { var idle = el.getAttribute('data-src-idle') || el.getAttribute('src') || ''; var walk = el.getAttribute('data-src-walk') || deriveStateSrc(idle, 'walk'); var attack = el.getAttribute('data-src-attack') || deriveStateSrc(idle, 'attack'); return { idle: idle, walk: walk, attack: attack }; } function bindSourceFallback(el, sources) { if (!el || !sources) { return; } el.addEventListener('error', function () { var currentSrc = el.getAttribute('src'); if (currentSrc === sources.walk) { sources.walk = sources.idle; } if (currentSrc === sources.attack) { sources.attack = sources.idle; } if (el.getAttribute('src') !== sources.idle) { el.setAttribute('src', sources.idle); } }); } function resolveDisplaySrc(state, sources, gifReadyAt) { if (Date.now() < gifReadyAt) { return sources.idle; } return sources.attack || sources[state] || sources.idle; } function applyState(el, state, sources, gifReadyAt) { var i; for (i = 0; i < STATE_CLASSES.length; i += 1) { el.classList.remove(STATE_CLASSES[i]); } if (state === 'walk') { el.classList.add('is-walk'); } else if (state === 'attack') { el.classList.add('is-attack'); } else { el.classList.add('is-idle'); } var nextSrc = resolveDisplaySrc(state, sources, gifReadyAt); if (nextSrc && el.getAttribute('src') !== nextSrc) { el.setAttribute('src', nextSrc); } } function setPosition(el, x, y, duration, easing) { if (typeof duration === 'number') { el.style.transitionDuration = duration + 'ms'; } if (easing) { el.style.transitionTimingFunction = easing; } var faceScale = el.classList.contains('is-face-left') ? -1 : 1; el.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scaleX(' + faceScale + ')'; } function parseTransformTranslate(transformText, fallbackX, fallbackY) { var x = typeof fallbackX === 'number' ? fallbackX : 0; var y = typeof fallbackY === 'number' ? fallbackY : 0; var values; var parsedX; var parsedY; if (!transformText || transformText === 'none') { return { x: x, y: y }; } if (transformText.indexOf('matrix3d(') === 0) { values = transformText.slice(9, -1).split(','); parsedX = parseFloat(values[12]); parsedY = parseFloat(values[13]); return { x: isNaN(parsedX) ? x : parsedX, y: isNaN(parsedY) ? y : parsedY }; } if (transformText.indexOf('matrix(') === 0) { values = transformText.slice(7, -1).split(','); parsedX = parseFloat(values[4]); parsedY = parseFloat(values[5]); return { x: isNaN(parsedX) ? x : parsedX, y: isNaN(parsedY) ? y : parsedY }; } return { x: x, y: y }; } function getCurrentTranslate(el, fallbackX, fallbackY) { if (!el || !window.getComputedStyle) { return { x: typeof fallbackX === 'number' ? fallbackX : 0, y: typeof fallbackY === 'number' ? fallbackY : 0 }; } var style = window.getComputedStyle(el); return parseTransformTranslate(style.transform || style.webkitTransform || '', fallbackX, fallbackY); } function initFacing(el, face) { el.classList.remove('is-face-left', 'is-face-right'); if (face === 'left') { el.classList.add('is-face-left'); } else { el.classList.add('is-face-right'); } } function runCharacter(el, config, defaultPhase) { var sources = resolveSources(el); var phase = mergePhase(defaultPhase, config.phase); var move = config.move || { x: 0, y: 0 }; var follow = config.follow || { x: 0, y: 0 }; var gifReadyAt = Date.now() + GIF_SWAP_DELAY_MS; var firstTargetX = move.x || 0; var firstTargetY = move.y || 0; var secondTargetX = firstTargetX + (follow.x || 0); var secondTargetY = firstTargetY + (follow.y || 0); initFacing(el, config.face); setPosition(el, 0, 0, 0, 'linear'); applyState(el, 'idle', sources, gifReadyAt); bindSourceFallback(el, sources); function loop() { applyState(el, 'idle', sources, gifReadyAt); setPosition(el, 0, 0, 120, 'ease-out'); window.setTimeout(function () { applyState(el, 'walk', sources, gifReadyAt); setPosition(el, firstTargetX, firstTargetY, phase.walk, 'cubic-bezier(0.2, 0.75, 0.25, 1)'); }, phase.idle); window.setTimeout(function () { applyState(el, 'walk', sources, gifReadyAt); setPosition(el, secondTargetX, secondTargetY, phase.follow, 'ease-out'); }, phase.idle + phase.walk); window.setTimeout(function () { applyState(el, 'attack', sources, gifReadyAt); }, phase.idle + phase.walk + phase.follow); window.setTimeout(function () { applyState(el, 'walk', sources, gifReadyAt); setPosition(el, 0, 0, phase.back, 'cubic-bezier(0.32, 0, 0.2, 1)'); }, phase.idle + phase.walk + phase.follow + phase.attack); window.setTimeout(function () { applyState(el, 'idle', sources, gifReadyAt); }, phase.idle + phase.walk + phase.follow + phase.attack + phase.back); window.setTimeout(loop, phase.idle + phase.walk + phase.follow + phase.attack + phase.back + phase.cool); } window.setTimeout(loop, config.startDelay || 0); } function runTagChase(runnerEl, chaserEl, config) { if (!runnerEl || !chaserEl || !config || !config.waypointList || config.waypointList.length < 2) { return; } var collision = config.collision || {}; var collisionDistance = Number(collision.distance); var collisionDistanceSq; var collisionActive = false; var collisionLockUntil = 0; var monitorId = 0; var waypointCount = config.waypointList.length; var runner = { el: runnerEl, control: config.runner || {}, sources: resolveSources(runnerEl), gifReadyAt: Date.now() + GIF_SWAP_DELAY_MS, x: 0, y: 0, routeIndex: 0, timerId: 0, token: 0, moving: false }; var chaser = { el: chaserEl, control: config.chaser || {}, sources: resolveSources(chaserEl), gifReadyAt: Date.now() + GIF_SWAP_DELAY_MS, x: 0, y: 0, routeIndex: 0, timerId: 0, token: 0, moving: false }; function normalizeWaypointIndex(index) { var parsed = parseInt(index, 10); if (isNaN(parsed)) { return 0; } parsed = parsed % waypointCount; if (parsed < 0) { parsed += waypointCount; } return parsed; } function getBounds() { var rect = root.getBoundingClientRect(); var width = Math.round(rect.width || root.clientWidth || 0); var height = Math.round(rect.height || root.clientHeight || 0); var spriteWidth = Math.max( Number(config.spriteWidth) || 0, runner.el.offsetWidth || 0, chaser.el.offsetWidth || 0 ); var spriteHeight = Math.max( Number(config.spriteHeight) || 0, runner.el.offsetHeight || 0, chaser.el.offsetHeight || 0 ); var maxX = Math.max(0, width - spriteWidth); var maxY = Math.max(0, height - spriteHeight); return { width: width, height: height, maxX: maxX, maxY: maxY }; } function resolveWaypoint(index) { var waypoint = config.waypointList[normalizeWaypointIndex(index)]; var bounds = getBounds(); var x; var y; var xRate; var yRate; xRate = typeof waypoint.xRate === 'number' ? waypoint.xRate : 0; yRate = typeof waypoint.yRate === 'number' ? waypoint.yRate : 0; if (typeof waypoint.x === 'number') { x = waypoint.x; } else { x = bounds.maxX * xRate; } if (typeof waypoint.y === 'number') { y = waypoint.y; } else { y = bounds.maxY * yRate; } if (typeof waypoint.xOffset === 'number') { x += waypoint.xOffset; } if (typeof waypoint.yOffset === 'number') { y += waypoint.yOffset; } return { x: clamp(x, 0, bounds.maxX), y: clamp(y, 0, bounds.maxY) }; } function syncActorPosition(actor) { var pos = getCurrentTranslate(actor.el, actor.x, actor.y); actor.x = pos.x; actor.y = pos.y; return pos; } function clearActorTimer(actor) { if (actor.timerId) { window.clearTimeout(actor.timerId); actor.timerId = 0; } } function setActorIdle(actor) { applyState(actor.el, 'idle', actor.sources, actor.gifReadyAt); } function setActorWalk(actor) { applyState(actor.el, 'walk', actor.sources, actor.gifReadyAt); } function setActorAttack(actor) { applyState(actor.el, 'attack', actor.sources, actor.gifReadyAt); } function placeActorAtWaypoint(actor, waypointIndex) { var index = normalizeWaypointIndex(waypointIndex); var point = resolveWaypoint(index); actor.routeIndex = index; actor.x = point.x; actor.y = point.y; setPosition(actor.el, actor.x, actor.y, 0, 'linear'); setActorIdle(actor); } function scheduleActor(actor, delay) { clearActorTimer(actor); actor.timerId = window.setTimeout(function () { moveActorNext(actor); }, Math.max(0, delay || 0)); } function moveActorNext(actor) { if (!document.body.contains(actor.el)) { return; } var token = actor.token + 1; var nextIndex = normalizeWaypointIndex(actor.routeIndex + 1); var nextPoint = resolveWaypoint(nextIndex); var duration = randomBetween( actor.control.moveDurationMin || 900, actor.control.moveDurationMax || 1200 ); var pause = randomBetween( actor.control.stopMin || 100, actor.control.stopMax || 220 ); var deltaX; actor.token = token; actor.moving = true; syncActorPosition(actor); deltaX = nextPoint.x - actor.x; initFacing(actor.el, deltaX < 0 ? 'left' : 'right'); setActorWalk(actor); setPosition(actor.el, nextPoint.x, nextPoint.y, duration, 'cubic-bezier(0.2, 0.75, 0.25, 1)'); clearActorTimer(actor); actor.timerId = window.setTimeout(function () { if (token !== actor.token) { return; } actor.moving = false; actor.routeIndex = nextIndex; actor.x = nextPoint.x; actor.y = nextPoint.y; setActorIdle(actor); scheduleActor(actor, pause); }, duration + 26); } function clampToBounds(point) { var bounds = getBounds(); return { x: clamp(point.x, 0, bounds.maxX), y: clamp(point.y, 0, bounds.maxY) }; } function interruptActor(actor, fixedPosition) { clearActorTimer(actor); actor.token += 1; actor.moving = false; actor.x = fixedPosition.x; actor.y = fixedPosition.y; setPosition(actor.el, actor.x, actor.y, 0, 'linear'); } function handleCollision() { var now = Date.now(); var runnerPos; var chaserPos; var dx; var dy; var distance; var unitX; var unitY; var runnerTarget; var chaserTarget; var knockbackDuration = Number(collision.knockbackDuration) || 220; var stunPause = Number(collision.stunPause) || 160; var cooldown = Number(collision.cooldown) || 1600; var runnerKnockback = Number(collision.knockbackRunner) || 34; var chaserKnockback = Number(collision.knockbackChaser) || 28; var resumeRunnerDelay = Number(collision.resumeRunnerDelay) || 160; var resumeChaserDelay = Number(collision.resumeChaserDelay) || 200; if (collisionActive || now < collisionLockUntil) { return; } runnerPos = syncActorPosition(runner); chaserPos = syncActorPosition(chaser); dx = chaserPos.x - runnerPos.x; dy = chaserPos.y - runnerPos.y; distance = Math.sqrt((dx * dx) + (dy * dy)); if ((distance * distance) > collisionDistanceSq) { return; } if (distance < 1) { distance = 1; dx = runner.el.classList.contains('is-face-left') ? -1 : 1; dy = 0; } unitX = dx / distance; unitY = dy / distance; runnerTarget = clampToBounds({ x: runnerPos.x - (unitX * runnerKnockback), y: runnerPos.y - (unitY * runnerKnockback) }); chaserTarget = clampToBounds({ x: chaserPos.x + (unitX * chaserKnockback), y: chaserPos.y + (unitY * chaserKnockback) }); collisionActive = true; collisionLockUntil = now + cooldown; interruptActor(runner, runnerPos); interruptActor(chaser, chaserPos); initFacing(runner.el, runnerTarget.x < runnerPos.x ? 'left' : 'right'); initFacing(chaser.el, chaserTarget.x < chaserPos.x ? 'left' : 'right'); setActorAttack(runner); setActorAttack(chaser); setPosition(runner.el, runnerTarget.x, runnerTarget.y, knockbackDuration, 'cubic-bezier(0.15, 0.8, 0.25, 1)'); setPosition(chaser.el, chaserTarget.x, chaserTarget.y, knockbackDuration, 'cubic-bezier(0.15, 0.8, 0.25, 1)'); runner.x = runnerTarget.x; runner.y = runnerTarget.y; chaser.x = chaserTarget.x; chaser.y = chaserTarget.y; window.setTimeout(function () { if (!document.body.contains(runner.el) || !document.body.contains(chaser.el)) { return; } setActorIdle(runner); setActorIdle(chaser); collisionActive = false; scheduleActor(runner, resumeRunnerDelay); scheduleActor(chaser, resumeChaserDelay); }, knockbackDuration + stunPause); } function startCollisionMonitor() { var interval = Number(collision.checkInterval) || 120; monitorId = window.setInterval(function () { var dx; var dy; var distSq; var runnerPos; var chaserPos; if (!document.body.contains(runner.el) || !document.body.contains(chaser.el)) { window.clearInterval(monitorId); monitorId = 0; return; } if (collisionActive || Date.now() < collisionLockUntil) { return; } if (!runner.moving && !chaser.moving) { return; } runnerPos = syncActorPosition(runner); chaserPos = syncActorPosition(chaser); dx = runnerPos.x - chaserPos.x; dy = runnerPos.y - chaserPos.y; distSq = (dx * dx) + (dy * dy); if (distSq <= collisionDistanceSq) { handleCollision(); } }, interval); } if (isNaN(collisionDistance) || collisionDistance <= 0) { collisionDistance = 48; } collisionDistanceSq = collisionDistance * collisionDistance; runner.el.classList.add('is-tag-chase'); chaser.el.classList.add('is-tag-chase'); bindSourceFallback(runner.el, runner.sources); bindSourceFallback(chaser.el, chaser.sources); placeActorAtWaypoint(runner, runner.control.startWaypoint || 0); placeActorAtWaypoint(chaser, chaser.control.startWaypoint || 0); initFacing(runner.el, 'right'); initFacing(chaser.el, 'left'); scheduleActor(runner, Number(runner.control.startDelay) || 0); scheduleActor(chaser, Number(chaser.control.startDelay) || 0); startCollisionMonitor(); } function nowMs() { if (window.performance && typeof window.performance.now === 'function') { return window.performance.now(); } return Date.now(); } function requestFrame(callback) { if (window.requestAnimationFrame) { window.requestAnimationFrame(callback); } else { window.setTimeout(function () { callback(nowMs()); }, 16); } } function clamp(value, min, max) { if (value < min) { return min; } if (value > max) { return max; } return value; } function setArrowTransform(el, x, y) { el.style.transform = 'translate3d(' + Math.round(x) + 'px, ' + Math.round(y) + 'px, 0)'; } function resolveArrowHeight(config) { var y = config.startY || 0; var candidates = config.heightCandidates || []; if (candidates.length) { y = candidates[randomBetween(0, candidates.length - 1)]; } if (config.yJitter) { y += randomBetween(-config.yJitter, config.yJitter); } return y; } function resolveArrowEndX(config, rootWidth) { if (config.direction === 'left') { return -rootWidth + (config.endX || 0); } return rootWidth + (config.endX || 0); } function primeArrow(el, config) { if (!el || !config) { return; } el.style.transition = 'none'; el.style.opacity = String(config.opacityStart); setArrowTransform(el, config.startX || 0, config.startY || 0); } function fireArrow(el, config, done) { var rootRect = root.getBoundingClientRect(); var rootWidth = Math.round(rootRect.width || root.clientWidth || 0); var startX = config.startX || 0; var endX = resolveArrowEndX(config, rootWidth); var baseY = resolveArrowHeight(config); var arcPeak = randomBetween(config.arcPeakMin || 10, config.arcPeakMax || 20); var duration = randomBetween(config.durationMin, config.durationMax); var fadeInDuration = config.fadeInDuration || 120; var fadeOutDuration = config.fadeOutDuration || 180; var opacityStart = Number(config.opacityStart); var opacityEnd = Number(config.opacityEnd); var fadeInRatio; var fadeOutRatio; var fadeOutStartRatio; var startAt; if (isNaN(opacityStart)) { opacityStart = 0; } if (isNaN(opacityEnd)) { opacityEnd = 0.75; } if (!rootWidth) { window.setTimeout(function () { if (typeof done === 'function') { done(); } }, 240); return; } fadeInRatio = clamp(fadeInDuration / duration, 0.05, 0.45); fadeOutRatio = clamp(fadeOutDuration / duration, 0.05, 0.45); fadeOutStartRatio = clamp(1 - fadeOutRatio, fadeInRatio, 0.98); el.style.transition = 'none'; el.style.opacity = String(opacityStart); setArrowTransform(el, startX, baseY); startAt = nowMs(); function step(timestamp) { var elapsed = timestamp - startAt; var progress = clamp(elapsed / duration, 0, 1); var currentX = startX + (endX - startX) * progress; var currentY = baseY - Math.sin(progress * Math.PI) * arcPeak; var opacity = opacityEnd; if (!document.body.contains(el)) { if (typeof done === 'function') { done(); } return; } if (progress <= fadeInRatio) { opacity = opacityStart + (opacityEnd - opacityStart) * (progress / fadeInRatio); } else if (progress >= fadeOutStartRatio) { opacity = opacityEnd + (opacityStart - opacityEnd) * ((progress - fadeOutStartRatio) / (1 - fadeOutStartRatio)); } setArrowTransform(el, currentX, currentY); el.style.opacity = String(opacity); if (progress < 1) { requestFrame(step); return; } el.style.transition = 'none'; el.style.opacity = String(opacityStart); setArrowTransform(el, startX, baseY); if (typeof done === 'function') { done(); } } requestFrame(step); } function runArrowLoop(el, config) { var burstRemaining = 0; var isBurstActive = false; function shouldStartBurst() { var chance = Number(config.burstChance); if (isNaN(chance) || chance <= 0) { return false; } return Math.random() < chance; } function nextBurstCount() { var minCount = randomBetween(config.burstCountMin || 2, config.burstCountMax || 3); return Math.max(2, minCount); } function nextBurstInterval() { return randomBetween(config.burstIntervalMin || 220, config.burstIntervalMax || 420); } function scheduleNext(delay) { window.setTimeout(tick, delay); } function tick() { if (!document.body.contains(el)) { return; } if (activeArrow) { scheduleNext(randomBetween(220, 520)); return; } activeArrow = true; fireArrow(el, config, function () { activeArrow = false; if (burstRemaining > 0) { burstRemaining -= 1; scheduleNext(nextBurstInterval()); return; } if (isBurstActive) { isBurstActive = false; scheduleNext(randomBetween(config.delayMin, config.delayMax)); return; } if (shouldStartBurst()) { isBurstActive = true; burstRemaining = nextBurstCount() - 1; if (burstRemaining > 0) { burstRemaining -= 1; scheduleNext(nextBurstInterval()); return; } isBurstActive = false; } scheduleNext(randomBetween(config.delayMin, config.delayMax)); }); } if (!el || !config) { return; } primeArrow(el, config); var initialDelay = (config.startDelay || 0) + randomBetween(0, config.startDelayJitter || 0); scheduleNext(initialDelay); } var currentDeviceConfig = hasSPSheet() ? DEVICE_CONFIG.sp : DEVICE_CONFIG.pc; var key; var open2chEl = wrap.querySelector(CHARA_SELECTORS.open2ch); var machiEl = wrap.querySelector(CHARA_SELECTORS.machi); for (key in CHARA_SELECTORS) { if (!Object.prototype.hasOwnProperty.call(CHARA_SELECTORS, key)) { continue; } if (key === 'open2ch' || key === 'machi') { continue; } var charaEl = wrap.querySelector(CHARA_SELECTORS[key]); var charaConfig = currentDeviceConfig.characters[key]; if (!charaEl || !charaConfig) { continue; } runCharacter(charaEl, charaConfig, currentDeviceConfig.defaultPhase); } if (open2chEl && machiEl && currentDeviceConfig.tagChase) { runTagChase(open2chEl, machiEl, currentDeviceConfig.tagChase); } if (!prefersReducedMotion() && currentDeviceConfig.arrows) { var rightArrowEl = wrap.querySelector(ARROW_SELECTORS.right); var leftArrowEl = wrap.querySelector(ARROW_SELECTORS.left); if (rightArrowEl) { runArrowLoop(rightArrowEl, currentDeviceConfig.arrows.right); } if (leftArrowEl) { runArrowLoop(leftArrowEl, currentDeviceConfig.arrows.left); } } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initBattleEvent); } else { initBattleEvent(); } })();