1(() => { 2 const API_URL = '/wp-content/weather-data/live.json'; 3 const DEFAULT_INDEX = 2; 4 const AUTOPLAY_INTERVAL = 500; 5 6 const imageEl = () => document.getElementById('weather-live-image'); 7 const timeEl = () => document.getElementById('weather-live-time'); 8 const legendEl = () => document.getElementById('weather-live-legend'); 9 const scaleEl = () => document.getElementById('weather-live-scale'); 10 const sliderEl = () => document.getElementById('weather-live-slider'); 11 const handleEl = () => document.getElementById('weather-live-handle'); 12 const prevEl = () => document.getElementById('weather-live-prev'); 13 const nextEl = () => document.getElementById('weather-live-next'); 14 const playEl = () => document.getElementById('weather-live-play'); 15 const stopEl = () => document.getElementById('weather-live-stop'); 16 const isJapanese = 17 document.documentElement.lang === 'ja' || 18 document.documentElement.lang.startsWith('ja-'); 19 20 let frames = []; 21 let currentIndex = 0; 22 let timer = null; 23 let dragging = false; 24 25 function stopAutoPlay() { 26 if (timer) { 27 clearInterval(timer); 28 timer = null; 29 } 30 } 31 32 function normalizeIndex(index) { 33 if (!frames.length) return 0; 34 return Math.max(0, Math.min(index, frames.length - 1)); 35 } 36 37 function parseFrameLabel(name) { 38 const match = String(name || '').match(/KN00(\d{4})w\.png/i); 39 if (!match) return name || ''; 40 const hhmm = match[1]; 41 return `${hhmm.slice(0, 2)}:${hhmm.slice(2, 4)}`; 42 } 43 44 function getFrameText(frame) { 45 const timeText = frame.time_text || parseFrameLabel(frame.name); 46 const typeText = frame.type === 'rain' ? 'é¨é' : 'ã¬ã¼ãã¼'; 47 return `${timeText}ã${typeText}`; 48 } 49 50 // æ§ radar.js ã® custom slider ç¸å½ 51 // index 0,1,2 = 28pxå»ã¿ 52 // index 3以é = 12.214pxå»ã¿ + 84pxéå§ 53 function getLegacySliderPx(index) { 54 const safeIndex = normalizeIndex(index); 55 56 if (safeIndex < 3) { 57 return 28 * safeIndex; 58 } 59 60 return (12.214 * (safeIndex - 3)) + 84; 61 } 62 63 function getLegacySliderMaxPx(lastIndex) { 64 return getLegacySliderPx(lastIndex); 65 } 66 67 function getSliderLeftPercent(index) { 68 if (!frames.length) return 0; 69 70 const lastIndex = frames.length - 1; 71 const maxPx = getLegacySliderMaxPx(lastIndex); 72 73 if (maxPx <= 0) return 0; 74 75 return (getLegacySliderPx(index) / maxPx) * 100; 76 } 77 78 function getIndexFromClientX(clientX) { 79 const slider = sliderEl(); 80 if (!slider || !frames.length) return 0; 81 82 const rect = slider.getBoundingClientRect(); 83 const rawX = clientX - rect.left; 84 const clampedX = Math.max(0, Math.min(rect.width, rawX)); 85 const lastIndex = frames.length - 1; 86 const maxPx = getLegacySliderMaxPx(lastIndex); 87 88 if (rect.width <= 0 || maxPx <= 0) return 0; 89 90 const legacyPx = (clampedX / rect.width) * maxPx; 91 92 let index = 0; 93 if (legacyPx < 84) { 94 index = Math.round(legacyPx / 28); 95 } else { 96 index = Math.round((legacyPx - 84) / 12.214) + 3; 97 } 98 99 return normalizeIndex(index); 100 } 101 102 function updateHandlePosition() { 103 const handle = handleEl(); 104 if (!handle || !frames.length) return; 105 106 handle.style.left = `${getSliderLeftPercent(currentIndex)}%`; 107 } 108 109 function buildScale() { 110 const scale = scaleEl(); 111 if (!scale || !frames.length) return; 112 113 scale.innerHTML = ''; 114 115 const marks = isJapanese 116 ? [ 117 { index: 0, label: '2æéå' }, 118 { index: 2, label: 'ç¾å¨', now: true }, 119 { index: 3, label: '1æéå¾' }, 120 { index: 5, label: '3æéå¾' }, 121 { index: 8, label: '6æéå¾' }, 122 { index: 17, label: '15æéå¾' } 123 ] 124 : [ 125 { index: 0, label: '-2h' }, 126 { index: 2, label: 'Now', now: true }, 127 { index: 3, label: '+1h' }, 128 { index: 5, label: '+3h' }, 129 { index: 8, label: '+6h' }, 130 { index: 17, label: '+15h' } 131 ]; 132 133 const used = new Set(); 134 135 marks.forEach((mark) => { 136 const index = normalizeIndex(mark.index); 137 if (used.has(index)) return; 138 used.add(index); 139 140 const item = document.createElement('span'); 141 item.className = 'weather-live__scale-item';
142 143 if (mark.now) { 144 item.classList.add('is-now'); 145 } 146 147 item.style.left = `${getSliderLeftPercent(index)}%`; 148 item.textContent = mark.label; 149 150 scale.appendChild(item); 151 }); 152 } 153 154 function updateButtonState() { 155 const prev = prevEl(); 156 const next = nextEl(); 157 const play = playEl(); 158 const stop = stopEl(); 159 160 if (prev) prev.disabled = currentIndex <= 0; 161 if (next) next.disabled = currentIndex >= frames.length - 1; 162 163 if (play) play.disabled = frames.length <= 1; 164 if (stop) stop.disabled = !timer; 165 } 166 167 function renderFrame(index) { 168 if (!frames.length) return; 169 170 currentIndex = normalizeIndex(index); 171 const frame = frames[currentIndex]; 172 173 const img = imageEl(); 174 const time = timeEl(); 175 176 if (img) { 177 img.src = frame.url; 178 img.alt = `é¨é²ã®åã ${frame.time_text || parseFrameLabel(frame.name)}`; 179 } 180 181 if (time) { 182 time.textContent = getFrameText(frame); 183 } 184 185 updateHandlePosition(); 186 updateButtonState(); 187 } 188 189 function startAutoPlay() { 190 if (!frames.length) return; 191 192 stopAutoPlay(); 193 194 timer = setInterval(() => { 195 if (currentIndex >= frames.length - 1) { 196 stopAutoPlay(); 197 updateButtonState(); 198 return; 199 } 200 201 renderFrame(currentIndex + 1); 202 }, AUTOPLAY_INTERVAL); 203 204 updateButtonState(); 205 } 206 207 function bindButtons() { 208 const prev = prevEl(); 209 const next = nextEl(); 210 const play = playEl(); 211 const stop = stopEl(); 212 213 if (prev) { 214 prev.addEventListener('click', () => { 215 stopAutoPlay(); 216 renderFrame(currentIndex - 1); 217 }); 218 } 219 220 if (next) { 221 next.addEventListener('click', () => { 222 stopAutoPlay(); 223 renderFrame(currentIndex + 1); 224 }); 225 } 226 227 if (play) { 228 play.addEventListener('click', () => { 229 startAutoPlay(); 230 }); 231 } 232 233 if (stop) { 234 stop.addEventListener('click', () => { 235 stopAutoPlay(); 236 updateButtonState(); 237 }); 238 } 239 } 240 241 function bindSlider() { 242 const slider = sliderEl(); 243 const handle = handleEl(); 244 if (!slider || !handle) return; 245 246 const updateFromPointer = (clientX) => { 247 stopAutoPlay(); 248 renderFrame(getIndexFromClientX(clientX)); 249 }; 250 251 slider.addEventListener('click', (e) => { 252 if (e.target === handle) return; 253 updateFromPointer(e.clientX); 254 }); 255 256 const startDrag = (clientX) => { 257 dragging = true; 258 updateFromPointer(clientX); 259 }; 260 261 const moveDrag = (clientX) => { 262 if (!dragging) return; 263 updateFromPointer(clientX); 264 }; 265 266 const endDrag = () => { 267 dragging = false; 268 }; 269 270 handle.addEventListener('mousedown', (e) => { 271 e.preventDefault(); 272 startDrag(e.clientX); 273 }); 274 275 slider.addEventListener('mousedown', (e) => { 276 if (e.target === handle) return; 277 startDrag(e.clientX); 278 }); 279 280 window.addEventListener('mousemove', (e) => { 281 moveDrag(e.clientX); 282 }); 283 284 window.addEventListener('mouseup', () => { 285 endDrag(); 286 }); 287 288 handle.addEventListener('touchstart', (e) => { 289 const touch = e.changedTouches && e.changedTouches[0]; 290 if (!touch) return; 291 startDrag(touch.clientX); 292 }, { passive: true }); 293 294 slider.addEventListener('touchstart', (e) => { 295 if (e.target === handle) return; 296 const touch = e.changedTouches && e.changedTouches[0]; 297 if (!touch) return; 298 startDrag(touch.clientX); 299 }, { passive: true }); 300 301 window.addEventListener('touchmove', (e) => { 302 const touch = e.touches && e.touches[0]; 303 if (!touch) return; 304 moveDrag(touch.clientX); 305 }, { passive: true }); 306 307 window.addEventListener('touchend', () => { 308 endDrag(); 309 }); 310 } 311 312 async function initWeatherLive() { 313 try { 314 const res = await fetch(API_URL, { cache: 'no-store' }); 315 if (!res.ok) { 316 throw new Error('Failed to fetch live data'); 317 } 318 319 const json = await res.json(); 320 const rainImages = Array.isArray(json.rain_images) ? json.rain_images : []; 321 const radarImages = Array.isArray(json.radar_images) ? json.radar_images : []; 322 323 frames = [ 324 ...rainImages.map((item) => ({ 325 type: 'rain', 326 name: item.name, 327 url: item.url, 328 label: item.label, 329 time_text: item.time_text 330 })), 331 ...radarImages.map((item) => ({ 332 type: 'radar', 333 name: item.name, 334 url: item.url, 335 label: item.label, 336 time_text: item.time_text 337 })) 338 ].filter((item) => item && item.url); 339 340 if (!frames.length) { 341 if (timeEl()) { 342 timeEl().textContent = 'ç»åãã¼ã¿ãããã¾ãã'; 343 } 344 return; 345 } 346 347 if (legendEl() && typeof mtfujiWeather !== 'undefined' && mtfujiWeather.themeUrl) { 348 legendEl().src = `${mtfujiWeather.themeUrl}/assets/img/${ 349 isJapanese ? 'rd_hanrei.png' : 'rd_hanrei_en.png' 350 }`; 351 } 352 353 function setControlLabels() { 354 const play = playEl(); 355 const stop = stopEl(); 356 357 if (play) { 358 play.textContent = isJapanese ? 'åç' : 'Play'; 359 play.setAttribute( 360 'aria-label', 361 isJapanese ? 'é¨é²ã®åããåç' : 'Play weather animation' 362 ); 363 } 364 365 if (stop) { 366 stop.textContent = isJapanese ? '忢' : 'Stop'; 367 stop.setAttribute( 368 'aria-label', 369 isJapanese ? 'é¨é²ã®åãã忢' : 'Stop weather animation' 370 ); 371 } 372 } 373 374 setControlLabels(); 375 bindButtons(); 376 bindSlider(); 377 buildScale(); 378 renderFrame(Math.min(DEFAULT_INDEX, frames.length - 1)); 379 } catch (error) { 380 console.error(error); 381 if (timeEl()) { 382 timeEl().textContent = 'é¨é²ãã¼ã¿ã®åå¾ã«å¤±æãã¾ãã'; 383 } 384 } 385 } 386 387 document.addEventListener('DOMContentLoaded', initWeatherLive); 388})();
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.