1/** 2 * ã¹ãã³ãµã¼è¡¨ç¤ºã·ã¹ãã 3 * APIããã¹ãã³ãµã¼æ å ±ãåå¾ããåçã«è¡¨ç¤º 4 */ 5 6(function(window) { 7 'use strict'; 8 9 const CONFIG = { 10 // APIè¨å® 11 API: { 12 URL: 'https://fortee.jp/komekaigi-2025/api/sponsors', 13 TIMEOUT: 10000, // APIã¿ã¤ã ã¢ã¦ãï¼10ç§ï¼ 14 RETRY_COUNT: 3, // ãªãã©ã¤åæ° 15 RETRY_DELAY: 1000 // ãªãã©ã¤ééï¼ããªç§ï¼ 16 }, 17 18 // ã³ã³ãã³ãè¨å® 19 CONTENT: { 20 MIN_PR_LENGTH: 20 // PRæã®æå°æåæ° 21 }, 22 23 // ã¹ãã³ãµã¼ã¿ã¤ã 24 SPONSOR_TYPES: { 25 GOLD: { key: 'gold', label: 'GOLD', labelJp: 'ã´ã¼ã«ãã¹ãã³ãµã¼' }, 26 SILVER: { key: 'silver', label: 'SILVER', labelJp: 'ã·ã«ãã¼ã¹ãã³ãµã¼' }, 27 VENUE: { key: 'venue', label: 'VENUE', labelJp: 'ä¼å ´æä¾' }, 28 ACCOUNTING: { key: 'accounting', label: 'ACCOUNTING', labelJp: 'ä¼è¨åå' } 29 } 30 }; 31 32 let sponsorsData = []; 33 34 /** 35 * ã¹ãã³ãµã¼æ å ±ãåå¾ãã¦è¡¨ç¤ºï¼ãªãã©ã¤æ©è½ä»ãï¼ 36 * @param {number} retryCount - ãªãã©ã¤åæ° 37 */ 38 async function loadSponsors(retryCount = 0) { 39 const sponsorSection = document.getElementById('sponsors-content'); 40 let shouldRetry = false; 41 42 try { 43 // ã¿ã¤ã ã¢ã¦ãè¨å®ä»ãfetch 44 const controller = new AbortController(); 45 const timeout = setTimeout(() => controller.abort(), CONFIG.API.TIMEOUT); 46 47 const response = await fetch(CONFIG.API.URL, { 48 signal: controller.signal 49 }); 50 clearTimeout(timeout); 51 52 if (!response.ok) { 53 shouldRetry = true; 54 } else { 55 const data = await response.json(); 56 57 if (!data || !data.sponsor_plans) { 58 shouldRetry = true; 59 } else { 60 // æ£å¸¸ã«ãã¼ã¿ãåå¾ã§ããå ´å 61 const sponsorsByType = categorizeSponsors(data.sponsor_plans); 62 renderSponsors(sponsorsByType); 63 return; // æåããã®ã§çµäº 64 } 65 } 66 67 } catch (error) { 68 // ãããã¯ã¼ã¯ã¨ã©ã¼ãã¿ã¤ã ã¢ã¦ããªã© 69 shouldRetry = true; 70 } 71 72 // ã¨ã©ã¼å¦ç 73 if (shouldRetry) { 74 // ãªãã©ã¤å¦ç 75 if (retryCount < CONFIG.API.RETRY_COUNT - 1) { 76 setTimeout(() => loadSponsors(retryCount + 1), CONFIG.API.RETRY_DELAY); 77 } else { 78 // ã¨ã©ã¼æã¯ä½ã表示ããªãï¼ã»ã¯ã·ã§ã³èªä½ãéè¡¨ç¤ºï¼ 79 hideSponsorsSection(sponsorSection); 80 } 81 } 82 } 83 84 /** 85 * ã¹ãã³ãµã¼ã»ã¯ã·ã§ã³ãé表示ã«ãã 86 * @param {HTMLElement} contentElement - ã³ã³ãã³ãè¦ç´ 87 */ 88 function hideSponsorsSection(contentElement) { 89 if (contentElement) { 90 contentElement.innerHTML = ''; 91 } 92 const sponsorsSection = document.getElementById('sponsors'); 93 if (sponsorsSection) { 94 sponsorsSection.style.display = 'none'; 95 } 96 } 97 98/** 99 * ã¹ãã³ãµã¼ãã¿ã¤ãå¥ã«åé¡ 100 * @param {Array} sponsorPlans - APIããåå¾ããã¹ãã³ãµã¼ãã©ã³ 101 * @returns {Object} ã¿ã¤ãå¥ã«åé¡ãããã¹ãã³ãµã¼ 102 */ 103function categorizeSponsors(sponsorPlans) { 104 const sponsorsByType = {}; 105 Object.values(CONFIG.SPONSOR_TYPES).forEach(config => { 106 sponsorsByType[config.key] = []; 107 }); 108 109 sponsorPlans.forEach(plan => { 110 const type_en = plan.name_en; 111 112 if (type_en && sponsorsByType[type_en]) { 113 filterValidSponsors(plan.sponsors).forEach(sponsor => { 114 sponsorsByType[type_en].push(sponsor); 115 sponsorsData.push(sponsor); 116 }); 117 } 118 }); 119 120 return sponsorsByType; 121} 122 123/** 124 * æå¹ãªã¹ãã³ãµã¼ã®ã¿ããã£ã«ã¿ãªã³ã° 125 * - avatarãåå¨ããã¹ãã³ãµã¼ 126 * @param {Array} sponsors - ã¹ãã³ãµã¼ãªã¹ã 127 * @returns {Array} ãã£ã«ã¿ãªã³ã°æ¸ã¿ã¹ãã³ãµã¼ãªã¹ã 128 */ 129function filterValidSponsors(sponsors) { 130 return sponsors.filter(sponsor => sponsor.avatar); 131} 132 133/** 134 * ã¹ãã³ãµã¼ã»ã¯ã·ã§ã³ãã¬ã³ããªã³ã° 135 * @param {Object} sponsorsByType - ã¿ã¤ãå¥ã¹ãã³ãµã¼ 136 */
137function renderSponsors(sponsorsByType) { 138 const sponsorSection = document.getElementById('sponsors-content'); 139 if (!sponsorSection) return; 140 141 let html = ''; 142 143 // åã¿ã¤ãã®ã¹ãã³ãµã¼ãã¬ã³ããªã³ã° 144 Object.values(CONFIG.SPONSOR_TYPES).forEach(config => { 145 const type = config.key; 146 const sponsors = sponsorsByType[type]; 147 148 if (sponsors && sponsors.length > 0) { 149 html += renderSponsorSection(type, config, sponsors); 150 } 151 }); 152 153 sponsorSection.innerHTML = html; 154} 155 156/** 157 * ã¹ãã³ãµã¼ã»ã¯ã·ã§ã³ã®HTMLãçæ 158 * @param {string} type - ã¹ãã³ãµã¼ã¿ã¤ã 159 * @param {Object} config - ã¿ã¤ãè¨å® 160 * @param {Array} sponsors - ã¹ãã³ãµã¼ãªã¹ã 161 * @returns {string} HTMLæåå 162 */ 163function renderSponsorSection(type, config, sponsors) { 164 const cards = sponsors 165 .map(sponsor => createSponsorCard(sponsor, type)) 166 .map(card => `<div class="sponsor-card-wrapper sponsor-card-${type}">${card}</div>`); 167 168 if (cards.length === 0) return ''; 169 170 return ` 171 <div class="sponsor-section-${type}"> 172 <div class="container mx-auto px-4${type !== 'silver' ? ' max-w-6xl' : ''}"> 173 <div class="text-center mb-8"> 174 <h4 class="text-2xl font-semibold text-primary"> 175 ${config.labelJp} 176 </h4> 177 </div> 178 <div class="sponsor-cards-container sponsor-cards-${type}"> 179 ${cards.join('')} 180 </div> 181 </div> 182 </div> 183 `; 184} 185 186/** 187 * ã¹ãã³ãµã¼ã«ã¼ãã使 188 * @param {Object} sponsor - ã¹ãã³ãµã¼æ å ± 189 * @param {string} type - ã¹ãã³ãµã¼ã¿ã¤ã 190 * @returns {string} ã«ã¼ãHTML 191 */ 192function createSponsorCard(sponsor, type) { 193 const name = sponsor.name || ''; 194 const logoUrl = sponsor.avatar; 195 const sponsorId = sponsor.id; 196 197 return ` 198 <div class="sponsor-card sponsor-card-${type}" data-sponsor-id="${sponsorId}"> 199 <div class="sponsor-logo-wrapper overflow-container"> 200 <img src="${logoUrl}" alt="${name}" loading="lazy" 201 class="image-border-fix sponsor-logo-img" 202 data-sponsor-name="${name.replace(/"/g, '"')}" 203 data-sponsor-type="${type}"> 204 </div> 205 </div> 206 `; 207} 208 209// ========================================== 210// ã¢ã¼ãã«é¢é£ 211// ========================================== 212 213/** 214 * ã¹ãã³ãµã¼ã¢ã¼ãã«ãéã 215 * @param {string} sponsorId - ã¹ãã³ãµã¼ID 216 */ 217function openSponsorModal(sponsorId) { 218 const sponsor = sponsorsData.find(s => s.id === sponsorId); 219 if (!sponsor) return; 220 221 const modal = document.getElementById('sponsorModal'); 222 const modalLogo = document.getElementById('modalLogo'); 223 const modalInfo = document.getElementById('modalInfo'); 224 225 // ãã´ã®è¡¨ç¤º 226 renderModalLogo(modalLogo, sponsor); 227 228 // æ å ±ã®è¡¨ç¤º 229 renderModalInfo(modalInfo, sponsor); 230 231 // ã¢ã¼ãã«ã表示 232 modal.classList.add('active'); 233 document.body.style.overflow = 'hidden'; 234} 235 236/** 237 * ã¢ã¼ãã«ã®ãã´ãã¬ã³ããªã³ã° 238 * @param {HTMLElement} element - ãã´è¡¨ç¤ºè¦ç´ 239 * @param {Object} sponsor - ã¹ãã³ãµã¼æ å ± 240 */ 241function renderModalLogo(element, sponsor) { 242 element.innerHTML = ` 243 <div class="modal-logo-inner overflow-container"> 244 <img src="${sponsor.avatar}" alt="${sponsor.name}" class="image-border-fix"> 245 </div> 246 `; 247} 248 249/** 250 * ã¢ã¼ãã«ã®æ å ±ãã¬ã³ããªã³ã° 251 * @param {HTMLElement} element - æ å ±è¡¨ç¤ºè¦ç´ 252 * @param {Object} sponsor - ã¹ãã³ãµã¼æ å ± 253 */ 254function renderModalInfo(element, sponsor) { 255 const links = getSponsorLinks(sponsor); 256 257 element.innerHTML = ` 258 <div style="display: flex; flex-direction: column; height: 100%; min-height: 300px;"> 259 <div style="flex: 1;"> 260 <h4 style="margin-bottom: 1.5rem;"> 261 ${sponsor.name} 262 </h4> 263 ${sponsor.pr && sponsor.pr.length >= CONFIG.CONTENT.MIN_PR_LENGTH ? ` 264 <div> 265 <p class="text-gray-700 leading-relaxed">${sponsor.pr}</p> 266 </div> 267 ` : ''} 268 </div> 269 ${links ? ` 270 <div style="margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e5e7eb;"> 271 <div class="flex gap-3 justify-start"> 272 ${links} 273 </div> 274 </div> 275 ` : ''} 276 </div> 277 `; 278} 279 280/** 281 * ã¹ãã³ãµã¼ãªã³ã¯ãçæ 282 * @param {Object} sponsor - ã¹ãã³ãµã¼æ å ± 283 * @returns {string} ãªã³ã¯HTML 284 */ 285function getSponsorLinks(sponsor) { 286 const links = []; 287 288 if (sponsor.url) { 289 links.push(` 290 <a href="${sponsor.url}" target="_blank" rel="noopener noreferrer" 291 class="inline-flex items-center px-4 py-2 bg-[#fabe00] text-white font-medium rounded-lg hover:bg-amber-600 transition-colors"> 292 <svg class="w-5 h-5 mr-2 fill-none stroke-current" viewBox="0 0 24 24"> 293 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 294 d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path> 295 </svg> 296 ã¹ãã³ãµã¼ãã¼ã¸ã¸ 297 </a> 298 `); 299 } 300 301 if (sponsor.twitter) { 302 const twitterUrl = sponsor.twitter.startsWith('http') 303 ? sponsor.twitter 304 : `https://x.com/${sponsor.twitter}`; 305 links.push(` 306 <a href="${twitterUrl}" target="_blank" rel="noopener noreferrer" 307 class="inline-flex items-center px-4 py-2 bg-black text-white font-medium rounded-lg hover:bg-gray-800 transition-colors"> 308 <svg class="w-5 h-5 fill-current" viewBox="0 0 24 24"> 309 <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/> 310 </svg> 311 </a> 312 `); 313 } 314 315 return links.join(''); 316} 317 318/** 319 * ã¹ãã³ãµã¼ã¢ã¼ãã«ãéãã 320 */ 321function closeSponsorModal() { 322 const modal = document.getElementById('sponsorModal'); 323 modal.classList.remove('active'); 324 document.body.style.overflow = ''; 325} 326 327// ========================================== 328// ã¤ãã³ããªã¹ãã¼ 329// ========================================== 330 331// ã¢ã¼ãã«ã®å¤å´ãã¯ãªãã¯ãããéãã 332window.addEventListener('click', function(event) { 333 const modal = document.getElementById('sponsorModal'); 334 if (event.target === modal) { 335 closeSponsorModal(); 336 } 337}); 338 339// ESCãã¼ã§ã¢ã¼ãã«ãéãã 340window.addEventListener('keydown', function(event) { 341 if (event.key === 'Escape') { 342 closeSponsorModal(); 343 } 344}); 345 346 // ========================================== 347 // ç»åã¨ã©ã¼ãã³ããªã³ã° 348 // ========================================== 349 350 /** 351 * ç»åèªã¿è¾¼ã¿ã¨ã©ã¼æã®ãã³ããªã³ã° 352 */ 353 function handleImageErrors() { 354 document.addEventListener('error', function(e) { 355 if (e.target.classList && e.target.classList.contains('sponsor-logo-img')) { 356 const img = e.target; 357 const wrapper = img.parentElement; 358 const name = img.getAttribute('data-sponsor-name'); 359 const type = img.getAttribute('data-sponsor-type'); 360 361 // ç»åãé表示ã«ãã¦ãã©ã¼ã«ããã¯è¡¨ç¤º 362 img.style.display = 'none'; 363 wrapper.innerHTML = `<div class="sponsor-card-fallback sponsor-card-fallback-${type}">${name}</div>`; 364 } 365 }, true); // ãã£ããã£ãªã³ã°ãã§ã¼ãºã§å¦ç 366 } 367 368 // ========================================== 369 // åæåå¦ç 370 // ========================================== 371 372 // ãã¼ã¸èªã¿è¾¼ã¿æã®åæå 373 document.addEventListener('DOMContentLoaded', function() { 374 loadSponsors(); 375 handleImageErrors(); 376 377 // ã¹ãã³ãµã¼ã«ã¼ãã®ã¯ãªãã¯ã¤ãã³ãï¼ã¤ãã³ãå§è²ï¼ 378 document.addEventListener('click', function(e) { 379 const card = e.target.closest('.sponsor-card'); 380 if (card && card.dataset.sponsorId) { 381 openSponsorModal(card.dataset.sponsorId); 382 } 383 }); 384 385 // ã¢ã¼ãã«ã®éãããã¿ã³ã®ã¤ãã³ããªã¹ãã¼ 386 const modalCloseButton = document.getElementById('modalCloseButton'); 387 if (modalCloseButton) { 388 modalCloseButton.addEventListener('click', closeSponsorModal); 389 } 390 }); 391 392})(window);
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.