1// AXêµì¡íë«í¼ ê³µíµ ì í¸ë¦¬í° 2 3const Utils = { 4 // í ì¤í¸ ì림 5 showToast(message, type = 'info', duration = 3000) { 6 let container = document.getElementById('toast-container'); 7 if (!container) { 8 container = document.createElement('div'); 9 container.id = 'toast-container'; 10 container.style.cssText = 'position:fixed;top:80px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;'; 11 document.body.appendChild(container); 12 } 13 const toast = document.createElement('div'); 14 const icons = { success: 'â ', error: 'â', info: 'â¹ï¸', warning: 'â ï¸' }; 15 const colors = { success: '#10B981', error: '#EF4444', info: '#3B82F6', warning: '#F59E0B' }; 16 toast.style.cssText = ` 17 padding:12px 20px;border-radius:8px;background:white; 18 box-shadow:0 4px 16px rgba(0,0,0,0.15);border-left:4px solid ${colors[type]}; 19 font-family:'Noto Sans KR',sans-serif;font-size:14px; 20 animation:slideInToast 0.3s ease;max-width:300px; 21 `; 22 toast.textContent = `${icons[type]} ${message}`; 23 container.appendChild(toast); 24 setTimeout(() => { toast.style.opacity='0'; toast.style.transition='opacity 0.3s'; setTimeout(() => toast.remove(), 300); }, duration); 25 }, 26 27 // ë¡ë© íì 28 showLoading(container, text = 'ë¡ë© ì¤...') { 29 const el = typeof container === 'string' ? document.querySelector(container) : container; 30 if (el) el.innerHTML = `<div style="text-align:center;padding:48px;color:#6B7280;"><div style="width:32px;height:32px;border:3px solid #E5E7EB;border-top-color:#FF6B35;border-radius:50%;animation:spin 0.8s linear infinite;margin:0 auto 16px;"></div><p>${text}</p></div>`; 31 }, 32 33 hideLoading(container) { 34 const el = typeof container === 'string' ? document.querySelector(container) : container; 35 if (el) el.innerHTML = ''; 36 }, 37 38 // ë ì§ í¬ë§· 39 formatDate(date, format = 'YYYY-MM-DD') { 40 const d = new Date(date); 41 const pad = n => String(n).padStart(2, '0'); 42 return format 43 .replace('YYYY', d.getFullYear()) 44 .replace('MM', pad(d.getMonth()+1)) 45 .replace('DD', pad(d.getDate())) 46 .replace('HH', pad(d.getHours())) 47 .replace('mm', pad(d.getMinutes())); 48 }, 49 50 // ìë ìê° 51 timeAgo(date) { 52 const diff = (Date.now() - new Date(date)) / 1000; 53 if (diff < 60) return 'ë°©ê¸ ì '; 54 if (diff < 3600) return `${Math.floor(diff/60)}ë¶ ì `; 55 if (diff < 86400) return `${Math.floor(diff/3600)}ìê° ì `; 56 return `${Math.floor(diff/86400)}ì¼ ì `; 57 }, 58 59 // ì«ì í¬ë§· 60 formatNumber(num) { 61 if (num >= 1000) return (num/1000).toFixed(1) + 'k'; 62 return String(num); 63 }, 64 65 // í ì¤í¸ ì르기 66 truncate(text, maxLen = 100) { 67 return text.length > maxLen ? text.slice(0, maxLen) + '...' : text; 68 }, 69 70 // ëë°ì´ì¤ 71 debounce(fn, wait = 300) { 72 let t; 73 return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); }; 74 }, 75 76 // URL íë¼ë¯¸í° 77 getParam(name) { 78 return new URLSearchParams(window.location.search).get(name); 79 }, 80 81 // ë¡ì»¬ì¤í ë¦¬ì§ ëí¼ 82 storage: { 83 get(key) { try { return JSON.parse(localStorage.getItem('ax_' + key)); } catch { return null; } }, 84 set(key, val) { try { localStorage.setItem('ax_' + key, JSON.stringify(val)); } catch {} }, 85 remove(key) { localStorage.removeItem('ax_' + key); } 86 }, 87 88 // ì´ëì ìë°í 89 getInitials(name) { 90 return name ? name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) : '?'; 91 }, 92 93 // ì ëë©ì´ì ì¹´ì´í° 94 animateCounter(el, target, duration = 2000) { 95 const start = 0; 96 const step = (timestamp) => { 97 const progress = Math.min((timestamp - startTime) / duration, 1); 98 el.textContent = Math.floor(progress * target).toLocaleString(); 99 if (progress < 1) requestAnimationFrame(step); 100 }; 101 let startTime; 102 requestAnimationFrame(ts => { startTime = ts; step(ts); }); 103 }, 104 105 // ì¤í¬ë¡¤ í 106 scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); }, 107 108 // ëª¨ë¬ ì ì´ 109 openModal(id) { 110 const m = document.getElementById(id); 111 if (m) { m.classList.add('active'); document.body.style.overflow = 'hidden'; } 112 }, 113 closeModal(id) { 114 const m = document.getElementById(id); 115 if (m) { m.classList.remove('active'); document.body.style.overflow = ''; } 116 } 117}; 118 119// ì ì CSS ì ëë©ì´ì 120const style = document.createElement('style'); 121style.textContent = ` 122 @keyframes slideInToast { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } 123 @keyframes spin { to { transform: rotate(360deg); } } 124 @keyframes fadeIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } 125`;
126document.head.appendChild(style);
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.