cdnhost 2.8.3 → 2.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/link.js +4 -4
- package/package.json +1 -1
- package/ws_cdn.js +1 -1
- package/ws_cdnhtml.js +6 -6
- package/ws_css.css +1 -1
package/link.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
const urls = [...new Set([
|
|
3
3
|
"//seekr.kr","//snapp.im","//manatoki.kr","//199.kr","//799.kr","//99s.kr","//thedeo.uk",
|
|
4
4
|
"//1a.seekr.kr","//1b.seekr.kr","//1c.seekr.kr","//1d.seekr.kr","//1e.seekr.kr","//1f.seekr.kr","//1g.seekr.kr","//1h.seekr.kr","//1i.seekr.kr","//1.seekr.kr","//2.seekr.kr","//3.seekr.kr","//4.seekr.kr","//5.seekr.kr","//6.seekr.kr","//7.seekr.kr","//8.seekr.kr","//9.seekr.kr","//10.seekr.kr","//11.seekr.kr","//12.seekr.kr","//13.seekr.kr","//14.seekr.kr","//15.seekr.kr","//16.seekr.kr","//17.seekr.kr","//18.seekr.kr","//19.seekr.kr","//20.seekr.kr","//21.seekr.kr","//22.seekr.kr","//23.seekr.kr","//24.seekr.kr","//25.seekr.kr","//26.seekr.kr","//27.seekr.kr","//28.seekr.kr","//29.seekr.kr","//30.seekr.kr","//31.seekr.kr","//32.seekr.kr","//33.seekr.kr","//34.seekr.kr","//35.seekr.kr","//39.seekr.kr","//42.seekr.kr","//43.seekr.kr","//48.seekr.kr","//50.seekr.kr","//53.seekr.kr","//56.seekr.kr","//67.seekr.kr","//70.seekr.kr","//129.seekr.kr","//124.seekr.kr","//127.seekr.kr","//128.seekr.kr",
|
|
5
|
-
"//
|
|
6
|
-
"//1.manatoki.kr","//2.manatoki.kr","//3.manatoki.kr","//4.manatoki.kr","//5.manatoki.kr","//6.manatoki.kr","//7.manatoki.kr","//8.manatoki.kr","//9.manatoki.kr","//10.manatoki.kr","//11.manatoki.kr","//12.manatoki.kr","//13.manatoki.kr","//14.manatoki.kr","//15.manatoki.kr","//16.manatoki.kr","//17.manatoki.kr","//18.manatoki.kr","//19.manatoki.kr","//20.manatoki.kr","//19.manatoki.kr","//20.manatoki.kr","//21.manatoki.kr","//22.manatoki.kr","//23.manatoki.kr","//24.manatoki.kr","//25.manatoki.kr","//26.manatoki.kr","//27.manatoki.kr","//28.manatoki.kr","//29.manatoki.kr","//30.manatoki.kr","//31.manatoki.kr","//32.manatoki.kr","//33.manatoki.kr","//34.manatoki.kr","//35.manatoki.kr","//36.manatoki.kr","//37.manatoki.kr","//38.manatoki.kr","//39.manatoki.kr","//40.manatoki.kr","//41.manatoki.kr","//42.manatoki.kr","//43.manatoki.kr","//44.manatoki.kr","//45.manatoki.kr","//46.manatoki.kr","//47.manatoki.kr","//48.manatoki.kr","//49.manatoki.kr","//50.manatoki.kr","//51.manatoki.kr","//52.manatoki.kr","//53.manatoki.kr","//54.manatoki.kr","//55.manatoki.kr","//56.manatoki.kr","//57.manatoki.kr","//58.manatoki.kr","//59.manatoki.kr","//60.manatoki.kr","//61.manatoki.kr","//62.manatoki.kr","//63.manatoki.kr","//64.manatoki.kr","//65.manatoki.kr","//66.manatoki.kr","//67.manatoki.kr","//68.manatoki.kr","//69.manatoki.kr","//70.manatoki.kr","//71.manatoki.kr","//72.manatoki.kr","//73.manatoki.kr","//74.manatoki.kr","//75.manatoki.kr","//76.manatoki.kr","//77.manatoki.kr","//78.manatoki.kr","//79.manatoki.kr",
|
|
5
|
+
"//16.snapp.im","//17.snapp.im","//18.snapp.im","//19.snapp.im","//20.snapp.im","//21.snapp.im","//22.snapp.im","//23.snapp.im","//24.snapp.im","//25.snapp.im",
|
|
6
|
+
"//oooooo.iwinv.net","//minecraft.iwinv.net","//zzzzzz.iwinv.net","//ssssss.iwinv.net","//tttttt.iwinv.net","//aaaaaa.iwinv.net","//1.manatoki.kr","//2.manatoki.kr","//3.manatoki.kr","//4.manatoki.kr","//5.manatoki.kr","//6.manatoki.kr","//7.manatoki.kr","//8.manatoki.kr","//9.manatoki.kr","//10.manatoki.kr","//11.manatoki.kr","//12.manatoki.kr","//13.manatoki.kr","//14.manatoki.kr","//15.manatoki.kr","//16.manatoki.kr","//17.manatoki.kr","//18.manatoki.kr","//19.manatoki.kr","//20.manatoki.kr","//19.manatoki.kr","//20.manatoki.kr","//21.manatoki.kr","//22.manatoki.kr","//23.manatoki.kr","//24.manatoki.kr","//25.manatoki.kr","//26.manatoki.kr","//27.manatoki.kr","//28.manatoki.kr","//29.manatoki.kr","//30.manatoki.kr","//31.manatoki.kr","//32.manatoki.kr","//33.manatoki.kr","//34.manatoki.kr","//35.manatoki.kr","//36.manatoki.kr","//37.manatoki.kr","//38.manatoki.kr","//39.manatoki.kr","//40.manatoki.kr","//41.manatoki.kr","//42.manatoki.kr","//43.manatoki.kr","//44.manatoki.kr","//45.manatoki.kr","//46.manatoki.kr","//47.manatoki.kr","//48.manatoki.kr","//49.manatoki.kr","//50.manatoki.kr","//51.manatoki.kr","//52.manatoki.kr","//53.manatoki.kr","//54.manatoki.kr","//55.manatoki.kr","//56.manatoki.kr","//57.manatoki.kr","//58.manatoki.kr","//59.manatoki.kr","//60.manatoki.kr","//61.manatoki.kr","//62.manatoki.kr","//63.manatoki.kr","//64.manatoki.kr","//65.manatoki.kr","//66.manatoki.kr","//67.manatoki.kr","//68.manatoki.kr","//69.manatoki.kr","//70.manatoki.kr","//71.manatoki.kr","//72.manatoki.kr","//73.manatoki.kr","//74.manatoki.kr","//75.manatoki.kr","//76.manatoki.kr","//77.manatoki.kr","//78.manatoki.kr","//79.manatoki.kr",
|
|
7
7
|
"//1.199.kr","//2.199.kr","//3.199.kr","//4.199.kr","//5.199.kr","//6.199.kr","//7.199.kr","//8.199.kr","//9.199.kr","//google.199.kr","//tistory.thedeo.uk","//blog.thedeo.uk","//news.thedeo.uk","//o.thedeo.uk","//oo.thedeo.uk","//ooo.thedeo.uk","//oooo.thedeo.uk","//ooooo.thedeo.uk","//i.thedeo.uk","//ii.thedeo.uk","//iii.thedeo.uk","//iiii.thedeo.uk","//iiiii.thedeo.uk","//a.thedeo.uk","//b.thedeo.uk","//c.thedeo.uk","//d.thedeo.uk","//e.thedeo.uk","//f.thedeo.uk","//g.thedeo.uk","//h.thedeo.uk","//j.thedeo.uk","//k.thedeo.uk","//l.thedeo.uk","//m.thedeo.uk","//n.thedeo.uk","//p.thedeo.uk","//q.thedeo.uk","//r.thedeo.uk","//s.thedeo.uk","//t.thedeo.uk","//u.thedeo.uk","//v.thedeo.uk","//w.thedeo.uk","//x.thedeo.uk","//y.thedeo.uk","//z.thedeo.uk","//a1.thedeo.uk","//a2.thedeo.uk","//a3.thedeo.uk","//a4.thedeo.uk","//a5.thedeo.uk","//a6.thedeo.uk","//a7.thedeo.uk","//a8.thedeo.uk","//a9.thedeo.uk","//b1.thedeo.uk","//b2.thedeo.uk","//b3.thedeo.uk","//b4.thedeo.uk","//b5.thedeo.uk","//b6.thedeo.uk","//b7.thedeo.uk","//b8.thedeo.uk","//b9.thedeo.uk","//cloud.thedeo.uk",
|
|
8
8
|
"//1.799.kr","//2.799.kr","//3.799.kr","//4.799.kr","//5.799.kr","//6.799.kr","//7.799.kr","//8.799.kr","//9.799.kr","//a.799.kr","//b.799.kr","//c.799.kr","//d.799.kr","//e.799.kr","//f.799.kr","//g.799.kr","//h.799.kr","//i.799.kr","//j.799.kr","//k.799.kr","//l.799.kr","//m.799.kr","//n.799.kr","//o.799.kr","//p.799.kr","//q.799.kr","//r.799.kr","//s.799.kr","//t.799.kr","//u.799.kr","//v.799.kr","//w.799.kr","//x.799.kr","//y.799.kr","//z.799.kr",
|
|
9
9
|
"//xn--vv0bw27a.99s.kr","//1.99s.kr","//2.99s.kr","//3.99s.kr","//4.99s.kr","//5.99s.kr","//6.99s.kr","//7.99s.kr","//8.99s.kr","//9.99s.kr","//a.99s.kr","//b.99s.kr","//c.99s.kr","//d.99s.kr","//e.99s.kr","//f.99s.kr","//g.99s.kr","//h.99s.kr","//i.99s.kr","//j.99s.kr","//k.99s.kr","//l.99s.kr","//m.99s.kr","//n.99s.kr","//o.99s.kr","//p.99s.kr","//q.99s.kr","//r.99s.kr","//s.99s.kr","//t.99s.kr","//u.99s.kr","//v.99s.kr","//w.99s.kr","//x.99s.kr","//y.99s.kr","//z.99s.kr",
|
|
10
10
|
"//9.news1.workers.dev","//8.news1.workers.dev","//7.news1.workers.dev","//6.news1.workers.dev","//5.news1.workers.dev","//4.news1.workers.dev","//3.news1.workers.dev","//2.news1.workers.dev","//1.news1.workers.dev","//9.news2.workers.dev","//8.news2.workers.dev","//7.news2.workers.dev","//6.news2.workers.dev","//5.news2.workers.dev","//4.news2.workers.dev","//3.news2.workers.dev","//2.news2.workers.dev","//1.news2.workers.dev","//8.00000-c22.workers.dev","//7.00000-c22.workers.dev","//6.00000-c22.workers.dev","//5.00000-c22.workers.dev","//4.00000-c22.workers.dev","//3.00000-c22.workers.dev","//2.00000-c22.workers.dev","//1.00000-c22.workers.dev","//0.00000-c22.workers.dev","//9.daum.workers.dev","//8.daum.workers.dev","//7.daum.workers.dev","//6.daum.workers.dev","//5.daum.workers.dev","//4.daum.workers.dev","//3.daum.workers.dev","//2.daum.workers.dev","//1.daum.workers.dev","//9.tistory.workers.dev","//8.tistory.workers.dev","//7.tistory.workers.dev","//6.tistory.workers.dev","//5.tistory.workers.dev","//4.tistory.workers.dev","//3.tistory.workers.dev","//2.tistory.workers.dev","//1.tistory.workers.dev","//9.l1i1i.workers.dev","//8.l1i1i.workers.dev","//7.l1i1i.workers.dev","//6.l1i1i.workers.dev","//5.l1i1i.workers.dev","//4.l1i1i.workers.dev","//3.l1i1i.workers.dev","//2.l1i1i.workers.dev","//1.l1i1i.workers.dev","//9.naver-news.workers.dev","//8.naver-news.workers.dev","//7.naver-news.workers.dev","//6.naver-news.workers.dev","//5.naver-news.workers.dev","//4.naver-news.workers.dev","//3.naver-news.workers.dev","//2.naver-news.workers.dev","//1.naver-news.workers.dev","//6.naver-116.workers.dev","//5.naver-116.workers.dev","//4.naver-116.workers.dev","//3.naver-116.workers.dev","//2.naver-116.workers.dev","//1.naver-116.workers.dev","//6.q7x2.workers.dev","//5.q7x2.workers.dev","//4.q7x2.workers.dev","//3.q7x2.workers.dev","//2.q7x2.workers.dev","//1.q7x2.workers.dev","//8.a9lq.workers.dev","//7.a9lq.workers.dev","//6.a9lq.workers.dev","//5.a9lq.workers.dev","//4.a9lq.workers.dev","//3.a9lq.workers.dev","//2.a9lq.workers.dev","//1.a9lq.workers.dev","//long-bar-4b4e.a9lq.workers.dev","//1.x36q.workers.dev","//3.x36q.workers.dev","//4.x36q.workers.dev","//5.x36q.workers.dev","//6.x36q.workers.dev","//8.x36q.workers.dev","//9.x36q.workers.dev","//1.p1q7.workers.dev","//3.p1q7.workers.dev","//4.p1q7.workers.dev","//5.p1q7.workers.dev","//6.p1q7.workers.dev","//7.p1q7.workers.dev","//8.p1q7.workers.dev","//1.ascklw21.workers.dev","//2.ascklw21.workers.dev","//3.ascklw21.workers.dev","//5.ascklw21.workers.dev","//6.ascklw21.workers.dev","//7.ascklw21.workers.dev","//8.ascklw21.workers.dev","//9.ascklw21.workers.dev","//2.fluke201.workers.dev","//3.fluke201.workers.dev","//4.fluke201.workers.dev","//5.fluke201.workers.dev","//6.fluke201.workers.dev","//7.fluke201.workers.dev","//8.fluke201.workers.dev","//9.fluke201.workers.dev","//1.ffluke103.workers.dev","//2.ffluke103.workers.dev","//3.ffluke103.workers.dev","//4.ffluke103.workers.dev","//5.ffluke103.workers.dev","//6.ffluke103.workers.dev","//7.ffluke103.workers.dev","//8.ffluke103.workers.dev","//9.ffluke103.workers.dev","//1.qpx.workers.dev","//2.qpx.workers.dev","//3.qpx.workers.dev","//4.qpx.workers.dev","//5.qpx.workers.dev","//6.qpx.workers.dev","//7.qpx.workers.dev","//8.qpx.workers.dev","//9.qpx.workers.dev","//1.cxi.workers.dev","//2.cxi.workers.dev","//3.cxi.workers.dev","//4.cxi.workers.dev","//5.cxi.workers.dev","//6.cxi.workers.dev","//7.cxi.workers.dev","//8.cxi.workers.dev","//9.cxi.workers.dev","//1.kxq.workers.dev","//2.kxq.workers.dev","//3.kxq.workers.dev","//4.kxq.workers.dev","//5.kxq.workers.dev","//6.kxq.workers.dev","//7.kxq.workers.dev","//8.kxq.workers.dev","//9.kxq.workers.dev","//1.lesunghwa408.workers.dev","//2.lesunghwa408.workers.dev","//3.lesunghwa408.workers.dev","//4.lesunghwa408.workers.dev","//5.lesunghwa408.workers.dev","//6.lesunghwa408.workers.dev","//7.lesunghwa408.workers.dev","//8.lesunghwa408.workers.dev","//2.silisoft078.workers.dev","//3.silisoft078.workers.dev","//4.silisoft078.workers.dev","//5.silisoft078.workers.dev","//6.silisoft078.workers.dev","//7.silisoft078.workers.dev","//8.silisoft078.workers.dev","//floral-field-6df2.silisoft078.workers.dev","//admin.099.kr",
|
|
11
|
-
"//tonews.logig.im","//allnews.logig.im","//znews.logig.im","//blog.099.kr","//099.kr","//wallpaper.ko-kr.workers.dev","//adsense.ko-kr.workers.dev","//youtube.ko-kr.workers.dev","//hham.ko-kr.workers.dev",
|
|
12
|
-
"//popcat.iwinv.net","//
|
|
11
|
+
"//ranovel.kr","//tonews.logig.im","//allnews.logig.im","//znews.logig.im","//blog.099.kr","//099.kr","//wallpaper.ko-kr.workers.dev","//adsense.ko-kr.workers.dev","//youtube.ko-kr.workers.dev","//hham.ko-kr.workers.dev",
|
|
12
|
+
"//popcat.iwinv.net","//tdgall.iwinv.net","//yakored.iwinv.net","//dctribe.iwinv.net","//hygall.iwinv.net","//skyscanner.iwinv.net","//flightaware.iwinv.net","//fconline.iwinv.net","//flextv.iwinv.net","//eomisae.iwinv.net","//ecount.iwinv.net","//mixamo.iwinv.net","//toptoon.iwinv.net","//slrclub.iwinv.net","//dmitory.iwinv.net","//kmcert.iwinv.net","//lolchess.iwinv.net","//curseforge.iwinv.net","//hsreplay.iwinv.net","//bikini.iwinv.net","//qqqqqq.iwinv.net","//iiiiii.iwinv.net","//llllll.iwinv.net","//xxxxxx.iwinv.net","//eeeeee.iwinv.net","//pdnews.iwinv.net","//valorant.iwinv.net","//tumblr.iwinv.net","//netflix.iwinv.net","//openai.iwinv.net","//palworld.iwinv.net",
|
|
13
13
|
])];
|
|
14
14
|
|
|
15
15
|
document.writeln('<div style="display:flex;overflow-x:scroll;width:100%;">');
|
package/package.json
CHANGED
package/ws_cdn.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
무단 복제, 수정, 배포를 금지합니다.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
document.addEventListener('DOMContentLoaded', function() { if (window.FHLWidgetInitialized) return; window.FHLWidgetInitialized = true; const languages = [ { code: 'ko', name: '한국어' }, { code: 'en', name: 'English' }, { code: 'es', name: 'Español' }, { code: 'hi', name: 'हिन्दी' }, { code: 'ar', name: 'العربية' }, { code: 'de', name: 'Deutsch' }, { code: 'fr', name: 'Français' }, { code: 'pt', name: 'Português' }, { code: 'bn', name: 'বাংলা' }, { code: 'ja', name: '日本語' }, { code: 'ru', name: 'Русский' }, { code: 'zh', name: '简体中文' }, { code: 'th', name: 'ไทย' }, { code: 'vi', name: 'Tiếng Việt' }, { code: 'id', name: 'Indonesia' }, { code: 'tr', name: 'Türkçe' }, { code: 'ur', name: 'اردو' }]; let currentDisplayItems = []; let currentPage = 1; const ITEMS_PER_PAGE = 30; let isLoadingNextPage = false; let searchTimeout = null; const widgetContainer = document.getElementById('fhl-widget-container'); const widgetWrapper = document.getElementById('fhl-widget-wrapper'); const listContainer = document.getElementById('fhl-list-container'); const searchInput = document.getElementById('fhl-search-input'); const searchResultsContainer = document.getElementById('fhl-search-results'); const searchTrigger = document.getElementById('fhl-search-trigger'); const searchClose = document.getElementById('fhl-search-close'); const indicatorTrack = document.getElementById('fhl-indicator-track'); const scrollIndicator = document.getElementById('fhl-scroll-indicator'); const addToHomeBtn = document.getElementById('fhl-add-to-home-btn'); const searchAddBtn = document.getElementById('fhl-search-add-btn'); const toastElement = document.getElementById('fhl-toast-notification'); const languageBtn = document.getElementById('fhl-language-btn'); const languagePanel = document.getElementById('fhl-language-panel'); const fixedQuestionBtn = document.getElementById('fhl-fixed-question'); const fixedChatBtn = document.getElementById('fhl-fixed-chat'); const fixedAdBtn = document.getElementById('fhl-fixed-ad'); const fixedInfoBtn = document.getElementById('fhl-fixed-info'); let toastTimeout = null; let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko'; const API_BASE_URL = 'https://isai.kr'; const shortcutManager = { key: 'fhl-custom-icons-with-expiry', get: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return []; try { const data = JSON.parse(rawData); const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000; if (Date.now() - data.timestamp > thirtyDaysInMs) { localStorage.removeItem(shortcutManager.key); return []; } return Array.isArray(data.shortcuts) ? data.shortcuts.filter(item => item && item.name && item.url) : []; } catch (e) { localStorage.removeItem(shortcutManager.key); return []; } }, set: (shortcuts) => { const dataToStore = { timestamp: Date.now(), shortcuts: shortcuts }; localStorage.setItem(shortcutManager.key, JSON.stringify(dataToStore)); }, syncFromURL: () => { const params = new URLSearchParams(window.location.search); const shortcutsParam = params.get('shortcuts'); if (shortcutsParam) { try { const decodedData = decodeURIComponent(shortcutsParam); JSON.parse(decodedData); localStorage.setItem(shortcutManager.key, decodedData); } catch (e) { console.error("Failed to parse shortcuts from URL:", e)} } }, getAsParam: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return ''; return `shortcuts=${encodeURIComponent(rawData)}`; } }; const updateAllWidgetLinks = () => { const paramString = shortcutManager.getAsParam(); if (!paramString) return; const allLinks = document.querySelectorAll('#fhl-widget-container .fhl-icon-display, #fhl-widget-container .fhl-fixed-item'); allLinks.forEach(link => { if (link.tagName === 'A') { const originalHref = link.getAttribute('href'); if (!originalHref || originalHref.startsWith('#')) return; try { let newUrl = new URL(originalHref); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); link.href = newUrl.toString(); } catch (e) { console.error('Invalid URL for link update:', originalHref)} } }); }; const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible')}, 2500)}; const applyLanguage = (langCode) => { if (typeof translations === 'undefined') { console.error("Translations not loaded! Make sure lang.js is included correctly."); return} const t = translations[langCode] || translations['en']; document.documentElement.lang = langCode; searchTrigger.setAttribute('aria-label', t.openSearch); addToHomeBtn.setAttribute('aria-label', t.addToWidget); searchClose.setAttribute('aria-label', t.closeSearch); languageBtn.setAttribute('aria-label', t.changeLanguage); searchAddBtn.setAttribute('aria-label', 'Copy Widget Code'); fixedQuestionBtn.setAttribute('aria-label', t.question); fixedChatBtn.setAttribute('aria-label', t.characterChat); fixedAdBtn.setAttribute('aria-label', t.ads); fixedInfoBtn.setAttribute('aria-label', t.info); searchInput.placeholder = t.searchPlaceholder; initialRender(); }; const populateLanguagePanel = () => { languagePanel.innerHTML = ''; languages.forEach(lang => { const langItem = document.createElement('a'); langItem.href = '#'; langItem.className = 'fhl-language-item'; langItem.textContent = lang.name; langItem.dataset.lang = lang.code; langItem.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newLang = e.target.dataset.lang; if (newLang !== currentLang) { currentLang = newLang; localStorage.setItem('fhl-widget-lang', currentLang); applyLanguage(currentLang)} languagePanel.classList.remove('visible')}); languagePanel.appendChild(langItem)})}; const getProcessedUrl = (fullUrl) => { try { const urlObj = new URL(fullUrl); return `${urlObj.protocol}//${urlObj.hostname}`} catch (e) { return fullUrl} }; const createIconItem = (item, type = 'default') => { if (!item || !item.name || !item.url) return document.createDocumentFragment(); const link = document.createElement('a'); if (type === 'search') { const paramString = shortcutManager.getAsParam(); let finalUrl = item.url; if (paramString) { try { let newUrl = new URL(item.url); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); finalUrl = newUrl.toString()} catch (e) { console.error('Invalid URL for search result link:', item.url)} } link.href = finalUrl; } else { link.href = item.url} link.className = 'fhl-icon-display'; link.setAttribute('aria-label', item.name); if (type === 'search') { link.addEventListener('mousedown', () => { fetch(`${API_BASE_URL}/update_view_count.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.url }) }).catch(console.error)}); link.target = '_blank'} const iconCircle = document.createElement('div'); iconCircle.className = 'fhl-icon-circle'; if (item.icon) { const icon = document.createElement('i'); icon.className = `ph-bold ${item.icon}`; iconCircle.appendChild(icon)} else if (item.img) { const customImage = document.createElement('img'); customImage.src = item.img; customImage.alt = item.name; customImage.onerror = () => { customImage.style.display = 'none'}; iconCircle.appendChild(customImage)} else { const favicon = document.createElement('img'); favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`; favicon.alt = item.name; favicon.onerror = () => { favicon.style.display = 'none'}; iconCircle.appendChild(favicon)} link.appendChild(iconCircle); if (type === 'search') { const tooltip = document.createElement('div'); tooltip.className = 'fhl-item-tooltip'; tooltip.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(tooltip); link.addEventListener('mouseenter', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.add('visible'); const widgetRect = widgetWrapper.getBoundingClientRect(); const tooltipRect = currentTooltip.getBoundingClientRect(); let offsetX = 0; const padding = 5; if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding} else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding} if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`} }); link.addEventListener('mouseleave', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.remove('visible'); currentTooltip.style.transform = 'translateX(-50%)'}); } else { const nameSpan = document.createElement('span'); nameSpan.className = 'fhl-item-name'; nameSpan.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(nameSpan)} if (type === 'local') { const controls = document.createElement('div'); controls.className = 'fhl-item-controls'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'fhl-control-btn'; deleteBtn.innerHTML = '×'; deleteBtn.title = '삭제'; deleteBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); storedItems = storedItems.filter(stored => stored.url !== item.url); shortcutManager.set(storedItems); initialRender()}); const moveBtn = document.createElement('button'); moveBtn.className = 'fhl-control-btn'; moveBtn.innerHTML = '>'; moveBtn.title = '오른쪽으로 이동'; moveBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); const currentIndex = storedItems.findIndex(stored => stored.url === item.url); if (currentIndex > -1) { const newIndex = (currentIndex + 1) % storedItems.length; const [movedItem] = storedItems.splice(currentIndex, 1); storedItems.splice(newIndex, 0, movedItem); shortcutManager.set(storedItems); initialRender()} }); controls.appendChild(deleteBtn); controls.appendChild(moveBtn); link.appendChild(controls); } return link; }; const appendNextPage = () => { if (isLoadingNextPage) return; isLoadingNextPage = true; const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = currentPage * ITEMS_PER_PAGE; if (startIndex >= currentDisplayItems.length) { isLoadingNextPage = false; return} const itemsToAppend = currentDisplayItems.slice(startIndex, endIndex); itemsToAppend.forEach(item => { searchResultsContainer.appendChild(createIconItem(item, 'search'))}); currentPage++; isLoadingNextPage = false}; const renderInitialResults = (items) => { searchResultsContainer.innerHTML = ''; currentPage = 1; currentDisplayItems = Array.isArray(items) ? items : []; appendNextPage(); }; const performSearch = async (query = '') => { try { const response = await fetch(`${API_BASE_URL}/appapi2.php?query=${encodeURIComponent(query)}`); if (!response.ok) throw new Error('API 응답 오류'); const results = await response.json(); renderInitialResults(results); } catch (error) { console.error('앱 목록 로드 실패:', error); searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>'; } }; const openSearch = () => { widgetContainer.classList.add('search-mode'); widgetWrapper.classList.add('search-mode'); searchInput.focus(); performSearch()}; const initialRender = () => { const a = translations[currentLang] || translations['en']; const b = [ { name: a.search, url: 'https://isai.kr', icon: 'ph-sparkle' }, { name: a.question, url: 'https://isai.kr/#chat', icon: 'ph-question-mark' }, { name: a.blog, url: 'https://blog.099.kr', icon: 'ph-article-medium' }, { name: a.characterChat, url: 'https://lai.oduc.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.7.6/lai48.png' }, { name: a.translate, url: 'https://translato.isai.kr/', icon: 'ph-translate' }, { name: a.tarot, url: 'https://tarot.isai.kr/', icon: 'ph-cards' }, { name: '라노벨', url: 'https://ranovel.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.2.2/__ra.png' }, { name: a.psychology, url: 'https://simpong.oduc.kr/', img: '//cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' }, { name: a.forum, url: 'https://logig.im', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.3.9/_______________logig.png' }, { name: a.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' }, ]; listContainer.innerHTML = '<button class="fhl-icon-circle" style="margin: 0 8px 0 0;border:0" id="fhl-edit-mode-btn"><i class="ph-bold ph-pencil-simple"></i></button>'; const editModeBtn = document.getElementById('fhl-edit-mode-btn'); if (editModeBtn) { if (listContainer.classList.contains('edit-mode')) { editModeBtn.classList.add('active'); } editModeBtn.addEventListener('click', () => { listContainer.classList.toggle('edit-mode'); editModeBtn.classList.toggle('active'); }); } const c = shortcutManager.get(); let d = [...b]; d.splice(2, 0, ...c); const fragment = document.createDocumentFragment(); d.forEach(e => { const f = c.some(g => g.url === e.url); const h = f && !e.icon && !e.img ? 'local' : 'default'; fragment.appendChild(createIconItem(e, h)); }); listContainer.appendChild(fragment); checkScrollability(); updateAllWidgetLinks(); }; const updateScrollIndicator = () => { const scrollLeft = listContainer.scrollLeft; const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth; if (maxScrollLeft <= 0) return; const scrollFraction = scrollLeft / maxScrollLeft; const trackWidth = indicatorTrack.clientWidth; const indicatorWidth = scrollIndicator.clientWidth; const maxIndicatorLeft = trackWidth - indicatorWidth; const indicatorLeft = scrollFraction * maxIndicatorLeft; scrollIndicator.style.transform = `translateY(-50%) translateX(${indicatorLeft}px)`}; const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator()}; let isDragging = false; const handleDragMove = (e) => { if (!isDragging) return; e.preventDefault(); const trackRect = indicatorTrack.getBoundingClientRect(); const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth; let positionRatio = (e.clientX - trackRect.left) / trackRect.width; positionRatio = Math.max(0, Math.min(1, positionRatio)); listContainer.scrollLeft = positionRatio * maxScrollLeft}; const handleDragEnd = () => { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', handleDragMove); document.removeEventListener('mouseup', handleDragEnd)}; indicatorTrack.addEventListener('mousedown', (e) => { isDragging = true; handleDragMove(e); document.addEventListener('mousemove', handleDragMove); document.addEventListener('mouseup', handleDragEnd)}); const closeSearch = () => { widgetContainer.classList.remove('search-mode'); widgetWrapper.classList.remove('search-mode'); searchInput.value = ''; searchResultsContainer.innerHTML = ''}; let hideTimeout = null; widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout)}); widgetContainer.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden')}, 6000)}); document.addEventListener('click', (e) => { if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) { languagePanel.classList.remove('visible')} if (widgetContainer.contains(e.target)) { return} if (widgetContainer.classList.contains('hidden')) { setTimeout(() => { widgetContainer.classList.remove('hidden'); clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden')}, 6000)}, 50)} }); listContainer.addEventListener('scroll', updateScrollIndicator); window.addEventListener('resize', checkScrollability); searchTrigger.addEventListener('click', openSearch); languageBtn.addEventListener('click', (e) => { e.stopPropagation(); languagePanel.classList.toggle('visible')}); searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.toLowerCase().trim(); searchTimeout = setTimeout(() => { performSearch(query); }, 300); }); searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage()} }); searchClose.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch()} }); const addCurrentPageToWidget = async (e) => { e.preventDefault(); const t = translations[currentLang] || translations['en']; const baseUrl = getProcessedUrl(window.location.href); let iconName = document.title || t.currentPage; try { const response = await fetch(`${API_BASE_URL}/get_title.php?url=${encodeURIComponent(baseUrl)}`); if (response.ok) { const data = await response.json(); if (data.title) { iconName = data.title} } } catch (error) { console.error('Failed to fetch title:', error)} const newIcon = { name: iconName, url: baseUrl }; let storedItems = shortcutManager.get(); const isAlreadyAdded = storedItems.some(item => getProcessedUrl(item.url) === getProcessedUrl(newIcon.url)); if (!isAlreadyAdded) { if (storedItems.length >= 10) { storedItems.shift()} storedItems.push(newIcon); shortcutManager.set(storedItems); try { await fetch(`${API_BASE_URL}/register_app.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIcon) })} catch (error) { console.error('DB 등록/갱신 실패:', error)} initialRender(); if (widgetWrapper.classList.contains('search-mode')) { closeSearch()} } else { showToast(t.alreadyAdded); } }; const copyWidgetScript = (e) => { e.preventDefault(); const textToCopy = '<script src="https://unpkg.com/@phosphor-icons/web"><\/script><script src="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_lg.js"><\/script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_css.css"><div id="app"><\/div><script src="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdnhtml.js"><\/script> <script src="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js"><\/script>'; const t = translations[currentLang] || translations['en']; const fallbackCopy = () => { const textArea = document.createElement("textarea"); textArea.value = textToCopy; textArea.style.position = "fixed"; textArea.style.top = 0; textArea.style.left = "-9999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showToast(t.codeCopied)} catch (err) { console.error('Fallback: Oops, unable to copy', err)} document.body.removeChild(textArea)}; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy).then(() => { showToast(t.codeCopied)}).catch(() => { fallbackCopy()})} else { fallbackCopy()} }; addToHomeBtn.addEventListener('click', addCurrentPageToWidget); searchAddBtn.addEventListener('click', copyWidgetScript); shortcutManager.syncFromURL(); shortcutManager.get(); populateLanguagePanel(); applyLanguage(currentLang); });
|
|
7
|
+
document.addEventListener('DOMContentLoaded', function() { if (window.FHLWidgetInitialized) return; window.FHLWidgetInitialized = true; const languages = [ { code: 'ko', name: '한국어' }, { code: 'en', name: 'English' }, { code: 'es', name: 'Español' }, { code: 'hi', name: 'हिन्दी' }, { code: 'ar', name: 'العربية' }, { code: 'de', name: 'Deutsch' }, { code: 'fr', name: 'Français' }, { code: 'pt', name: 'Português' }, { code: 'bn', name: 'বাংলা' }, { code: 'ja', name: '日本語' }, { code: 'ru', name: 'Русский' }, { code: 'zh', name: '简体中文' }, { code: 'th', name: 'ไทย' }, { code: 'vi', name: 'Tiếng Việt' }, { code: 'id', name: 'Indonesia' }, { code: 'tr', name: 'Türkçe' }, { code: 'ur', name: 'اردو' }]; let currentDisplayItems = []; let currentPage = 1; const ITEMS_PER_PAGE = 30; let isLoadingNextPage = false; let searchTimeout = null; const widgetContainer = document.getElementById('fhl-widget-container'); const widgetWrapper = document.getElementById('fhl-widget-wrapper'); const listContainer = document.getElementById('fhl-list-container'); const searchInput = document.getElementById('fhl-search-input'); const searchResultsContainer = document.getElementById('fhl-search-results'); const searchTrigger = document.getElementById('fhl-search-trigger'); const searchClose = document.getElementById('fhl-search-close'); const indicatorTrack = document.getElementById('fhl-indicator-track'); const scrollIndicator = document.getElementById('fhl-scroll-indicator'); const addToHomeBtn = document.getElementById('fhl-add-to-home-btn'); const searchAddBtn = document.getElementById('fhl-search-add-btn'); const toastElement = document.getElementById('fhl-toast-notification'); const languageBtn = document.getElementById('fhl-language-btn'); const languagePanel = document.getElementById('fhl-language-panel'); const fixedQuestionBtn = document.getElementById('fhl-fixed-question'); const fixedChatBtn = document.getElementById('fhl-fixed-chat'); const fixedAdBtn = document.getElementById('fhl-fixed-ad'); const fixedInfoBtn = document.getElementById('fhl-fixed-info'); let toastTimeout = null; let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko'; const API_BASE_URL = 'https://isai.kr'; const shortcutManager = { key: 'fhl-custom-icons-with-expiry', get: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return []; try { const data = JSON.parse(rawData); const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000; if (Date.now() - data.timestamp > thirtyDaysInMs) { localStorage.removeItem(shortcutManager.key); return []; } return Array.isArray(data.shortcuts) ? data.shortcuts.filter(item => item && item.name && item.url) : []; } catch (e) { localStorage.removeItem(shortcutManager.key); return []; } }, set: (shortcuts) => { const dataToStore = { timestamp: Date.now(), shortcuts: shortcuts }; localStorage.setItem(shortcutManager.key, JSON.stringify(dataToStore)); }, syncFromURL: () => { const params = new URLSearchParams(window.location.search); const shortcutsParam = params.get('shortcuts'); if (shortcutsParam) { try { const decodedData = decodeURIComponent(shortcutsParam); JSON.parse(decodedData); localStorage.setItem(shortcutManager.key, decodedData); } catch (e) { console.error("Failed to parse shortcuts from URL:", e)} } }, getAsParam: () => { const rawData = localStorage.getItem(shortcutManager.key); if (!rawData) return ''; return `shortcuts=${encodeURIComponent(rawData)}`; } }; const updateAllWidgetLinks = () => { const paramString = shortcutManager.getAsParam(); if (!paramString) return; const allLinks = document.querySelectorAll('#fhl-widget-container .fhl-icon-display, #fhl-widget-container .fhl-fixed-item'); allLinks.forEach(link => { if (link.tagName === 'A') { const originalHref = link.getAttribute('href'); if (!originalHref || originalHref.startsWith('#')) return; try { let newUrl = new URL(originalHref); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); link.href = newUrl.toString(); link.rel = 'nofollow'; } catch (e) { console.error('Invalid URL for link update:', originalHref)} } }); }; const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible')}, 2500)}; const applyLanguage = (langCode) => { if (typeof translations === 'undefined') { console.error("Translations not loaded! Make sure lang.js is included correctly."); return} const t = translations[langCode] || translations['en']; document.documentElement.lang = langCode; searchTrigger.setAttribute('aria-label', t.openSearch); addToHomeBtn.setAttribute('aria-label', t.addToWidget); searchClose.setAttribute('aria-label', t.closeSearch); languageBtn.setAttribute('aria-label', t.changeLanguage); searchAddBtn.setAttribute('aria-label', 'Copy Widget Code'); fixedQuestionBtn.setAttribute('aria-label', t.question); fixedChatBtn.setAttribute('aria-label', t.characterChat); fixedAdBtn.setAttribute('aria-label', t.ads); fixedInfoBtn.setAttribute('aria-label', t.info); searchInput.placeholder = t.searchPlaceholder; initialRender(); }; const populateLanguagePanel = () => { languagePanel.innerHTML = ''; languages.forEach(lang => { const langItem = document.createElement('a'); langItem.href = '#'; langItem.className = 'fhl-language-item'; langItem.textContent = lang.name; langItem.dataset.lang = lang.code; langItem.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newLang = e.target.dataset.lang; if (newLang !== currentLang) { currentLang = newLang; localStorage.setItem('fhl-widget-lang', currentLang); applyLanguage(currentLang)} languagePanel.classList.remove('visible')}); languagePanel.appendChild(langItem)})}; const getProcessedUrl = (fullUrl) => { try { const urlObj = new URL(fullUrl); return `${urlObj.protocol}//${urlObj.hostname}`} catch (e) { return fullUrl} }; const createIconItem = (item, type = 'default') => { if (!item || !item.name || !item.url) return document.createDocumentFragment(); const link = document.createElement('a'); if (type === 'search') { const paramString = shortcutManager.getAsParam(); let finalUrl = item.url; if (paramString) { try { let newUrl = new URL(item.url); const [key, value] = paramString.split('='); newUrl.searchParams.set(key, value); finalUrl = newUrl.toString()} catch (e) { console.error('Invalid URL for search result link:', item.url)} } link.href = finalUrl; } else { link.href = item.url} link.className = 'fhl-icon-display'; link.rel = 'nofollow'; link.setAttribute('aria-label', item.name); if (type === 'search') { link.addEventListener('mousedown', () => { fetch(`${API_BASE_URL}/update_view_count.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.url }) }).catch(console.error)}); link.target = '_blank'} const iconCircle = document.createElement('div'); iconCircle.className = 'fhl-icon-circle'; if (item.icon) { const icon = document.createElement('i'); icon.className = `ph-bold ${item.icon}`; iconCircle.appendChild(icon)} else if (item.img) { const customImage = document.createElement('img'); customImage.src = item.img; customImage.alt = item.name; customImage.onerror = () => { customImage.style.display = 'none'}; iconCircle.appendChild(customImage)} else { const favicon = document.createElement('img'); favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`; favicon.alt = item.name; favicon.onerror = () => { favicon.style.display = 'none'}; iconCircle.appendChild(favicon)} link.appendChild(iconCircle); if (type === 'search') { const tooltip = document.createElement('div'); tooltip.className = 'fhl-item-tooltip'; tooltip.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(tooltip); link.addEventListener('mouseenter', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.add('visible'); const widgetRect = widgetWrapper.getBoundingClientRect(); const tooltipRect = currentTooltip.getBoundingClientRect(); let offsetX = 0; const padding = 5; if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding} else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding} if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`} }); link.addEventListener('mouseleave', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.remove('visible'); currentTooltip.style.transform = 'translateX(-50%)'}); } else { const nameSpan = document.createElement('span'); nameSpan.className = 'fhl-item-name'; nameSpan.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name; link.appendChild(nameSpan)} if (type === 'local') { const controls = document.createElement('div'); controls.className = 'fhl-item-controls'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'fhl-control-btn'; deleteBtn.innerHTML = '×'; deleteBtn.title = '삭제'; deleteBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); storedItems = storedItems.filter(stored => stored.url !== item.url); shortcutManager.set(storedItems); initialRender()}); const moveBtn = document.createElement('button'); moveBtn.className = 'fhl-control-btn'; moveBtn.innerHTML = '>'; moveBtn.title = '오른쪽으로 이동'; moveBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); const currentIndex = storedItems.findIndex(stored => stored.url === item.url); if (currentIndex > -1) { const newIndex = (currentIndex + 1) % storedItems.length; const [movedItem] = storedItems.splice(currentIndex, 1); storedItems.splice(newIndex, 0, movedItem); shortcutManager.set(storedItems); initialRender()} }); controls.appendChild(deleteBtn); controls.appendChild(moveBtn); link.appendChild(controls); } return link; }; const appendNextPage = () => { if (isLoadingNextPage) return; isLoadingNextPage = true; const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = currentPage * ITEMS_PER_PAGE; if (startIndex >= currentDisplayItems.length) { isLoadingNextPage = false; return} const itemsToAppend = currentDisplayItems.slice(startIndex, endIndex); itemsToAppend.forEach(item => { searchResultsContainer.appendChild(createIconItem(item, 'search'))}); currentPage++; isLoadingNextPage = false}; const renderInitialResults = (items) => { searchResultsContainer.innerHTML = ''; currentPage = 1; currentDisplayItems = Array.isArray(items) ? items : []; appendNextPage(); }; const performSearch = async (query = '') => { try { const response = await fetch(`${API_BASE_URL}/appapi2.php?query=${encodeURIComponent(query)}`); if (!response.ok) throw new Error('API 응답 오류'); const results = await response.json(); renderInitialResults(results); } catch (error) { console.error('앱 목록 로드 실패:', error); searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>'; } }; const openSearch = () => { widgetContainer.classList.add('search-mode'); widgetWrapper.classList.add('search-mode'); searchInput.focus(); performSearch()}; const initialRender = () => { const a = translations[currentLang] || translations['en']; const b = [ { name: a.search, url: 'https://isai.kr', icon: 'ph-sparkle' }, { name: a.question, url: 'https://isai.kr/#chat', icon: 'ph-question-mark' }, { name: a.blog, url: 'https://blog.099.kr', icon: 'ph-article-medium' }, { name: a.characterChat, url: 'https://lai.oduc.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.7.6/lai48.png' }, { name: a.translate, url: 'https://translato.isai.kr/', icon: 'ph-translate' }, { name: a.tarot, url: 'https://tarot.isai.kr/', icon: 'ph-cards' }, { name: '라노벨', url: 'https://ranovel.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.2.2/__ra.png' }, { name: a.psychology, url: 'https://simpong.oduc.kr/', img: '//cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' }, { name: a.forum, url: 'https://logig.im', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.3.9/_______________logig.png' }, { name: a.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' }, ]; listContainer.innerHTML = '<button class="fhl-icon-circle" style="margin: 0 8px 0 0;border:0" id="fhl-edit-mode-btn"><i class="ph-bold ph-pencil-simple"></i></button>'; const editModeBtn = document.getElementById('fhl-edit-mode-btn'); if (editModeBtn) { if (listContainer.classList.contains('edit-mode')) { editModeBtn.classList.add('active'); } editModeBtn.addEventListener('click', () => { listContainer.classList.toggle('edit-mode'); editModeBtn.classList.toggle('active'); }); } const c = shortcutManager.get(); let d = [...b]; d.splice(2, 0, ...c); const fragment = document.createDocumentFragment(); d.forEach(e => { const f = c.some(g => g.url === e.url); const h = f && !e.icon && !e.img ? 'local' : 'default'; fragment.appendChild(createIconItem(e, h)); }); listContainer.appendChild(fragment); checkScrollability(); updateAllWidgetLinks(); }; const updateScrollIndicator = () => { const scrollLeft = listContainer.scrollLeft; const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth; if (maxScrollLeft <= 0) return; const scrollFraction = scrollLeft / maxScrollLeft; const trackWidth = indicatorTrack.clientWidth; const indicatorWidth = scrollIndicator.clientWidth; const maxIndicatorLeft = trackWidth - indicatorWidth; const indicatorLeft = scrollFraction * maxIndicatorLeft; scrollIndicator.style.transform = `translateY(-50%) translateX(${indicatorLeft}px)`}; const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator()}; let isDragging = false; const handleDragMove = (e) => { if (!isDragging) return; e.preventDefault(); const trackRect = indicatorTrack.getBoundingClientRect(); const maxScrollLeft = listContainer.scrollWidth - listContainer.clientWidth; let positionRatio = (e.clientX - trackRect.left) / trackRect.width; positionRatio = Math.max(0, Math.min(1, positionRatio)); listContainer.scrollLeft = positionRatio * maxScrollLeft}; const handleDragEnd = () => { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', handleDragMove); document.removeEventListener('mouseup', handleDragEnd)}; indicatorTrack.addEventListener('mousedown', (e) => { isDragging = true; handleDragMove(e); document.addEventListener('mousemove', handleDragMove); document.addEventListener('mouseup', handleDragEnd)}); const closeSearch = () => { widgetContainer.classList.remove('search-mode'); widgetWrapper.classList.remove('search-mode'); searchInput.value = ''; searchResultsContainer.innerHTML = ''}; let hideTimeout = null; widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout)}); widgetContainer.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden')}, 6000)}); document.addEventListener('click', (e) => { if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) { languagePanel.classList.remove('visible')} if (widgetContainer.contains(e.target)) { return} if (widgetContainer.classList.contains('hidden')) { setTimeout(() => { widgetContainer.classList.remove('hidden'); clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden')}, 6000)}, 50)} }); listContainer.addEventListener('scroll', updateScrollIndicator); window.addEventListener('resize', checkScrollability); searchTrigger.addEventListener('click', openSearch); languageBtn.addEventListener('click', (e) => { e.stopPropagation(); languagePanel.classList.toggle('visible')}); searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.toLowerCase().trim(); searchTimeout = setTimeout(() => { performSearch(query); }, 300); }); searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage()} }); searchClose.addEventListener('click', closeSearch); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch()} }); const addCurrentPageToWidget = async (e) => { e.preventDefault(); const t = translations[currentLang] || translations['en']; const baseUrl = getProcessedUrl(window.location.href); let iconName = document.title || t.currentPage; try { const response = await fetch(`${API_BASE_URL}/get_title.php?url=${encodeURIComponent(baseUrl)}`); if (response.ok) { const data = await response.json(); if (data.title) { iconName = data.title} } } catch (error) { console.error('Failed to fetch title:', error)} const newIcon = { name: iconName, url: baseUrl }; let storedItems = shortcutManager.get(); const isAlreadyAdded = storedItems.some(item => getProcessedUrl(item.url) === getProcessedUrl(newIcon.url)); if (!isAlreadyAdded) { if (storedItems.length >= 10) { storedItems.shift()} storedItems.push(newIcon); shortcutManager.set(storedItems); try { await fetch(`${API_BASE_URL}/register_app.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newIcon) })} catch (error) { console.error('DB 등록/갱신 실패:', error)} initialRender(); if (widgetWrapper.classList.contains('search-mode')) { closeSearch()} } else { showToast(t.alreadyAdded); } }; const copyWidgetScript = (e) => { e.preventDefault(); const textToCopy = '<script src="https://unpkg.com/@phosphor-icons/web"><\/script><script src="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_lg.js"><\/script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_css.css"><div id="app"><\/div><script src="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdnhtml.js"><\/script> <script src="https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js"><\/script>'; const t = translations[currentLang] || translations['en']; const fallbackCopy = () => { const textArea = document.createElement("textarea"); textArea.value = textToCopy; textArea.style.position = "fixed"; textArea.style.top = 0; textArea.style.left = "-9999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showToast(t.codeCopied)} catch (err) { console.error('Fallback: Oops, unable to copy', err)} document.body.removeChild(textArea)}; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy).then(() => { showToast(t.codeCopied)}).catch(() => { fallbackCopy()})} else { fallbackCopy()} }; addToHomeBtn.addEventListener('click', addCurrentPageToWidget); searchAddBtn.addEventListener('click', copyWidgetScript); shortcutManager.syncFromURL(); shortcutManager.get(); populateLanguagePanel(); applyLanguage(currentLang); });
|
package/ws_cdnhtml.js
CHANGED
|
@@ -12,7 +12,7 @@ const widgetHTML = `
|
|
|
12
12
|
<button class="fhl-icon-display" id="fhl-search-trigger"><i class="ph-bold ph-magnifying-glass"></i></button>
|
|
13
13
|
<div class="fhl-list-container" id="fhl-list-container"></div>
|
|
14
14
|
<button class="fhl-icon-display" id="fhl-language-btn"><i class="ph-bold ph-globe"></i></button>
|
|
15
|
-
<a href="#" id="fhl-add-to-home-btn" class="fhl-icon-display" rel="nofollow
|
|
15
|
+
<a href="#" id="fhl-add-to-home-btn" class="fhl-icon-display" rel="nofollow"><i class="ph-bold ph-download-simple"></i></a>
|
|
16
16
|
</div>
|
|
17
17
|
<div class="fhl-search-view" id="fhl-search-view">
|
|
18
18
|
<div class="fhl-search-input-wrapper">
|
|
@@ -22,11 +22,11 @@ const widgetHTML = `
|
|
|
22
22
|
<div class="fhl-search-content">
|
|
23
23
|
<div class="fhl-search-results" id="fhl-search-results"></div>
|
|
24
24
|
<div class="fhl-fixed-panel">
|
|
25
|
-
<a href="#" id="fhl-search-add-btn" class="fhl-fixed-item"><i class="ph-bold ph-code"></i></a>
|
|
26
|
-
<a href="https://isai.kr/#chat" id="fhl-fixed-question" class="fhl-fixed-item"><i class="ph-bold ph-question"></i></a>
|
|
27
|
-
<a href="https://
|
|
28
|
-
<a href="https://gig.snapp.im/" id="fhl-fixed-ad" class="fhl-fixed-item"><i class="ph-bold ph-currency-circle-dollar"></i></a>
|
|
29
|
-
<a href="https://webstore.isai.kr/" id="fhl-fixed-info" class="fhl-fixed-item"><i class="ph-bold ph-info"></i></a>
|
|
25
|
+
<a href="#" id="fhl-search-add-btn" rel="nofollow" class="fhl-fixed-item"><i class="ph-bold ph-code"></i></a>
|
|
26
|
+
<a href="https://isai.kr/#chat" rel="nofollow" id="fhl-fixed-question" class="fhl-fixed-item"><i class="ph-bold ph-question"></i></a>
|
|
27
|
+
<a href="https://zoai.oduc.kr/ko/character/select" rel="nofollow" id="fhl-fixed-chat" class="fhl-fixed-item"><i class="ph-bold ph-chats-circle"></i></a>
|
|
28
|
+
<a href="https://gig.snapp.im/" id="fhl-fixed-ad" rel="nofollow" class="fhl-fixed-item"><i class="ph-bold ph-currency-circle-dollar"></i></a>
|
|
29
|
+
<a href="https://webstore.isai.kr/" id="fhl-fixed-info" rel="nofollow" class="fhl-fixed-item"><i class="ph-bold ph-info"></i></a>
|
|
30
30
|
</div>
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
package/ws_css.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
#fhl-widget-container i {color:#000;font-size:22px} .fhl-widget-wrapper *, .fhl-widget-wrapper *::before, .fhl-widget-wrapper *::after { box-sizing: border-box} #fhl-widget-container { position: fixed; z-index: 1000; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(0); display: flex; flex-direction: column; align-items: center; width: calc(100% - 30px); max-width: 460px; pointer-events: auto; transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #fhl-widget-container.hidden { opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none} #fhl-widget-container .fhl-indicator-track { position: relative; width: 100px; height: 14px; margin-bottom: 8px; cursor: pointer; background-color: transparent; transition: opacity 0.3s ease; opacity: 0; pointer-events: auto} #fhl-widget-container.scrollable .fhl-indicator-track { opacity: 1} #fhl-widget-container.search-mode .fhl-indicator-track { display: none} #fhl-widget-container .fhl-scroll-indicator { position: absolute; top: 50%; left: 0; width: 12px; height: 12px; background-color: #555; border-radius: 50%; transform: translateY(-50%); transition: transform 0.1s linear; pointer-events: none} #fhl-widget-container .fhl-widget-wrapper { position: relative; display: flex; align-items: center; width: 100%; max-width: 460px; height: 52px; padding: 7px 10px; border-radius: 26px; background-color: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); transition: all 0.3s ease-in-out; pointer-events: auto; overflow: visible} #fhl-widget-container .fhl-icon-display { position: relative; flex-shrink: 0; display: flex; justify-content: center; align-items: center; width: 38px; height: 38px; color: #333; background-color: rgba(0, 0, 0, 0.05); border: none; border-radius: 50%; text-decoration: none; cursor: pointer; padding: 0; transition: background-color 0.2s ease} #fhl-widget-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.1)} #fhl-widget-container .fhl-icon-display i { font-size: 22px} #fhl-widget-container .fhl-list-container .fhl-icon-display { display: inline-flex; width: auto; padding: 0 8px 0 0; border-radius: 20px; background-color: transparent} #fhl-widget-container .fhl-list-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.08)} #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-icon-circle { background-color: transparent} #fhl-widget-container .fhl-item-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; max-width: 0; opacity: 0; transition: max-width 0.3s ease-in-out, opacity 0.2s ease-in-out 0.05s} #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-item-name { max-width: 150px; opacity: 1} #fhl-widget-container .fhl-icon-circle { flex-shrink: 0; display: flex; justify-content: center; align-items: center; width: 38px; height: 38px; background-color: rgba(0, 0, 0, 0.05); border-radius: 50%; transition: background-color 0.2s ease; overflow: hidden} #fhl-widget-container .fhl-icon-circle img { width: 24px; height: 24px; object-fit: cover; border-radius: 50%} #fhl-widget-container .fhl-icon-bar { display: flex; align-items: center; gap: 8px; width: 100%; transition: opacity 0.3s ease} #fhl-widget-container .fhl-list-container { padding:0 8px; flex-grow: 1; display: flex; align-items: center; gap: 6px; overflow-x: auto; -ms-overflow-style: none; scrollbar-width: none; scroll-snap-type: x mandatory; mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); -webkit-mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); padding-bottom: 30px; margin-bottom: -30px; overflow-y: visible} #fhl-widget-container .fhl-list-container::-webkit-scrollbar { display: none} #fhl-widget-container .fhl-list-container > .fhl-icon-display { scroll-snap-align: center} #fhl-widget-container .fhl-widget-wrapper.search-mode { height: 280px; align-items: flex-start; border-radius: 20px} #fhl-widget-container .fhl-widget-wrapper.search-mode .fhl-icon-bar { opacity: 0; pointer-events: none} #fhl-widget-container .fhl-search-view { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; padding: 10px; opacity: 0; visibility: hidden; transition: opacity 0.3s 0.1s ease} #fhl-widget-container .fhl-widget-wrapper.search-mode .fhl-search-view { opacity: 1; visibility: visible} #fhl-widget-container .fhl-search-input-wrapper { display: flex; align-items: center; flex-shrink: 0; margin-bottom: 10px} #fhl-widget-container .fhl-search-input { flex-grow: 1; height: 38px; border: none; background: transparent; font-size: 16px; color: #111; padding: 0 10px; outline: none; border-bottom: 2px solid #ccc; transition: border-color 0.2s ease} #fhl-widget-container .fhl-search-input:focus { border-bottom-color: #007bff} #fhl-widget-container .fhl-search-content { display: flex; flex-grow: 1; overflow: hidden} #fhl-widget-container .fhl-search-results { flex-grow: 1; width: 0; overflow-y: auto; display: flex; flex-wrap: wrap; justify-content: flex-start; align-content: flex-start; gap: 12px; padding: 15px; -ms-overflow-style: none; scrollbar-width: none; padding-bottom: 40px} #fhl-widget-container .fhl-search-results::-webkit-scrollbar { display: none} #fhl-widget-container .fhl-search-results .fhl-icon-display { background-color: transparent} #fhl-widget-container .fhl-search-results .fhl-icon-display:hover .fhl-icon-circle { background-color: rgba(0, 0, 0, 0.1)} #fhl-widget-container .fhl-item-tooltip { position: absolute; bottom: -28px; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 3px 6px; border-radius: 5px; font-size: 11px !important; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.2s ease-in-out, bottom 0.2s ease-in-out; z-index: 10} #fhl-widget-container .fhl-item-tooltip.visible { opacity: 1; visibility: visible; bottom: -32px} #fhl-widget-container .fhl-fixed-panel { flex-shrink: 0; width: 70px; height: 100%; padding: 0; border-left: 1px solid rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-around; align-items: center} #fhl-widget-container .fhl-fixed-item { display: flex; align-items: center; justify-content: center; width: 42px !important; height: 42px; border-radius: 10px; text-decoration: none; color: #444; cursor: pointer; transition: background-color 0.2s ease} #fhl-widget-container .fhl-fixed-item:hover { background-color: rgba(0, 0, 0, 0.08)} #fhl-widget-container .fhl-fixed-item i { font-size: 24px !important} #fhl-widget-container .fhl-item-controls { position: absolute; bottom: -20px; left: 20px; transform: translateX(-50%); display: flex; gap: 4px; background-color: rgba(0,0,0,0.7); border-radius: 10px; padding: 2px 4px; opacity: 0; visibility: hidden; transition: all 0.2s ease; pointer-events: none; z-index: 20} #fhl-widget-container .fhl-list-container.edit-mode .fhl-item-controls { opacity: 1; visibility: visible; pointer-events: auto; bottom: -13px} #fhl-widget-container .fhl-control-btn { background: none; border: none; color: white; cursor: pointer; font-size: 16px; line-height: 1; padding: 2px; font-family: monospace} #fhl-widget-container .fhl-control-btn:hover { color: #007bff} #fhl-widget-container #fhl-edit-mode-btn.active { background-color: rgba(0, 123, 255, 0.2); color: #007bff} .fhl-toast { position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%) translateY(10px); background-color: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px 16px; border-radius: 18px; font-size: 14px; z-index: 1001; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s} .fhl-toast.visible { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0)} #fhl-widget-container .fhl-language-panel { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 15px; background-color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); padding: 8px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease; transform-origin: bottom center} #fhl-widget-container .fhl-language-panel.visible { opacity: 1; visibility: visible; pointer-events: auto} #fhl-widget-container .fhl-language-item { display: block; padding: 6px 12px; font-size: 14px; color: #333; text-decoration: none; border-radius: 8px; transition: background-color 0.2s ease; cursor: pointer; text-align: center} #fhl-widget-container .fhl-language-item:hover { background-color: rgba(0,0,0,0.1)}
|
|
1
|
+
#fhl-widget-container i {color:#000;font-size:22px !important} .fhl-widget-wrapper *, .fhl-widget-wrapper *::before, .fhl-widget-wrapper *::after { box-sizing: border-box} #fhl-widget-container { position: fixed; z-index: 1000; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(0); display: flex; flex-direction: column; align-items: center; width: calc(100% - 30px); max-width: 460px; pointer-events: auto; transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #fhl-widget-container.hidden { opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none} #fhl-widget-container .fhl-indicator-track { position: relative; width: 100px; height: 14px; margin-bottom: 8px; cursor: pointer; background-color: transparent; transition: opacity 0.3s ease; opacity: 0; pointer-events: auto} #fhl-widget-container.scrollable .fhl-indicator-track { opacity: 1} #fhl-widget-container.search-mode .fhl-indicator-track { display: none} #fhl-widget-container .fhl-scroll-indicator { position: absolute; top: 50%; left: 0; width: 12px; height: 12px; background-color: #555; border-radius: 50%; transform: translateY(-50%); transition: transform 0.1s linear; pointer-events: none} #fhl-widget-container .fhl-widget-wrapper { position: relative; display: flex; align-items: center; width: 100%; max-width: 460px; height: 52px; padding: 7px 10px; border-radius: 26px; background-color: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); transition: all 0.3s ease-in-out; pointer-events: auto; overflow: visible} #fhl-widget-container .fhl-icon-display { position: relative; flex-shrink: 0; display: flex; justify-content: center; align-items: center; width: 38px; height: 38px; color: #333; background-color: rgba(0, 0, 0, 0.05); border: none; border-radius: 50%; text-decoration: none; cursor: pointer; padding: 0; transition: background-color 0.2s ease} #fhl-widget-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.1)} #fhl-widget-container .fhl-icon-display i { font-size: 22px} #fhl-widget-container .fhl-list-container .fhl-icon-display { display: inline-flex; width: auto; padding: 0 8px 0 0; border-radius: 20px; background-color: transparent} #fhl-widget-container .fhl-list-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.08)} #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-icon-circle { background-color: transparent} #fhl-widget-container .fhl-item-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; max-width: 0; opacity: 0; transition: max-width 0.3s ease-in-out, opacity 0.2s ease-in-out 0.05s} #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-item-name { max-width: 150px; opacity: 1} #fhl-widget-container .fhl-icon-circle { flex-shrink: 0; display: flex; justify-content: center; align-items: center; width: 38px; height: 38px; background-color: rgba(0, 0, 0, 0.05); border-radius: 50%; transition: background-color 0.2s ease; overflow: hidden} #fhl-widget-container .fhl-icon-circle img { width: 24px; height: 24px; object-fit: cover; border-radius: 50%} #fhl-widget-container .fhl-icon-bar { display: flex; align-items: center; gap: 8px; width: 100%; transition: opacity 0.3s ease} #fhl-widget-container .fhl-list-container { padding:0 8px; flex-grow: 1; display: flex; align-items: center; gap: 6px; overflow-x: auto; -ms-overflow-style: none; scrollbar-width: none; scroll-snap-type: x mandatory; mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); -webkit-mask-image: linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent); padding-bottom: 30px; margin-bottom: -30px; overflow-y: visible} #fhl-widget-container .fhl-list-container::-webkit-scrollbar { display: none} #fhl-widget-container .fhl-list-container > .fhl-icon-display { scroll-snap-align: center} #fhl-widget-container .fhl-widget-wrapper.search-mode { height: 280px; align-items: flex-start; border-radius: 20px} #fhl-widget-container .fhl-widget-wrapper.search-mode .fhl-icon-bar { opacity: 0; pointer-events: none} #fhl-widget-container .fhl-search-view { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; padding: 10px; opacity: 0; visibility: hidden; transition: opacity 0.3s 0.1s ease} #fhl-widget-container .fhl-widget-wrapper.search-mode .fhl-search-view { opacity: 1; visibility: visible} #fhl-widget-container .fhl-search-input-wrapper { display: flex; align-items: center; flex-shrink: 0; margin-bottom: 10px} #fhl-widget-container .fhl-search-input { flex-grow: 1; height: 38px; border: none; background: transparent; font-size: 16px; color: #111; padding: 0 10px; outline: none; border-bottom: 2px solid #ccc; transition: border-color 0.2s ease} #fhl-widget-container .fhl-search-input:focus { border-bottom-color: #007bff} #fhl-widget-container .fhl-search-content { display: flex; flex-grow: 1; overflow: hidden} #fhl-widget-container .fhl-search-results { flex-grow: 1; width: 0; overflow-y: auto; display: flex; flex-wrap: wrap; justify-content: flex-start; align-content: flex-start; gap: 12px; padding: 15px; -ms-overflow-style: none; scrollbar-width: none; padding-bottom: 40px} #fhl-widget-container .fhl-search-results::-webkit-scrollbar { display: none} #fhl-widget-container .fhl-search-results .fhl-icon-display { background-color: transparent} #fhl-widget-container .fhl-search-results .fhl-icon-display:hover .fhl-icon-circle { background-color: rgba(0, 0, 0, 0.1)} #fhl-widget-container .fhl-item-tooltip { position: absolute; bottom: -28px; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 3px 6px; border-radius: 5px; font-size: 11px !important; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.2s ease-in-out, bottom 0.2s ease-in-out; z-index: 10} #fhl-widget-container .fhl-item-tooltip.visible { opacity: 1; visibility: visible; bottom: -32px} #fhl-widget-container .fhl-fixed-panel { flex-shrink: 0; width: 70px; height: 100%; padding: 0; border-left: 1px solid rgba(0,0,0,0.1); display: flex; flex-direction: column; justify-content: space-around; align-items: center} #fhl-widget-container .fhl-fixed-item { display: flex; align-items: center; justify-content: center; width: 42px !important; height: 42px; border-radius: 10px; text-decoration: none; color: #444; cursor: pointer; transition: background-color 0.2s ease} #fhl-widget-container .fhl-fixed-item:hover { background-color: rgba(0, 0, 0, 0.08)} #fhl-widget-container .fhl-fixed-item i { font-size: 24px !important} #fhl-widget-container .fhl-item-controls { position: absolute; bottom: -20px; left: 20px; transform: translateX(-50%); display: flex; gap: 4px; background-color: rgba(0,0,0,0.7); border-radius: 10px; padding: 2px 4px; opacity: 0; visibility: hidden; transition: all 0.2s ease; pointer-events: none; z-index: 20} #fhl-widget-container .fhl-list-container.edit-mode .fhl-item-controls { opacity: 1; visibility: visible; pointer-events: auto; bottom: -13px} #fhl-widget-container .fhl-control-btn { background: none; border: none; color: white; cursor: pointer; font-size: 16px; line-height: 1; padding: 2px; font-family: monospace} #fhl-widget-container .fhl-control-btn:hover { color: #007bff} #fhl-widget-container #fhl-edit-mode-btn.active { background-color: rgba(0, 123, 255, 0.2); color: #007bff} .fhl-toast { position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%) translateY(10px); background-color: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px 16px; border-radius: 18px; font-size: 14px; z-index: 1001; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s} .fhl-toast.visible { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0)} #fhl-widget-container .fhl-language-panel { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 15px; background-color: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); padding: 8px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease; transform-origin: bottom center} #fhl-widget-container .fhl-language-panel.visible { opacity: 1; visibility: visible; pointer-events: auto} #fhl-widget-container .fhl-language-item { display: block; padding: 6px 12px; font-size: 14px; color: #333; text-decoration: none; border-radius: 8px; transition: background-color 0.2s ease; cursor: pointer; text-align: center} #fhl-widget-container .fhl-language-item:hover { background-color: rgba(0,0,0,0.1)}
|