1/* ===================================================== 2 Midori Photo Art - ã¡ã¤ã³JavaScript 3 ===================================================== */ 4 5// =================== 6// BASE_PATH èªå解決 (404ã¨ã©ã¼é²æ¢) 7// =================== 8if (typeof window !== 'undefined' && window.BASE_PATH === undefined) { 9 const isSubdir = window.location.pathname.includes('/new_toppage/'); 10 window.BASE_PATH = isSubdir ? '' : 'new_toppage/'; 11} 12 13// =================== 14// ã°ãã¼ãã«å¤æ° 15// =================== 16let articlesData = []; 17let currentPage = 1; 18const articlesPerPage = 20; 19let searchTerm = ''; 20let activeTag = ''; 21const randomArticlesState = { 22 pool: [], 23 visibleIndices: [] 24}; 25const newestArticleIds = new Set(); 26const cardHeightCache = new Map(); 27 28/** éç index è¨äºã¯ contentFile ã¸ã徿¥ã¯ article.html?id= 㸠*/ 29function getArticleReadUrl(article) { 30 if (!article) return `${window.BASE_PATH || ''}article.html?id=`; 31 const basePath = window.BASE_PATH || ''; 32 const normalized = basePath.endsWith('/') ? basePath
32: (basePath ? `${basePath}/` : ''); 33 const cf = article.contentFile || ''; 34 if (cf && /\.html$/i.test(cf) && !/^https?:\/\//i.test(cf)) { 35 if (cf.startsWith('/')) return cf; 36 if (cf.startsWith('../')) return cf; 37 return normalized + cf.replace(/^\//, ''); 38 } 39 return `${normalized}article.html?id=${encodeURIComponent(article.id)}`; 40} 41 42function shouldShowFutureArticles() { 43 if (typeof window === 'undefined') { 44 return false; 45 } 46 47 if (window.SHOW_FUTURE_ARTICLES === true) { 48 return true; 49 } 50 51 const protocol = window.location?.protocol || ''; 52 if (protocol === 'file:') { 53 return true; 54 } 55 56 const hostname = (window.location?.hostname || '').toLowerCase(); 57 if (!hostname) { 58 return true; 59 } 60 61 if (['localhost', '127.0.0.1', '::1', '0.0.0.0'].includes(hostname)) { 62 return true; 63 } 64 65 if (hostname.endsWith('.local') || hostname.endsWith('.test')) { 66 return true; 67 } 68 69 if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(hostname)) { 70 return true; 71 } 72 73 if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) { 74 return true; 75 } 76 77 if (/^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)) { 78 return true; 79 } 80 81 return false; 82} 83 84// =================== 85// åæå 86// =================== 87document.addEventListener('DOMContentLoaded', async () => { 88 console.log('ãµã¤ãåæåéå§'); 89 90 document.body.classList.add('hero-active'); 91 setTimeout(() => { 92 document.body.classList.remove('hero-active'); 93 console.log('ãã¼ãã¼ã¢ãã¡ã¼ã·ã§ã³å®äºãã¹ã¯ãã¼ã«æå¹å'); 94 }, 4000); 95 96 initializeNav(); 97 initializeSearch(); 98 initializeScrollEffects(); 99 100 const articlesReady = loadArticlesData(); 101 const extraReady = Promise.allSettled([ 102 loadBriefings(), 103 loadPowerWords(), 104 articlesReady.then(() => displayRandomArticles()) 105 ]); 106 107 await articlesReady; 108 renderPopularTags(); 109 displayArticles(); 110 await extraReady; 111 112 console.log('ãµã¤ãåæåå®äº'); 113}); 114 115// =================== 116// ãã¼ã¿èªã¿è¾¼ã¿ 117// =================== 118async function loadArticlesData() { 119 try { 120 // ãã¼ã¹ãã¹ãåå¾ï¼ã«ã¼ãããå®è¡ãããå ´åã«å¯¾å¿ï¼ 121 const basePath = window.BASE_PATH || ''; 122 const cacheBuster = `_=${Date.now()}`; 123 const legacyUrl = `${basePath}data/articles.json?${cacheBuster}`; 124 const interactiveUrl = `${basePath}data/interactive-articles.json?${cacheBuster}`; 125 126 const [legacyRes, interactiveRes] = await Promise.all([ 127 fetch(legacyUrl, { cache: 'no-store' }), 128 fetch(interactiveUrl, { cache: 'no-store' }) 129 ]); 130 131 if (!legacyRes.ok) { 132 throw new Error('è¨äºãã¼ã¿ï¼articles.jsonï¼ã®èªã¿è¾¼ã¿ã«å¤±æãã¾ãã'); 133 } 134 const legacyData = await legacyRes.json(); 135 let legacyArticles = Array.isArray(legacyData.articles) ? legacyData.articles : []; 136 137 let interactiveArticles = []; 138 if (interactiveRes.ok) { 139 try { 140 const intData = await interactiveRes.json(); 141 interactiveArticles = Array.isArray(intData.articles) ? intData.articles : []; 142 } catch (e) { 143 console.warn('interactive-articles.json ã®è§£æã«å¤±æ:', e); 144 } 145 } 146 147 const includeFutureArticles = shouldShowFutureArticles();
148 const filterFuture = (arr) => { 149 if (includeFutureArticles) return [...arr]; 150 const today = new Date(); 151 today.setHours(0, 0, 0, 0); 152 return arr.filter((article) => { 153 const articleDate = new Date(article.date); 154 articleDate.setHours(0, 0, 0, 0); 155 return articleDate <= today; 156 }); 157 }; 158 159 let a = filterFuture(interactiveArticles); 160 let b = filterFuture(legacyArticles); 161 const interactiveIds = new Set(a.map((x) => x.id).filter(Boolean)); 162 b = b.filter((x) => x && x.id && !interactiveIds.has(x.id)); 163 164 articlesData = [...a, ...b]; 165 articlesData.sort((x, y) => { 166 const dx = new Date(x.date || 0).getTime(); 167 const dy = new Date(y.date || 0).getTime(); 168 if (dy !== dx) return dy - dx; 169 return String(y.id || '').localeCompare(String(x.id || ''), 'ja'); 170 }); 171 172 // ææ°è¨äºä¸ä½3ä»¶ãè¨é² 173 newestArticleIds.clear(); 174 articlesData.slice(0, 3).forEach(article => { 175 if (article && article.id !== undefined && article.id !== null) { 176 newestArticleIds.add(article.id); 177 } 178 }); 179 180 console.log( 181 includeFutureArticles 182 ? `${articlesData.length}ä»¶ã®è¨äºãèªã¿è¾¼ã¿ã¾ããï¼æ¥ä»æ°ããé ã»æªæ¥å«ãï¼` 183 : `${articlesData.length}ä»¶ã®è¨äºãèªã¿è¾¼ã¿ã¾ããï¼æ¥ä»æ°ããé ï¼` 184 ); 185 } catch (error) { 186 console.error('è¨äºãã¼ã¿èªã¿è¾¼ã¿ã¨ã©ã¼:', error); 187 displayError('è¨äºãã¼ã¿ã®èªã¿è¾¼ã¿ã«å¤±æãã¾ããããã°ããçµã£ã¦ããå度ã試ããã ããã'); 188 } 189} 190 191// =================== 192// ããã²ã¼ã·ã§ã³ 193// =================== 194function initializeNav() { 195 const navToggle = document.querySelector('.nav-toggle'); 196 const navMenu = document.querySelector('.nav-menu'); 197 const nav = document.querySelector('.main-nav'); 198 199 if (navToggle && navMenu) { 200 navToggle.addEventListener('click', () => { 201 navMenu.classList.toggle('active'); 202 203 // ã¢ãã¡ã¼ã·ã§ã³ 204 const spans = navToggle.querySelectorAll('span'); 205 if (navMenu.classList.contains('active')) { 206 spans[0].style.transform = 'rotate(45deg) translate(5px, 5px)'; 207 spans[1].style.opacity = '0'; 208 spans[2].style.transform = 'rotate(-45deg) translate(7px, -6px)'; 209 } else { 210 spans[0].style.transform = ''; 211 spans[1].style.opacity = ''; 212 spans[2].style.transform = ''; 213 } 214 }); 215 216 // ã¡ãã¥ã¼ãªã³ã¯ã¯ãªãã¯ã§éãã 217 navMenu.querySelectorAll('.nav-link').forEach(link => { 218 link.addEventListener('click', () => { 219 navMenu.classList.remove('active'); 220 const spans = navToggle.querySelectorAll('span'); 221 spans[0].style.transform = ''; 222 spans[1].style.opacity = ''; 223 spans[2].style.transform = ''; 224 }); 225 }); 226 } 227 228 if (!nav) return; 229 const mq = window.matchMedia('(min-width: 769px)'); 230 const IDLE_MS = 3000; 231 let hideTimer = 0; 232 let lastScrollY = window.scrollY; 233 234 function isPc() { return mq.matches; } 235 236 function showNav() { 237 nav.classList.remove('main-nav--compact'); 238 scheduleHide(); 239 } 240 241 function scheduleHide() { 242 clearTimeout(hideTimer); 243 if (!isPc()) { 244 nav.classList.remove('main-nav--compact'); 245 return; 246 } 247 hideTimer = setTimeout(() => { 248 if (nav.matches(':hover') || nav.contains(document.activeElement)) return; 249 if (navMenu && navMenu.classList.contains('active')) return; 250 nav.classList.add('main-nav--compact'); 251 }, IDLE_MS); 252 } 253 254 nav.addEventListener('mouseenter', showNav); 255 nav.addEventListener('mouseleave', scheduleHide); 256 nav.addEventListener('focusin', showNav); 257 document.addEventListener('mousemove', (e) => { 258 if (isPc() && e.clientY < 72) showNav(); 259 }); 260 window.addEventListener('scroll', () => { 261 const y = window.scrollY; 262 if (y + 10 < lastScrollY) showNav(); 263 lastScrollY = y; 264 }, { passive: true }); 265 mq.addEventListener('change', scheduleHide); 266 scheduleHide(); 267} 268 269// =================== 270// æ¤ç´¢æ©è½ 271// =================== 272function initializeSearch() { 273 const searchInput = document.getElementById('articleSearchInput'); 274 const searchBtn = document.getElementById('articleSearchButton'); 275 276 if (searchInput) { 277 // ãªã¢ã«ã¿ã¤ã æ¤ç´¢ 278 searchInput.addEventListener('input', debounce((e) => { 279 searchTerm = e.target.value.toLowerCase(); 280 currentPage = 1; 281 displayArticles(); 282 displayRandomArticles(); 283 }, 300)); 284 285 // Enterãã¼ã§æ¤ç´¢ 286 searchInput.addEventListener('keypress', (e) => { 287 if (e.key === 'Enter') { 288 searchTerm = e.target.value.toLowerCase(); 289 currentPage = 1; 290 displayArticles(); 291 displayRandomArticles(); 292 } 293 }); 294 } 295 296 if (searchBtn) { 297 searchBtn.addEventListener('click', () => { 298 if (searchInput) { 299 searchTerm = searchInput.value.toLowerCase(); 300 currentPage = 1; 301 displayArticles(); 302 displayRandomArticles(); 303 } 304 }); 305 } 306} 307 308 309// =================== 310// 人æ°ã¿ã° 311// =================== 312function renderPopularTags() { 313 const container = document.getElementById('popularTags'); 314 if (!container) return; 315 316 const tagCounts = new Map(); 317 articlesData.forEach(article => { 318 if (!Array.isArray(article.tags)) return;
319 article.tags.forEach(tag => { 320 const trimmed = (tag || '').trim(); 321 if (!trimmed) return; 322 tagCounts.set(trimmed, (tagCounts.get(trimmed) || 0) + 1); 323 }); 324 }); 325 326 const popularEntries = [...tagCounts.entries()] 327 .sort((a, b) => { 328 if (b[1] !== a[1]) { 329 return b[1] - a[1]; 330 } 331 return a[0].localeCompare(b[0], 'ja'); 332 }) 333 .slice(0, 15); 334 335 if (popularEntries.length === 0) { 336 container.innerHTML = ''; 337 container.style.display = 'none'; 338 return; 339 } 340 341 container.style.display = 'flex'; 342 343 const buttonsHtml = [ 344 `<button class="popular-tag" data-tag="">ãã¹ã¦</button>`, 345 ...popularEntries.map(([tag, count]) => { 346 const encodedTag = encodeURIComponent(tag); 347 const safeTag = escapeHtml(tag); 348 return `<button class="popular-tag" data-tag="${encodedTag}"> 349 ${safeTag}<span class="popular-tag__count">${count}</span> 350 </button>`; 351 }) 352 ].join(''); 353 354 container.innerHTML = buttonsHtml; 355 356 container.querySelectorAll('.popular-tag').forEach(button => { 357 button.addEventListener('click', () => { 358 const encodedValue = button.dataset.tag || ''; 359 const tagValue = encodedValue ? decodeURIComponent(encodedValue) : ''; 360 activeTag = activeTag === tagValue ? '' : tagValue; 361 currentPage = 1; 362 displayArticles(); 363 displayRandomArticles(); 364 }); 365 }); 366 367 updatePopularTagsActiveState(); 368} 369 370function updatePopularTagsActiveState() { 371 const container = document.getElementById('popularTags'); 372 if (!container) return; 373 374 container.querySelectorAll('.popular-tag').forEach(button => { 375 const encodedValue = button.dataset.tag || ''; 376 const tagValue = encodedValue ? decodeURIComponent(encodedValue) : ''; 377 const isActive = activeTag ? tagValue === activeTag : encodedValue === ''; 378 button.classList.toggle('popular-tag--active', isActive); 379 button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); 380 }); 381} 382 383 384// =================== 385// è¨äºè¡¨ç¤º 386// =================== 387function displayArticles() { 388 const articlesGrid = document.getElementById('articlesGrid'); 389 if (!articlesGrid) return; 390 391 // æ¤ç´¢ãé©ç¨ 392 let filteredArticles = articlesData; 393 394 if (activeTag) { 395 const normalizedTag = activeTag.toLowerCase(); 396 filteredArticles = filteredArticles.filter(article => 397 Array.isArray(article.tags) && article.tags.some(tag => tag.toLowerCase() === normalizedTag) 398 ); 399 } 400 401 if (searchTerm) { 402 filteredArticles = filteredArticles.filter(article => { 403 return article.title.toLowerCase().includes(searchTerm) || 404 article.description.toLowerCase().includes(searchTerm) || 405 (article.tags && article.tags.some(tag => 406 tag.toLowerCase().includes(searchTerm) 407 )); 408 }); 409 } 410 411 // è¨äºæ°ã表示 412 updateArticleCount(filteredArticles.length); 413 updatePopularTagsActiveState(); 414 415 if (filteredArticles.length === 0) { 416 articlesGrid.innerHTML = '<div class="loading">該å½ããè¨äºãè¦ã¤ããã¾ããã§ãã</div>'; 417 document.getElementById('pagination').innerHTML = ''; 418 return; 419 } 420 421 // ãã¼ã¸ãã¼ã·ã§ã³ 422 const totalPages = Math.ceil(filteredArticles.length / articlesPerPage); 423 if (currentPage > totalPages) { 424 currentPage = totalPages; 425 } 426 const startIndex = (currentPage - 1) * articlesPerPage; 427 const endIndex = startIndex + articlesPerPage; 428 const displayedArticles = filteredArticles.slice(startIndex, endIndex); 429 430 // è¨äºã«ã¼ãçæ 431 articlesGrid.innerHTML = displayedArticles.map(article => createArticleCard(article)).join(''); 432 paintCardBars(articlesGrid); 433 434 // ãã¼ã¸ãã¼ã·ã§ã³çæ 435 createPagination(totalPages); 436 437 normalizeArticleCardHeights(); 438} 439 440// =================== 441// è¨äºã«ã¼ãçæ 442// =================== 443const CARD_DATE_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>'; 444const CARD_TIME_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>'; 445 446function createBriefingStyleCard(item) { 447 const tagsHtml = (item.tags || []).slice(0, 5).map((tag) => 448 `<span class="briefing-card__tag">#${escapeHtml(tag)}</span>` 449 ).join(''); 450 const readHtml = (item.readTime !== undefined && item.readTime !== null && item.readTime !== '') 451 ? `<span class="briefing-card__read-time">${CARD_TIME_SVG}${escapeHtml(String(item.readTime))}å</span>` 452 : ''; 453 const thumb = item.thumb 454 ? `<img src="${escapeHtml(item.thumb)}" alt="${escapeHtml(item.title || '')}" loading="lazy" onerror="this.style.display='none'">` 455 : ''; 456 return `<article class="briefing-card"> 457 <a href="${escapeHtml(item.href || '#')}" class="briefing-card__link"> 458 <div class="briefing-card__thumbnail">${thumb}</div> 459 <div class="briefing-card__content"> 460 <div class="briefing-card__meta"> 461 <time class="briefing-card__date">${CARD_DATE_SVG}${escapeHtml(item.date || '')}</time> 462 ${readHtml} 463 </div> 464 <h3 class="briefing-card__title">${escapeHtml(item.title || '')}</h3> 465 <p class="briefing-card__description">${escapeHtml(item.desc || '')}</p> 466 <div class="briefing-card__tags">${tagsHtml}</div> 467 </div> 468 </a> 469 </article>`; 470} 471 472function createArticleCard(article) { 473 const basePath = window.BASE_PATH || ''; 474 return createBriefingStyleCard({
475 href: getArticleReadUrl(article), 476 thumb: article.thumbnail ? basePath + article.thumbnail : '', 477 title: article.title || article.id || '', 478 desc: article.description || '', 479 date: formatDate(article.date), 480 readTime: article.readTime || 5, 481 tags: article.tags || [] 482 }); 483} 484 485// =================== 486// ãã¼ã¸ãã¼ã·ã§ã³ 487// =================== 488function createPagination(totalPages) { 489 const paginationContainer = document.getElementById('pagination'); 490 if (!paginationContainer || totalPages <= 1) { 491 paginationContainer.innerHTML = ''; 492 return; 493 } 494 495 let html = ''; 496 497 // åã¸ãã¿ã³ 498 html += `<button onclick="changePage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}> 499 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 500 <path d="M15 18l-6-6 6-6"/> 501 </svg> 502 å㸠503 </button>`; 504 505 // ãã¼ã¸çªå· 506 const maxButtons = 5; 507 let startPage = Math.max(1, currentPage - Math.floor(maxButtons / 2)); 508 let endPage = Math.min(totalPages, startPage + maxButtons - 1); 509
510 if (endPage - startPage < maxButtons - 1) { 511 startPage = Math.max(1, endPage - maxButtons + 1); 512 } 513 514 if (startPage > 1) { 515 html += `<button onclick="changePage(1)">1</button>`; 516 if (startPage > 2) { 517 html += `<span>...</span>`; 518 } 519 } 520 521 for (let i = startPage; i <= endPage; i++) { 522 html += `<button onclick="changePage(${i})" ${i === currentPage ? 'class="active"' : ''}>${i}</button>`; 523 } 524 525 if (endPage < totalPages) { 526 if (endPage < totalPages - 1) { 527 html += `<span>...</span>`; 528 } 529 html += `<button onclick="changePage(${totalPages})">${totalPages}</button>`; 530 } 531 532 // 次ã¸ãã¿ã³ 533 html += `<button onclick="changePage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}> 534 次㸠535 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 536 <path d="M9 18l6-6-6-6"/> 537 </svg> 538 </button>`; 539 540 paginationContainer.innerHTML = html; 541} 542 543function changePage(page) { 544 currentPage = page; 545 displayArticles(); 546} 547 548// =================== 549// ã¹ã¯ãã¼ã«å¹æ 550// =================== 551function initializeScrollEffects() { 552 // ãããã¸æ»ããã¿ã³ 553 const scrollTopBtn = document.getElementById('scrollTop'); 554 if (scrollTopBtn) { 555 window.addEventListener('scroll', () => { 556 if (window.scrollY > 500) { 557 scrollTopBtn.classList.add('visible'); 558 } else { 559 scrollTopBtn.classList.remove('visible'); 560 } 561 }); 562 563 scrollTopBtn.addEventListener('click', () => { 564 window.scrollTo({ top: 0, behavior: 'smooth' }); 565 }); 566 } 567} 568 569// =================== 570// ã¦ã¼ãã£ãªãã£é¢æ° 571// =================== 572function debounce(func, wait) { 573 let timeout; 574 return function executedFunction(...args) { 575 const later = () => { 576 clearTimeout(timeout); 577 func(...args); 578 }; 579 clearTimeout(timeout); 580 timeout = setTimeout(later, wait); 581 }; 582} 583 584function formatDate(dateString) { 585 const date = new Date(dateString); 586 const year = date.getFullYear();
587 const month = (date.getMonth() + 1).toString().padStart(2, '0'); 588 const day = date.getDate().toString().padStart(2, '0'); 589 return `${year}/${month}/${day}`; 590} 591 592function displayError(message) { 593 const articlesGrid = document.getElementById('articlesGrid'); 594 if (articlesGrid) { 595 articlesGrid.innerHTML = ` 596 <div style="grid-column: 1 / -1; text-align: center; padding: 2rem; color: var(--color-secondary);"> 597 <p>${message}</p> 598 </div> 599 `; 600 } 601} 602 603// =================== 604// è¨äºæ°ã®è¡¨ç¤ºãæ´æ° 605// =================== 606function updateArticleCount(count) { 607 const countElement = document.getElementById('articleCount'); 608 if (!countElement) return; 609 610 const totalCount = articlesData.length; 611 const hasFilter = (searchTerm && searchTerm.trim() !== '') || !!activeTag; 612 613 if (hasFilter) { 614 countElement.textContent = `該å½${count}ä»¶ / å ¨${totalCount}ä»¶`; 615 } else { 616 countElement.textContent = `å ¨${count}ä»¶ã®è¨äº`; 617 } 618} 619 620// =================== 621// ã©ã³ãã è¨äºè¡¨ç¤º 622// =================== 623function siteRootPrefix() { 624 return (window.BASE_PATH || '').indexOf('new_toppage') === 0 ? '' : '../'; 625} 626function pickRand(arr) { 627 if (!arr || !arr.length) return null; 628 return arr[Math.floor(Math.random() * arr.length)]; 629} 630function pickRandN(arr, n) { 631 if (!arr || !arr.length || n <= 0) return []; 632 const copy = arr.slice(); 633 for (let i = copy.length - 1; i > 0; i--) { 634 const j = Math.floor(Math.random() * (i + 1)); 635 [copy[i], copy[j]] = [copy[j], copy[i]]; 636 } 637 return copy.slice(0, Math.min(n, copy.length)); 638} 639function dirOfPath(path) { 640 return String(path || '').replace(/index\.html$/i, '').replace(/\/?$/, '/'); 641} 642 643async function displayRandomArticles() { 644 const randomContainer = document.getElementById('randomArticles'); 645 const randomSection = randomContainer && randomContainer.closest('.random-section'); 646 if (!randomContainer || randomContainer.dataset.mixed === '1') return; 647 648 const root = siteRootPrefix(); 649 const bp = (window.BASE_PATH || 'new_toppage/').replace(/\/?$/, '/'); 650 651 const fetchJson = (url, fallback) => fetch(url, { cache: 'no-store' }) 652 .then((r) => r.ok ? r.json() : fallback) 653 .catch(() => fallback); 654 655 const [br, ur, gm, sw] = await Promise.all([ 656 fetchJson(bp + 'data/briefings.json', { briefings: [] }), 657 fetchJson(root + 'uranai/data/uranai.json', { omikuji: [] }), 658 fetchJson(root + 'online-free-game/data/games.json', { games: [] }), 659 fetchJson(root + 'online-free-software/data/software.json', { software: [] }) 660 ]); 661 662 const items = []; 663 const notFuture = (dateStr) => { 664 if (shouldShowFutureArticles() || !dateStr) return true; 665 const d = new Date(dateStr); 666 if (isNaN(d.getTime())) return true; 667 const today = new Date(); 668 today.setHours(0, 0, 0, 0); 669 d.setHours(0, 0, 0, 0); 670 return d <= today; 671 }; 672 const briefingPool = (br.briefings || []).filter((b) => notFuture(b.date)); 673 const omikujiPool = (ur.omikuji || []).filter((o) => notFuture(o.createdAt)); 674 const gamePool = (gm.games || []).filter((g) => notFuture(g.created)); 675 const softwarePool = (sw.software || []).filter((s) => notFuture(s.created || s.released || s.updated)); 676 const used = {}; 677 function pushUnique(item) { 678 if (!item || !item.href || used[item.href] || items.length >= 8) return; 679 used[item.href] = true; 680 items.push(item); 681 } 682 pickRandN(briefingPool, 2).forEach((briefing) => { 683 pushUnique({ 684 href: bp + (briefing.path || ''), 685 thumb: briefing.thumbnail ? bp + briefing.thumbnail : '', 686 title: briefing.title || '', 687 desc: briefing.description || '', 688 date: formatDate(briefing.date), 689 readTime: briefing.readTime || 3, 690 tags: briefing.tags || [] 691 }); 692 }); 693 pickRandN(articlesData || [], 2).forEach((art) => { 694 pushUnique({
695 href: getArticleReadUrl(art), 696 thumb: art.thumbnail ? bp + art.thumbnail : '', 697 title: art.title || art.id || '', 698 desc: art.description || '', 699 date: formatDate(art.date), 700 readTime: art.readTime || 5, 701 tags: art.tags || [] 702 }); 703 }); 704 pickRandN(omikujiPool, 1).forEach((omikuji) => { 705 const id = omikuji.id || ''; 706 pushUnique({ 707 href: root + 'uranai/' + dirOfPath(omikuji.path), 708 thumb: root + 'uranai/' + dirOfPath(omikuji.path) + 'ogp.png', 709 title: omikuji.title || id, 710 desc: omikuji.description || (omikuji.tags || []).join(' · '), 711 date: formatDate(omikuji.createdAt), 712 readTime: '', 713 tags: omikuji.tags || [] 714 }); 715 }); 716 pickRandN(gamePool, 2).forEach((game) => { 717 const dir = root + 'online-free-game/' + dirOfPath(game.path); 718 pushUnique({ 719 href: dir, 720 thumb: dir + 'ogp.png', 721 title: game.title || game.id || '', 722 desc: (game.tags || []).filter((t) => t && t !== '·').join(' · '), 723 date: formatDate(game.created), 724 readTime: '', 725 tags: game.tags || [] 726 }); 727 }); 728 pickRandN(softwarePool, 1).forEach((tool) => { 729 const dir = root + 'online-free-software/' + dirOfPath(tool.path); 730 const tags = (tool.tags || []).filter((t) => t && t !== '·'); 731 pushUnique({ 732 href: dir, 733 thumb: tool.logo 734 ? root + 'online-free-software/' + String(tool.logo).replace(/^\//, '') 735 : (dir + 'logo.png'), 736 title: tool.title || tool.id || '', 737 desc: tool.description || tags.join(' · '), 738 date: formatDate(tool.created || tool.released || tool.updated), 739 readTime: '', 740 tags: tags 741 }); 742 }); 743 const leftovers = [] 744 .concat(pickRandN(briefingPool, 12).map((briefing) => ({ 745 href: bp + (briefing.path || ''), 746 thumb: briefing.thumbnail ? bp + briefing.thumbnail : '', 747 title: briefing.title || '', 748 desc: briefing.description || '', 749 date: formatDate(briefing.date), 750 readTime: briefing.readTime || 3, 751 tags: briefing.tags || [] 752 }))) 753 .concat(pickRandN(articlesData || [], 12).map((art) => ({ 754 href: getArticleReadUrl(art), 755 thumb: art.thumbnail ? bp + art.thumbnail : '', 756 title: art.title || art.id || '', 757 desc: art.description || '', 758 date: formatDate(art.date), 759 readTime: art.readTime || 5, 760 tags: art.tags || [] 761 }))) 762 .concat(pickRandN(omikujiPool, 8).map((omikuji) => ({ 763 href: root + 'uranai/' + dirOfPath(omikuji.path), 764 thumb: root + 'uranai/' + dirOfPath(omikuji.path) + 'ogp.png', 765 title: omikuji.title || omikuji.id || '', 766 desc: omikuji.description || (omikuji.tags || []).join(' · '), 767 date: formatDate(omikuji.createdAt), 768 readTime: '', 769 tags: omikuji.tags || [] 770 }))) 771 .concat(pickRandN(gamePool, 8).map((game) => { 772 const dir = root + 'online-free-game/' + dirOfPath(game.path); 773 return { 774 href: dir, 775 thumb: dir + 'ogp.png', 776 title: game.title || game.id || '', 777 desc: (game.tags || []).filter((t) => t && t !== '·').join(' · '), 778 date: formatDate(game.created), 779 readTime: '', 780 tags: game.tags || [] 781 }; 782 })) 783 .concat(pickRandN(softwarePool, 8).map((tool) => { 784 const dir = root + 'online-free-software/' + dirOfPath(tool.path); 785 const tags = (tool.tags || []).filter((t) => t && t !== '·'); 786 return { 787 href: dir, 788 thumb: tool.logo 789 ? root + 'online-free-software/' + String(tool.logo).replace(/^\//, '') 790 : (dir + 'logo.png'), 791 title: tool.title || tool.id || '', 792 desc: tool.description || tags.join(' · '), 793 date: formatDate(tool.created || tool.released || tool.updated), 794 readTime: '', 795 tags: tags 796 }; 797 })); 798 leftovers.sort(() => Math.random() - 0.5);
799 leftovers.forEach(pushUnique); 800 801 items.sort(() => Math.random() - 0.5); 802 if (randomSection) randomSection.style.display = items.length ? 'block' : 'none'; 803 if (!items.length) return; 804 randomContainer.dataset.mixed = '1'; 805 randomContainer.innerHTML = items.map(createBriefingStyleCard).join(''); 806 paintCardBars(randomContainer); 807} 808 809// =================== 810// åç»åçã¢ã¼ãã« 811// =================== 812function playVideo(videoUrl, title) { 813 // ã¢ã¼ãã«ã使 814 const modal = document.createElement('div'); 815 modal.id = 'videoModal'; 816 modal.style.cssText = ` 817 position: fixed; 818 top: 0; 819 left: 0; 820 width: 100%; 821 height: 100%; 822 background: rgba(0, 0, 0, 0.9); 823 display: flex; 824 align-items: center; 825 justify-content: center; 826 z-index: 10000; 827 animation: fadeIn 0.3s ease; 828 `; 829 830 modal.innerHTML = ` 831 <div style="position: relative; width: 90%; max-width: 1200px; background: #1a1a1a; border-radius: 12px; overflow: hidden; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);"> 832 <div style="padding: 1.5rem; background: #2c3e50; color: white; display: flex; justify-content: space-between; align-items: center;"> 833 <h3 style="margin: 0; font-size: 1.2rem;">${title}</h3> 834 <button onclick="closeVideoModal()" style="background: none; border: none; color: white; font-size: 2rem; cursor: pointer; padding: 0; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.3s ease;"> 835 à 836 </button> 837 </div> 838 <div style="padding: 0; background: black;"> 839 <video controls autoplay style="width: 100%; height: auto; display: block; max-height: 70vh;"> 840 <source src="${videoUrl}" type="video/mp4"> 841 ã使ãã®ãã©ã¦ã¶ã¯åç»ã¿ã°ããµãã¼ããã¦ãã¾ããã 842 </video> 843 </div> 844 </div> 845 `; 846 847 // ã¢ã¼ãã«å¤ãã¯ãªãã¯ã§éãã 848 modal.addEventListener('click', (e) => { 849 if (e.target === modal) { 850 closeVideoModal(); 851 } 852 }); 853 854 // ESCãã¼ã§éãã 855 document.addEventListener('keydown', function escHandler(e) { 856 if (e.key === 'Escape') { 857 closeVideoModal(); 858 document.removeEventListener('keydown', escHandler); 859 } 860 }); 861 862 document.body.appendChild(modal); 863} 864 865function closeVideoModal() { 866 const modal = document.getElementById('videoModal'); 867 if (modal) { 868 modal.style.animation = 'fadeOut 0.3s ease'; 869 setTimeout(() => modal.remove(), 300); 870 } 871} 872 873// ã¢ãã¡ã¼ã·ã§ã³ 874const style = document.createElement('style'); 875style.textContent = ` 876 @keyframes fadeIn { 877 from { opacity: 0; } 878 to { opacity: 1; } 879 } 880 @keyframes fadeOut { 881 from { opacity: 1; } 882 to { opacity: 0; } 883 } 884`; 885document.head.appendChild(style); 886 887// =================== 888// åç»åçã»åæ¢ã®ã¨ã©ã¼ãã³ããªã³ã° 889// =================== 890function handleVideoPlay(videoElement) { 891 if (!videoElement) return; 892 893 const playPromise = videoElement.play(); 894 895 if (playPromise !== undefined) { 896 playPromise.catch(error => { 897 // AbortErrorããã®ä»ã®ã¨ã©ã¼ãéãã«å¦ç 898 // ã¦ã¼ã¶ã¼ä½é¨ãæãªããªããããã³ã³ã½ã¼ã«ãã°ã¯åºããªã 899 }); 900 } 901} 902 903function handleVideoPause(videoElement) { 904 if (!videoElement) return; 905 906 try { 907 videoElement.pause(); 908 videoElement.currentTime = 0.1; 909 } catch (error) { 910 // ã¨ã©ã¼ãéãã«å¦ç 911 } 912} 913 914function handleRandomImageError(imgElement) { 915 if (!imgElement) return; 916 const basePath = window.BASE_PATH || ''; 917 imgElement.onerror = null; 918 imgElement.src = `${basePath}assets/images/default-thumbnail.svg`; 919} 920 921function handleRandomVideoError(videoElement) { 922 if (!videoElement) return; 923 const fallback = videoElement.dataset.fallback || `${window.BASE_PATH || ''}assets/images/default-thumbnail.svg`; 924 const altText = videoElement.dataset.alt || ''; 925 926 const img = document.createElement('img'); 927 img.src = fallback; 928 img.alt = altText; 929 img.className = videoElement.className; 930 img.loading = 'lazy'; 931 img.onerror = () => handleRandomImageError(img); 932 933 videoElement.replaceWith(img); 934} 935 936function renderRandomArticleCards() { 937 const randomContainer = document.getElementById('randomArticles'); 938 if (!randomContainer) return; 939
940 const cardsHtml = randomArticlesState.visibleIndices.map((poolIndex, cardIndex) => { 941 const article = randomArticlesState.pool[poolIndex]; 942 return createRandomArticleCard(article, cardIndex); 943 }).join(''); 944 945 randomContainer.innerHTML = cardsHtml; 946 attachRandomNavigationHandlers(); 947 948 console.log(`${randomArticlesState.visibleIndices.length}ä»¶ã®ã©ã³ãã è¨äºã«ã¼ããæç»ãã¾ããï¼ãã¼ã«:${randomArticlesState.pool.length}ä»¶ï¼`); 949 950 normalizeArticleCardHeights(); 951} 952 953function createRandomArticleCard(article, cardIndex) { 954 if (!article) return ''; 955 956 const basePath = window.BASE_PATH || ''; 957 const articleUrl = getArticleReadUrl(article); 958 const fallbackThumbnail = `${basePath}assets/images/default-thumbnail.svg`; 959 let thumbnailHtml = ''; 960 const safeTitle = escapeHtml(article.title); 961 962 if (article.videoUrl) { 963 const videoUrl = basePath + article.videoUrl; 964 const posterUrl = article.thumbnail ? basePath + article.thumbnail : fallbackThumbnail; 965 thumbnailHtml = `<video src="${videoUrl}" 966 class="article-card__thumbnail article-card__video" 967 data-alt="${safeTitle}" 968 poster="${posterUrl}" 969 muted 970 loop 971 playsinline 972 preload="metadata" 973 onloadeddata="this.currentTime=0.1" 974 onmouseover="handleVideoPlay(this)" 975 onmouseout="handleVideoPause(this)" 976 style="object-fit: cover; background: #000;" 977 data-fallback="${fallbackThumbnail}" 978 onerror="handleRandomVideoError(this)"></video>`; 979 } else if (article.thumbnail) { 980 const thumbnailUrl = basePath + article.thumbnail; 981 thumbnailHtml = `<img src="${thumbnailUrl}" 982 alt="${safeTitle}" 983 class="article-card__thumbnail" 984 loading="lazy" 985 onerror="handleRandomImageError(this)"/>`; 986 } else { 987 thumbnailHtml = `<img src="${fallbackThumbnail}" 988 alt="${safeTitle}" 989 class="article-card__thumbnail" 990 loading="lazy">`; 991 } 992 993 const tagsHtml = article.tags 994 ? article.tags.map(tag => `<span class="article-card__tag">#${escapeHtml(tag)}</span>`).join('') 995 : ''; 996 997 return ` 998 <div class="article-card" data-card-index="${cardIndex}"> 999 ${thumbnailHtml} 1000 <div class="article-card__content"> 1001 <div class="article-card__meta"> 1002 <span class="article-card__date"> 1003 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 1004 <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect> 1005 <line x1="16" y1="2" x2="16" y2="6"></line> 1006 <line x1="8" y1="2" x2="8" y2="6"></line> 1007 <line x1="3" y1="10" x2="21" y2="10"></line> 1008 </svg> 1009 ${formatDate(article.date)} 1010 </span> 1011 </div> 1012 <h3 class="article-card__title">${safeTitle}</h3> 1013 <p class="article-card__description">${escapeHtml(article.description || '')}</p> 1014 <div class="article-card__tags">${tagsHtml}</div> 1015 </div> 1016 <div class="random-card__nav" role="group" aria-label="ããããè¨äºã®åãæ¿ã"> 1017 <button type="button" 1018 class="random-card__btn random-card__btn--prev" 1019 data-random-card="${cardIndex}" 1020 data-direction="prev" 1021 aria-label="åã®è¨äºã¸"> 1022 â å 1023 </button> 1024 <a href="${articleUrl}" class="random-card__btn random-card__btn--primary"> 1025 èªã 1026 </a> 1027 <button type="button" 1028 class="random-card__btn random-card__btn--next" 1029 data-random-card="${cardIndex}" 1030 data-direction="next" 1031 aria-label="次ã®è¨äºã¸"> 1032 次 â 1033 </button> 1034 </div> 1035 </div> 1036 `; 1037} 1038 1039function attachRandomNavigationHandlers() { 1040 document.querySelectorAll('.random-card__btn').forEach(button => { 1041 button.addEventListener('click', (event) => { 1042 const target = event.currentTarget; 1043 const cardIndex = Number(target.dataset.randomCard); 1044 const direction = target.dataset.direction === 'prev' ? 'prev' : 'next'; 1045 handleRandomNavigation(cardIndex, direction); 1046 }); 1047 }); 1048} 1049 1050function handleRandomNavigation(cardIndex, direction) { 1051 const poolLength = randomArticlesState.pool.length; 1052 if (!Number.isInteger(cardIndex) || poolLength === 0) return; 1053 1054 const currentIndex = randomArticlesState.visibleIndices[cardIndex]; 1055 if (currentIndex === undefined) return; 1056
1057 if (poolLength === 1) { 1058 console.log('ã©ã³ãã è¨äº: ãã¼ã«ã1ä»¶ã®ããåãæ¿ããªã'); 1059 return; 1060 } 1061 1062 const step = direction === 'prev' ? -1 : 1; 1063 let nextIndex = currentIndex; 1064 1065 for (let offset = 1; offset <= poolLength; offset++) { 1066 const candidate = (currentIndex + step * offset + poolLength) % poolLength; 1067 1068 if (!randomArticlesState.visibleIndices.some((value, idx) => idx !== cardIndex && value === candidate) || 1069 poolLength <= randomArticlesState.visibleIndices.length) { 1070 nextIndex = candidate; 1071 break; 1072 } 1073 } 1074 1075 if (nextIndex === currentIndex) { 1076 console.log('ã©ã³ãã è¨äº: 代æ¿åè£ãè¦ã¤ãããåãæ¿ããªã'); 1077 return; 1078 } 1079 1080 randomArticlesState.visibleIndices[cardIndex] = nextIndex; 1081 updateRandomCard(cardIndex); 1082} 1083 1084function updateRandomCard(cardIndex) { 1085 const randomContainer = document.getElementById('randomArticles'); 1086 if (!randomContainer) return; 1087 1088 const poolIndex = randomArticlesState.visibleIndices[cardIndex]; 1089 const article = randomArticlesState.pool[poolIndex]; 1090 const currentCard = randomContainer.querySelector(`.article-card[data-card-index="${cardIndex}"]`); 1091 1092 if (!article || !currentCard) return; 1093 1094 currentCard.outerHTML = createRandomArticleCard(article, cardIndex); 1095 attachRandomNavigationHandlers(); 1096 normalizeArticleCardHeights(); 1097} 1098 1099function escapeHtml(str) { 1100 if (str === null || str === undefined) return ''; 1101 return String(str) 1102 .replace(/&/g, '&') 1103 .replace(/</g, '<') 1104 .replace(/>/g, '>') 1105 .replace(/"/g, '"') 1106 .replace(/'/g, '''); 1107} 1108 1109function normalizeArticleCardHeights(options = {}) { 1110 const { withDelay = true } = options; 1111 1112 window.requestAnimationFrame(() => { 1113 const selectors = ['#articlesGrid .article-card', '#randomArticles .article-card']; 1114 1115 selectors.forEach(selector => { 1116 const cards = document.querySelectorAll(selector); 1117 if (!cards.length) return; 1118 1119 const previousHeight = cardHeightCache.get(selector); 1120 1121 cards.forEach(card => { 1122 card.style.minHeight = ''; 1123 card.style.height = ''; 1124 }); 1125 1126 let maxHeight = 0; 1127 cards.forEach(card => { 1128 const height = card.getBoundingClientRect().height; 1129 if (height > maxHeight) { 1130 maxHeight = height; 1131 } 1132 }); 1133 1134 if (!maxHeight) return; 1135 1136 const normalizedHeight = Math.ceil(maxHeight); 1137 if (previousHeight === normalizedHeight) { 1138 cards.forEach(card => { 1139 card.style.minHeight = `${normalizedHeight}px`; 1140 card.style.height = `${normalizedHeight}px`; 1141 }); 1142 return; 1143 } 1144 1145 cardHeightCache.set(selector, normalizedHeight); 1146 1147 cards.forEach(card => { 1148 card.style.minHeight = `${normalizedHeight}px`; 1149 card.style.height = `${normalizedHeight}px`; 1150 }); 1151 }); 1152 }); 1153 1154 if (withDelay) { 1155 setTimeout(() => normalizeArticleCardHeights({ withDelay: false }), 400); 1156 } 1157} 1158 1159window.addEventListener('resize', debounce(() => { 1160 normalizeArticleCardHeights(); 1161}, 200)); 1162 1163window.addEventListener('load', () => { 1164 normalizeArticleCardHeights({ withDelay: false }); 1165}); 1166 1167// =================== 1168// Tech Briefingèªã¿è¾¼ã¿ 1169// =================== 1170async function loadBriefings() { 1171 const grid = document.getElementById('briefingsGrid'); 1172 if (!grid) { 1173 // briefingsGridãåå¨ããªããã¼ã¸ï¼åå¥è¨äºãã¼ã¸ãªã©ï¼ã§ã¯ä½ãããªã 1174 return; 1175 } 1176 1177 // æ¢ã«ã«ã¼ããã¬ã³ããªã³ã°æ¸ã¿ã§ããæªæ¥æ¥ä»ã¯æ¬çªã§ã¯å¤ã 1178 if (grid.querySelector('.briefing-card')) { 1179 if (!shouldShowFutureArticles()) { 1180 const today = new Date(); 1181 today.setHours(0, 0, 0, 0); 1182 grid.querySelectorAll('.briefing-card').forEach((card) => { 1183 const t = card.querySelector('time[datetime]'); 1184 const raw = t && t.getAttribute('datetime'); 1185 if (!raw) return; 1186 const d = new Date(raw); 1187 d.setHours(0, 0, 0, 0); 1188 if (d > today) card.remove(); 1189 }); 1190 }
1191 console.log('Tech Briefingè¨äºã¯æ¢ã«ãµã¼ãã¼ãµã¤ãã§ã¬ã³ããªã³ã°æ¸ã¿ã§ã'); 1192 paintCardBars(grid); 1193 return; 1194 } 1195 1196 try { 1197 const basePath = window.BASE_PATH || 'new_toppage/'; 1198 const dataUrl = `${basePath}data/briefings.json`; 1199 const cacheBuster = `_=${Date.now()}`; 1200 const fullUrl = `${dataUrl}?${cacheBuster}`; 1201 1202 console.log('Tech Briefingãã¼ã¿ãèªã¿è¾¼ã¿ä¸:', fullUrl); 1203 1204 const response = await fetch(fullUrl, { cache: 'no-store' }); 1205 1206 if (!response.ok) { 1207 throw new Error(`HTTPã¨ã©ã¼: ${response.status} ${response.statusText}`); 1208 } 1209 1210 const data = await response.json(); 1211 let briefings = data.briefings || []; 1212 1213 if (!Array.isArray(briefings)) { 1214 throw new Error('briefingsãã¼ã¿ãé åã§ã¯ããã¾ãã'); 1215 } 1216 1217 if (!shouldShowFutureArticles()) { 1218 const today = new Date(); 1219 today.setHours(0, 0, 0, 0); 1220 briefings = briefings.filter((b) => { 1221 if (!b.date) return true; 1222 const d = new Date(b.date); 1223 d.setHours(0, 0, 0, 0); 1224 return d <= today; 1225 }); 1226 } 1227 1228 briefings.sort((a, b) => { 1229 const dateA = new Date(a.date || '1900-01-01'); 1230 const dateB = new Date(b.date || '1900-01-01'); 1231 return dateB - dateA; 1232 }); 1233 1234 const displayBriefings = briefings.slice(0, 20); 1235 1236 if (displayBriefings.length === 0) { 1237 grid.innerHTML = '<p class="briefings-empty">
1237Tech Briefingè¨äºã¯ã¾ã ããã¾ããã</p>'; 1238 return; 1239 } 1240 1241 grid.innerHTML = displayBriefings.map(briefing => { 1242 const basePathForUrl = basePath.replace(/\/$/, ''); 1243 const thumbnailUrl = `${basePathForUrl}/${briefing.thumbnail}`; 1244 const briefingUrl = `${basePathForUrl}/${briefing.path}`; 1245 const date = new Date(briefing.date); 1246 const formattedDate = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`; 1247 1248 const tagsHtml = (briefing.tags || []).slice(0, 5).map(tag => 1249 `<span class="briefing-card__tag">#${tag}</span>` 1250 ).join(''); 1251 1252 return ` 1253 <article class="briefing-card"> 1254 <a href="${briefingUrl}" class="briefing-card__link"> 1255 <div class="briefing-card__thumbnail"> 1256 <img src="${thumbnailUrl}" 1257 alt="${briefing.title || ''}" 1258 loading="lazy" 1259 onerror="this.style.display='none';"> 1260 </div> 1261 <div class="briefing-card__content"> 1262 <div class="briefing-card__meta"> 1263 <time class="briefing-card__date" datetime="${briefing.date || ''}"> 1264 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 1265 <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect> 1266 <line x1="16" y1="2" x2="16" y2="6"></line> 1267 <line x1="8" y1="2" x2="8" y2="6"></line> 1268 <line x1="3" y1="10" x2="21" y2="10"></line> 1269 </svg> 1270 ${formattedDate} 1271 </time> 1272 <span class="briefing-card__read-time"> 1273 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 1274 <circle cx="12" cy="12" r="10"></circle> 1275 <polyline points="12 6 12 12 16 14"></polyline> 1276 </svg> 1277 ${briefing.readTime || 3}å 1278 </span> 1279 </div> 1280 <h3 class="briefing-card__title">${briefing.title || 'ã¿ã¤ãã«æªè¨å®'}</h3> 1281 <p class="briefing-card__description">${briefing.description || ''}</p> 1282 <div class="briefing-card__tags"> 1283 ${tagsHtml} 1284 </div> 1285 </div> 1286 </a> 1287 </article> 1288 `; 1289 }).join(''); 1290 1291 // ã¹ã¿ã¤ã«é©ç¨ã確å®ã«ãããããå¼·å¶çã«åããã¼ãçºçããã 1292 grid.offsetHeight; // ãªããã¼ãçºçããã 1293 1294 console.log(`${displayBriefings.length}ä»¶ã®Tech Briefingè¨äºã表示ãã¾ãã`); 1295 paintCardBars(grid); 1296 } catch (error) { 1297 console.error('Tech Briefingè¨äºã®èªã¿è¾¼ã¿ã¨ã©ã¼:', error); 1298 console.error('ã¨ã©ã¼è©³ç´°:', { 1299 message: error.message, 1300 stack: error.stack, 1301 url: `${window.BASE_PATH || 'new_toppage/'}data/briefings.json` 1302 }); 1303 1304 if (grid) { 1305 grid.innerHTML = `<p class="briefings-empty">
1305Tech Briefingè¨äºã®èªã¿è¾¼ã¿ã«å¤±æãã¾ããã<br><small style="color: #999;">ã¨ã©ã¼: ${error.message}</small></p>`; 1306 } 1307 } 1308} 1309 1310// =================== 1311// PowerWord Ocean ææ°ã¯ã¼ãï¼ã©ã³ãã ãã¼ãã¼ã·ã§ã³ï¼ 1312// =================== 1313const PW_SLOT_COUNT_DESKTOP = 40; 1314const PW_SLOT_COUNT_MOBILE = 20; 1315const PW_DESKTOP_MQ = window.matchMedia('(min-width: 1024px)'); 1316const PW_FADE_MS = 1500; 1317const PW_MIN_STAY_MS = 16500; 1318const PW_MAX_STAY_MS = 39000; 1319 1320function pwRandomStayMs() { 1321 return PW_MIN_STAY_MS + Math.random() * (PW_MAX_STAY_MS - PW_MIN_STAY_MS); 1322} 1323 1324function pwGetSlotCount() { 1325 return PW_DESKTOP_MQ.matches ? PW_SLOT_COUNT_DESKTOP : PW_SLOT_COUNT_MOBILE; 1326} 1327 1328function pwGetSlotTier() { 1329 return PW_DESKTOP_MQ.matches ? 'desktop' : 'mobile'; 1330} 1331 1332function pwWordKey(w) { 1333 return w.path || w.word || ''; 1334} 1335 1336function pwPickWord(pool, exclude) { 1337 const available = pool.filter((w) => !exclude.has(pwWordKey(w))); 1338 const source = available.length > 0 ? available : pool; 1339 return source[Math.floor(Math.random() * source.length)]; 1340} 1341 1342function pwSetSlotWord(link, w, base) { 1343 link.href = `${base}/${(w.path || '').replace(/^\//, '')}`; 1344 link.dataset.wordKey = pwWordKey(w); 1345 link.querySelector('.pw-ocean-teaser__word-name').textContent = w.word || ''; 1346} 1347 1348function pwScheduleRotation(slot, pool, base, visibleSet) { 1349 if (slot._rotateTimer) clearTimeout(slot._rotateTimer); 1350 slot._rotateTimer = setTimeout(() => pwRotateSlot(slot, pool, base, visibleSet), pwRandomStayMs()); 1351} 1352 1353function pwRotateSlot(slot, pool, base, visibleSet) { 1354 const link = slot.querySelector('.pw-ocean-teaser__word'); 1355 if (!link) return; 1356 1357 link.classList.remove('pw-ocean-teaser__word--visible'); 1358 link.classList.add('pw-ocean-teaser__word--fading'); 1359 1360 setTimeout(() => { 1361 visibleSet.delete(link.dataset.wordKey || ''); 1362 const w = pwPickWord(pool, visibleSet); 1363 visibleSet.add(pwWordKey(w)); 1364 pwSetSlotWord(link, w, base); 1365 1366 link.classList.remove('pw-ocean-teaser__word--fading'); 1367 requestAnimationFrame(() => { 1368 link.classList.add('pw-ocean-teaser__word--visible'); 1369 }); 1370 1371 pwScheduleRotation(slot, pool, base, visibleSet); 1372 }, PW_FADE_MS); 1373} 1374 1375function pwCreateSlot() { 1376 const li = document.createElement('li'); 1377 li.className = 'pw-ocean-teaser__slot'; 1378 li.innerHTML = `<a href="#" class="pw-ocean-teaser__word pw-ocean-teaser__word--fading"> 1379 <span class="pw-ocean-teaser__word-name"></span> 1380 </a>`; 1381 return li; 1382} 1383 1384function pwSeededRandom(index, salt) { 1385 const x = Math.sin(index * 127.1 + salt * 311.7) * 43758.5453; 1386 return x - Math.floor(x); 1387} 1388 1389function pwDecorateSlots(list) { 1390 const slots = [...list.querySelectorAll('.pw-ocean-teaser__slot')]; 1391 slots.forEach((slot, i) => { 1392 slot.style.position = ''; 1393 slot.style.left = ''; 1394 slot.style.top = ''; 1395 slot.style.zIndex = ''; 1396 1397 const mx = Math.round((pwSeededRandom(i, 1) - 0.5) * 8); 1398 const rot = ((pwSeededRandom(i, 3) - 0.5) * 1.2).toFixed(2); 1399 1400 slot.style.marginLeft = mx ? `${mx}px` : ''; 1401 slot.style.marginTop = ''; 1402 slot.style.transform = rot !== '0.00' ? `rotate(${rot}deg)` : ''; 1403 }); 1404 1405 pwLockWordsHeight(list); 1406} 1407 1408function pwLockWordsHeight(list) { 1409 if (list) list.style.minHeight = ''; 1410} 1411 1412let pwHeightResizeTimer; 1413let pwLastSlotTier = ''; 1414 1415function pwBindWordsHeightLock(list, words, base) { 1416 pwLockWordsHeight(list); 1417 1418 if (list.dataset.pwResizeBound === '1') return; 1419 list.dataset.pwResizeBound = '1'; 1420 pwLastSlotTier = pwGetSlotTier(); 1421 1422 window.addEventListener('resize', () => { 1423 clearTimeout(pwHeightResizeTimer); 1424 pwHeightResizeTimer = setTimeout(() => { 1425 const tier = pwGetSlotTier(); 1426 if (tier !== pwLastSlotTier) { 1427 pwLastSlotTier = tier; 1428 initPowerWordRotator(list, words, base); 1429 return; 1430 } 1431 pwDecorateSlots(list); 1432 pwLockWordsHeight(list); 1433 }, 150); 1434 }); 1435} 1436 1437function initPowerWordRotator(list, words, base) { 1438 list.querySelectorAll('li').forEach((slot) => { 1439 if (slot._rotateTimer) clearTimeout(slot._rotateTimer); 1440 }); 1441 1442 if (!words.length) { 1443 list.innerHTML = '<li class="pw-ocean-teaser__empty">ã¯ã¼ãã¯ã¾ã ç»é²ããã¦ãã¾ããã</li>'; 1444 return; 1445 } 1446 1447 const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 1448 const pool = words; 1449 const visibleSet = new Set(); 1450 1451 list.innerHTML = ''; 1452 const slotCount = Math.min(pwGetSlotCount(), pool.length); 1453 1454 for (let i = 0; i < slotCount; i++) { 1455 const slot = pwCreateSlot(); 1456 list.appendChild(slot); 1457 1458 const link = slot.querySelector('.pw-ocean-teaser__word'); 1459 const w = pwPickWord(pool, visibleSet); 1460 visibleSet.add(pwWordKey(w)); 1461 pwSetSlotWord(link, w, base); 1462 1463 if (reduceMotion) { 1464 link.classList.add('pw-ocean-teaser__word--visible'); 1465 continue; 1466 } 1467 1468 const initialDelay = Math.random() * 1800; 1469 setTimeout(() => { 1470 link.classList.remove('pw-ocean-teaser__word--fading'); 1471 link.classList.add('pw-ocean-teaser__word--visible'); 1472 pwScheduleRotation(slot, pool, base, visibleSet); 1473 }, initialDelay); 1474 } 1475 1476 pwDecorateSlots(list); 1477 pwBindWordsHeightLock(list, words, base); 1478} 1479 1480async function loadPowerWords() { 1481 const list = document.getElementById('powerWordLatest'); 1482 if (!list || list.dataset.rotatorReady === '1') return; 1483 1484 try { 1485 const basePath = window.BASE_PATH || 'new_toppage/'; 1486 const dataUrl = `${basePath}data/power-word.json`; 1487 const response = await fetch(`${dataUrl}?_=${Date.now()}`, { cache: 'no-store' }); 1488 if (!response.ok) { 1489 throw new Error(`HTTP ${response.status}`); 1490 } 1491 1492 const data = await response.json(); 1493 const words = Array.isArray(data.words) ? data.words : []; 1494 const base = basePath.replace(/\/$/, ''); 1495 1496 list.dataset.rotatorReady = '1'; 1497 initPowerWordRotator(list, words, base); 1498 } catch (error) { 1499 console.error('PowerWordèªã¿è¾¼ã¿ã¨ã©ã¼:', error); 1500 if (!list.querySelector('.pw-ocean-teaser__word')) { 1501 list.innerHTML = '<li class="pw-ocean-teaser__empty">ã¯ã¼ãã®èªã¿è¾¼ã¿ã«å¤±æãã¾ããã</li>'; 1502 } 1503 } 1504} 1505 1506function initGrovePager(grid) { 1507 if (!grid) return; 1508 const tiles = Array.from(grid.querySelectorAll('.grove-tile, .news-card')); 1509 const nav = grid.parentElement && grid.parentElement.querySelector('.grove-pager'); 1510 if (!tiles.length) return; 1511 const base = Number(grid.getAttribute('data-grove-page-size') || 8) || 8; 1512 function size() { 1513 return window.matchMedia('(max-width: 1024px)').matches ? Math.max(1, Math.floor(base / 2)) : base; 1514 } 1515 let page = 0; 1516 const status = nav && nav.querySelector('[data-grove-status]'); 1517 const prev = nav && nav.querySelector('[data-grove-prev]'); 1518 const next = nav && nav.querySelector('[data-grove-next]'); 1519 function lockMinHeight() { 1520 requestAnimationFrame(function () { 1521 const h = grid.scrollHeight; 1522 const cur = parseFloat(grid.style.minHeight) || 0; 1523 if (h > cur) grid.style.minHeight = h + 'px'; 1524 }); 1525 } 1526 function hydrateImgs(el) { 1527 el.querySelectorAll('img[data-src]').forEach((img) => { 1528 const src = img.getAttribute('data-src'); 1529 if (!src) return; 1530 img.removeAttribute('data-src'); 1531 img.src = src; 1532 }); 1533 } 1534 function show() { 1535 const sz = size();
1536 const pages = Math.max(1, Math.ceil(tiles.length / sz)); 1537 if (page >= pages) page = pages - 1; 1538 tiles.forEach((el, i) => { 1539 const on = i >= page * sz && i < (page + 1) * sz; 1540 el.hidden = !on; 1541 if (on) hydrateImgs(el); 1542 }); 1543 if (nav) nav.hidden = tiles.length <= sz; 1544 if (status) status.textContent = (page + 1) + ' / ' + pages; 1545 if (prev) prev.disabled = page === 0; 1546 if (next) next.disabled = page >= pages - 1; 1547 if (page === 0) lockMinHeight(); 1548 } 1549 tiles.slice(0, size()).forEach(function (el) { 1550 el.querySelectorAll('img').forEach(function (img) { 1551 if (!img.complete) { 1552 img.addEventListener('load', function () { if (page === 0) lockMinHeight(); }, { once: true }); 1553 } 1554 }); 1555 }); 1556 if (grid._onGroveResize) window.removeEventListener('resize', grid._onGroveResize); 1557 grid._onGroveResize = function () { 1558 grid.style.minHeight = ''; 1559 show(); 1560 }; 1561 window.addEventListener('resize', grid._onGroveResize); 1562 if (prev) prev.onclick = function () { if (page > 0) { page -= 1; show(); } }; 1563 if (next) next.onclick = function () { if ((page + 1) * size() < tiles.length) { page += 1; show(); } }; 1564 show(); 1565 paintCardBars(grid); 1566} 1567function paintCardBars(root) { 1568 const colors = ['#38bdf8', '#5dade2', '#14b8a6', '#2dd4bf', '#a78bfa', '#7c3aed', '#f59e0b', '#fb7185', '#f472b6', '#34d399', '#f97316', '#0ea5e9']; 1569 const scope = root && root.querySelectorAll ? root : document; 1570 const list = scope.matches && scope.matches('.news-card, .grove-tile, .briefing-card') 1571 ? [scope] 1572 : Array.from(scope.querySelectorAll('.news-card, .grove-tile, .briefing-card')); 1573 list.forEach((el) => { 1574 el.style.setProperty('--card-bar', colors[Math.floor(Math.random() * colors.length)]); 1575 }); 1576} 1577window.paintCardBars = paintCardBars; 1578window.initGrovePager = initGrovePager; 1579document.querySelectorAll('[data-grove-page-size]').forEach(initGrovePager); 1580paintCardBars(); 1581 1582document.addEventListener('click', function (e) { 1583 const btn = e.target && e.target.closest && e.target.closest('[data-topics-more]'); 1584 if (!btn) return; 1585 const digest = btn.closest('.topics-digest'); 1586 if (!digest) return; 1587 const step = Math.max(1, Number(digest.getAttribute('data-topics-step') || 3) || 3); 1588 const hidden = Array.from(digest.querySelectorAll('.topic-row[hidden]')); 1589 hidden.slice(0, step).forEach(function (el) { el.hidden = false; }); 1590 const left = digest.querySelectorAll('.topic-row[hidden]').length; 1591 if (!left) { 1592 btn.remove(); 1593 return; 1594 } 1595 const show = Math.min(step, left); 1596 btn.textContent = '次ã®' + show + 'ä»¶ãè¦ãï¼æ®ã' + left + 'ï¼'; 1597}); 1598 1599// =================== 1600// ã¨ã¯ã¹ãã¼ãï¼ã°ãã¼ãã«ã¹ã³ã¼ãç¨ï¼ 1601// =================== 1602window.changePage = changePage; 1603window.playVideo = playVideo; 1604window.closeVideoModal = closeVideoModal; 1605window.handleVideoPlay = handleVideoPlay; 1606window.handleVideoPause = handleVideoPause; 1607window.handleRandomVideoError = handleRandomVideoError; 1608window.handleRandomImageError = handleRandomImageError; 1609
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.