cdnhost 2.6.6 → 2.6.8
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/package.json +1 -1
- package/ws_cdn.js +1 -325
- package/ws_cdnhtml.js +3 -4
- package/ws_css.css +1 -51
package/package.json
CHANGED
package/ws_cdn.js
CHANGED
|
@@ -1,325 +1 @@
|
|
|
1
|
-
|
|
2
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
3
|
-
if (window.FHLWidgetInitialized) return;
|
|
4
|
-
window.FHLWidgetInitialized = true;
|
|
5
|
-
|
|
6
|
-
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: 'اردو' }];
|
|
7
|
-
let fullApiItemsData = null;
|
|
8
|
-
let currentDisplayItems = [];
|
|
9
|
-
let currentPage = 1;
|
|
10
|
-
const ITEMS_PER_PAGE = 30;
|
|
11
|
-
let isLoadingNextPage = false;
|
|
12
|
-
|
|
13
|
-
const widgetContainer = document.getElementById('fhl-widget-container');
|
|
14
|
-
const widgetWrapper = document.getElementById('fhl-widget-wrapper');
|
|
15
|
-
const listContainer = document.getElementById('fhl-list-container');
|
|
16
|
-
const searchInput = document.getElementById('fhl-search-input');
|
|
17
|
-
const searchResultsContainer = document.getElementById('fhl-search-results');
|
|
18
|
-
const searchTrigger = document.getElementById('fhl-search-trigger');
|
|
19
|
-
const searchClose = document.getElementById('fhl-search-close');
|
|
20
|
-
const indicatorTrack = document.getElementById('fhl-indicator-track');
|
|
21
|
-
const scrollIndicator = document.getElementById('fhl-scroll-indicator');
|
|
22
|
-
const addToHomeBtn = document.getElementById('fhl-add-to-home-btn');
|
|
23
|
-
const searchAddBtn = document.getElementById('fhl-search-add-btn');
|
|
24
|
-
const toastElement = document.getElementById('fhl-toast-notification');
|
|
25
|
-
const languageBtn = document.getElementById('fhl-language-btn');
|
|
26
|
-
const languagePanel = document.getElementById('fhl-language-panel');
|
|
27
|
-
|
|
28
|
-
const fixedImageBtn = document.getElementById('fhl-fixed-image');
|
|
29
|
-
const fixedChatBtn = document.getElementById('fhl-fixed-chat');
|
|
30
|
-
const fixedAdBtn = document.getElementById('fhl-fixed-ad');
|
|
31
|
-
|
|
32
|
-
let toastTimeout = null;
|
|
33
|
-
let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko';
|
|
34
|
-
|
|
35
|
-
const shortcutManager = {
|
|
36
|
-
key: 'fhl-custom-icons-with-expiry',
|
|
37
|
-
get: () => {
|
|
38
|
-
const rawData = localStorage.getItem(shortcutManager.key);
|
|
39
|
-
if (!rawData) return [];
|
|
40
|
-
try {
|
|
41
|
-
const data = JSON.parse(rawData);
|
|
42
|
-
const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000;
|
|
43
|
-
if (Date.now() - data.timestamp > thirtyDaysInMs) {
|
|
44
|
-
localStorage.removeItem(shortcutManager.key);
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
return data.shortcuts || [];
|
|
48
|
-
} catch (e) {
|
|
49
|
-
localStorage.removeItem(shortcutManager.key);
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
set: (shortcuts) => {
|
|
54
|
-
const dataToStore = { timestamp: Date.now(), shortcuts: shortcuts };
|
|
55
|
-
localStorage.setItem(shortcutManager.key, JSON.stringify(dataToStore));
|
|
56
|
-
},
|
|
57
|
-
syncFromURL: () => {
|
|
58
|
-
const params = new URLSearchParams(window.location.search);
|
|
59
|
-
const shortcutsParam = params.get('shortcuts');
|
|
60
|
-
if (shortcutsParam) {
|
|
61
|
-
try {
|
|
62
|
-
const decodedData = atob(shortcutsParam);
|
|
63
|
-
JSON.parse(decodedData);
|
|
64
|
-
localStorage.setItem(shortcutManager.key, decodedData);
|
|
65
|
-
} catch (e) {
|
|
66
|
-
console.error("Failed to parse shortcuts from URL:", e);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
getAsParam: () => {
|
|
71
|
-
const rawData = localStorage.getItem(shortcutManager.key);
|
|
72
|
-
if (!rawData) return '';
|
|
73
|
-
return `shortcuts=${btoa(rawData)}`;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const updateAllWidgetLinks = () => {
|
|
78
|
-
const paramString = shortcutManager.getAsParam();
|
|
79
|
-
if (!paramString) return;
|
|
80
|
-
const allLinks = document.querySelectorAll('.fhl-icon-display, .fhl-fixed-item');
|
|
81
|
-
allLinks.forEach(link => {
|
|
82
|
-
if (link.tagName === 'A') {
|
|
83
|
-
const originalHref = link.getAttribute('href');
|
|
84
|
-
if (!originalHref || originalHref.startsWith('#')) return;
|
|
85
|
-
try {
|
|
86
|
-
let newUrl = new URL(originalHref);
|
|
87
|
-
const [key, value] = paramString.split('=');
|
|
88
|
-
newUrl.searchParams.set(key, value);
|
|
89
|
-
link.href = newUrl.toString();
|
|
90
|
-
} catch (e) { console.error('Invalid URL for link update:', originalHref); }
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible'); }, 2500); };
|
|
96
|
-
|
|
97
|
-
const applyLanguage = (langCode) => {
|
|
98
|
-
if (typeof translations === 'undefined') {
|
|
99
|
-
console.error("Translations not loaded! Make sure lang.js is included correctly.");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const t = translations[langCode] || translations['en'];
|
|
103
|
-
|
|
104
|
-
document.documentElement.lang = langCode;
|
|
105
|
-
|
|
106
|
-
searchTrigger.setAttribute('aria-label', t.openSearch);
|
|
107
|
-
addToHomeBtn.setAttribute('aria-label', t.addToWidget);
|
|
108
|
-
searchClose.setAttribute('aria-label', t.closeSearch);
|
|
109
|
-
languageBtn.setAttribute('aria-label', t.changeLanguage);
|
|
110
|
-
searchAddBtn.setAttribute('aria-label', 'Copy Widget Code');
|
|
111
|
-
fixedImageBtn.setAttribute('aria-label', t.images);
|
|
112
|
-
fixedChatBtn.setAttribute('aria-label', t.characterChat);
|
|
113
|
-
fixedAdBtn.setAttribute('aria-label', t.ads);
|
|
114
|
-
|
|
115
|
-
searchInput.placeholder = t.searchPlaceholder;
|
|
116
|
-
|
|
117
|
-
initialRender();
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const populateLanguagePanel = () => {
|
|
121
|
-
languagePanel.innerHTML = '';
|
|
122
|
-
languages.forEach(lang => {
|
|
123
|
-
const langItem = document.createElement('a');
|
|
124
|
-
langItem.href = '#';
|
|
125
|
-
langItem.className = 'fhl-language-item';
|
|
126
|
-
langItem.textContent = lang.name;
|
|
127
|
-
langItem.dataset.lang = lang.code;
|
|
128
|
-
langItem.addEventListener('click', (e) => {
|
|
129
|
-
e.preventDefault();
|
|
130
|
-
e.stopPropagation();
|
|
131
|
-
const newLang = e.target.dataset.lang;
|
|
132
|
-
if (newLang !== currentLang) {
|
|
133
|
-
currentLang = newLang;
|
|
134
|
-
localStorage.setItem('fhl-widget-lang', currentLang);
|
|
135
|
-
applyLanguage(currentLang);
|
|
136
|
-
}
|
|
137
|
-
languagePanel.classList.remove('visible');
|
|
138
|
-
});
|
|
139
|
-
languagePanel.appendChild(langItem);
|
|
140
|
-
});
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const getProcessedUrl = (fullUrl) => {
|
|
144
|
-
try {
|
|
145
|
-
const urlObj = new URL(fullUrl);
|
|
146
|
-
return `${urlObj.protocol}//${urlObj.hostname}`;
|
|
147
|
-
} catch (e) {
|
|
148
|
-
return fullUrl;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const createIconItem = (item, type = 'default') => {
|
|
157
|
-
const link = document.createElement('a');
|
|
158
|
-
|
|
159
|
-
if (type === 'search') {
|
|
160
|
-
const paramString = shortcutManager.getAsParam();
|
|
161
|
-
let finalUrl = item.url;
|
|
162
|
-
if (paramString) {
|
|
163
|
-
try {
|
|
164
|
-
let newUrl = new URL(item.url);
|
|
165
|
-
const [key, value] = paramString.split('=');
|
|
166
|
-
newUrl.searchParams.set(key, value);
|
|
167
|
-
finalUrl = newUrl.toString();
|
|
168
|
-
} catch (e) { console.error('Invalid URL for search result link:', item.url); }
|
|
169
|
-
}
|
|
170
|
-
link.href = finalUrl;
|
|
171
|
-
} else {
|
|
172
|
-
link.href = item.url;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
link.className = 'fhl-icon-display';
|
|
176
|
-
link.setAttribute('aria-label', item.name);
|
|
177
|
-
|
|
178
|
-
if (type === 'search') {
|
|
179
|
-
link.addEventListener('mousedown', () => {
|
|
180
|
-
fetch('https://isai.kr/update_view_count.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.url }) }).catch(console.error);
|
|
181
|
-
});
|
|
182
|
-
link.target = '_blank';
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const iconCircle = document.createElement('div');
|
|
186
|
-
iconCircle.className = 'fhl-icon-circle';
|
|
187
|
-
|
|
188
|
-
if (item.icon) {
|
|
189
|
-
const icon = document.createElement('i');
|
|
190
|
-
icon.className = `ph-bold ${item.icon}`;
|
|
191
|
-
iconCircle.appendChild(icon);
|
|
192
|
-
} else if (item.img) {
|
|
193
|
-
const customImage = document.createElement('img');
|
|
194
|
-
customImage.src = item.img;
|
|
195
|
-
customImage.alt = item.name;
|
|
196
|
-
customImage.onerror = () => { customImage.style.display = 'none'; };
|
|
197
|
-
iconCircle.appendChild(customImage);
|
|
198
|
-
} else {
|
|
199
|
-
const favicon = document.createElement('img');
|
|
200
|
-
favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`;
|
|
201
|
-
favicon.alt = item.name;
|
|
202
|
-
favicon.onerror = () => { favicon.style.display = 'none'; };
|
|
203
|
-
iconCircle.appendChild(favicon);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
link.appendChild(iconCircle);
|
|
207
|
-
|
|
208
|
-
if (type === 'search') {
|
|
209
|
-
const tooltip = document.createElement('div');
|
|
210
|
-
tooltip.className = 'fhl-item-tooltip';
|
|
211
|
-
tooltip.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name;
|
|
212
|
-
link.appendChild(tooltip);
|
|
213
|
-
link.addEventListener('mouseenter', (e) => {
|
|
214
|
-
const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip');
|
|
215
|
-
if (!currentTooltip) return;
|
|
216
|
-
currentTooltip.classList.add('visible');
|
|
217
|
-
const widgetRect = widgetWrapper.getBoundingClientRect();
|
|
218
|
-
const tooltipRect = currentTooltip.getBoundingClientRect();
|
|
219
|
-
let offsetX = 0; const padding = 5;
|
|
220
|
-
if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding; } else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding; }
|
|
221
|
-
if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`; }
|
|
222
|
-
});
|
|
223
|
-
link.addEventListener('mouseleave', (e) => {
|
|
224
|
-
const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip');
|
|
225
|
-
if (!currentTooltip) return;
|
|
226
|
-
currentTooltip.classList.remove('visible');
|
|
227
|
-
currentTooltip.style.transform = 'translateX(-50%)';
|
|
228
|
-
});
|
|
229
|
-
} else {
|
|
230
|
-
const nameSpan = document.createElement('span');
|
|
231
|
-
nameSpan.className = 'fhl-item-name';
|
|
232
|
-
nameSpan.textContent = item.name.length > 5 ? item.name.slice(0, 5) + '..' : item.name;
|
|
233
|
-
link.appendChild(nameSpan);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (type === 'local') {
|
|
237
|
-
const controls = document.createElement('div');
|
|
238
|
-
controls.className = 'fhl-item-controls';
|
|
239
|
-
const deleteBtn = document.createElement('button');
|
|
240
|
-
deleteBtn.className = 'fhl-control-btn';
|
|
241
|
-
deleteBtn.innerHTML = '×';
|
|
242
|
-
deleteBtn.title = '삭제';
|
|
243
|
-
deleteBtn.addEventListener('click', (e) => {
|
|
244
|
-
e.preventDefault(); e.stopPropagation();
|
|
245
|
-
let storedItems = shortcutManager.get();
|
|
246
|
-
storedItems = storedItems.filter(stored => stored.url !== item.url);
|
|
247
|
-
shortcutManager.set(storedItems);
|
|
248
|
-
initialRender();
|
|
249
|
-
});
|
|
250
|
-
const moveBtn = document.createElement('button');
|
|
251
|
-
moveBtn.className = 'fhl-control-btn';
|
|
252
|
-
moveBtn.innerHTML = '>';
|
|
253
|
-
moveBtn.title = '오른쪽으로 이동';
|
|
254
|
-
moveBtn.addEventListener('click', (e) => {
|
|
255
|
-
e.preventDefault(); e.stopPropagation();
|
|
256
|
-
let storedItems = shortcutManager.get();
|
|
257
|
-
const currentIndex = storedItems.findIndex(stored => stored.url === item.url);
|
|
258
|
-
if (currentIndex > -1) {
|
|
259
|
-
const newIndex = (currentIndex + 1) % storedItems.length;
|
|
260
|
-
const [movedItem] = storedItems.splice(currentIndex, 1);
|
|
261
|
-
storedItems.splice(newIndex, 0, movedItem);
|
|
262
|
-
shortcutManager.set(storedItems);
|
|
263
|
-
initialRender();
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
controls.appendChild(deleteBtn);
|
|
267
|
-
controls.appendChild(moveBtn);
|
|
268
|
-
link.appendChild(controls);
|
|
269
|
-
}
|
|
270
|
-
return link;
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
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; };
|
|
281
|
-
const renderInitialResults = (items) => { searchResultsContainer.innerHTML = ''; currentPage = 1; currentDisplayItems = items; appendNextPage(); };
|
|
282
|
-
const openSearch = async () => { widgetContainer.classList.add('search-mode'); widgetWrapper.classList.add('search-mode'); searchInput.focus(); if (fullApiItemsData === null) { try { const response = await fetch('https://isai.kr/appapi2.php'); if (!response.ok) throw new Error('API 응답 오류'); fullApiItemsData = await response.json(); } catch (error) { console.error('전체 앱 목록 로드 실패:', error); searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>'; fullApiItemsData = []; return; } } renderInitialResults(fullApiItemsData); };
|
|
283
|
-
|
|
284
|
-
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://zoai.oduc.kr/ko/character/select',icon:'ph-chats-circle'},{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:'http://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='';const c=shortcutManager.get();let d=[...b];d.splice(2,0,...c);d.forEach(e=>{const f=c.some(g=>g.url===e.url);const h=f&&!e.icon&&!e.img?'local':'default';listContainer.appendChild(createIconItem(e,h));});checkScrollability();updateAllWidgetLinks();};
|
|
285
|
-
|
|
286
|
-
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)`; };
|
|
287
|
-
const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator(); };
|
|
288
|
-
let isDragging = false;
|
|
289
|
-
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; };
|
|
290
|
-
const handleDragEnd = () => { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', handleDragMove); document.removeEventListener('mouseup', handleDragEnd); };
|
|
291
|
-
indicatorTrack.addEventListener('mousedown', (e) => { isDragging = true; handleDragMove(e); document.addEventListener('mousemove', handleDragMove); document.addEventListener('mouseup', handleDragEnd); });
|
|
292
|
-
const closeSearch = () => { widgetContainer.classList.remove('search-mode'); widgetWrapper.classList.remove('search-mode'); searchInput.value = ''; searchResultsContainer.innerHTML = ''; };
|
|
293
|
-
let hideTimeout = null;
|
|
294
|
-
widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); });
|
|
295
|
-
widgetContainer.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); });
|
|
296
|
-
document.addEventListener('click', (e) => { if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) { languagePanel.classList.remove('visible'); } if (!widgetContainer.classList.contains('hidden') || widgetContainer.contains(e.target)) { return; } widgetContainer.classList.remove('hidden'); clearTimeout(hideTimeout); hideTimeout = setTimeout(() => { widgetContainer.classList.add('hidden'); }, 6000); });
|
|
297
|
-
listContainer.addEventListener('scroll', updateScrollIndicator);
|
|
298
|
-
window.addEventListener('resize', checkScrollability);
|
|
299
|
-
searchTrigger.addEventListener('click', openSearch);
|
|
300
|
-
languageBtn.addEventListener('click', (e) => { e.stopPropagation(); languagePanel.classList.toggle('visible'); });
|
|
301
|
-
searchInput.addEventListener('input', function() { if (!fullApiItemsData) return; const searchTerm = this.value.toLowerCase().trim(); const filteredItems = fullApiItemsData.filter(item => item.name.toLowerCase().includes(searchTerm)); renderInitialResults(filteredItems); });
|
|
302
|
-
searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage(); } });
|
|
303
|
-
searchClose.addEventListener('click', closeSearch);
|
|
304
|
-
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch(); } });
|
|
305
|
-
|
|
306
|
-
const addCurrentPageToWidget=async a=>{a.preventDefault();const b=translations[currentLang]||translations.en,c=getProcessedUrl(window.location.href);let d=document.title||b.currentPage;try{const e=await fetch(`https://isai.kr/get_title.php?url=${encodeURIComponent(c)}`);if(e.ok){const f=await e.json();f.title&&(d=f.title)}}catch(g){console.error("Failed to fetch title:",g)}const h={name:d,url:c};let i=shortcutManager.get();if(!i.some(j=>getProcessedUrl(j.url)===getProcessedUrl(h.url))){i.length>=10&&i.shift(),i.push(h),shortcutManager.set(i);try{await fetch("https://isai.kr/register_app.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)})}catch(k){console.error("DB 등록/갱신 실패:",k)}initialRender(),widgetWrapper.classList.contains("search-mode")&&closeSearch()}else showToast(b.alreadyAdded)};
|
|
307
|
-
|
|
308
|
-
const copyWidgetScript = (e) => {
|
|
309
|
-
e.preventDefault();
|
|
310
|
-
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>';
|
|
311
|
-
const t = translations[currentLang] || translations['en'];
|
|
312
|
-
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); };
|
|
313
|
-
if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(textToCopy).then(() => { showToast(t.codeCopied); }).catch(() => { fallbackCopy(); }); } else { fallbackCopy(); }
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
addToHomeBtn.addEventListener('click', addCurrentPageToWidget);
|
|
317
|
-
searchAddBtn.addEventListener('click', copyWidgetScript);
|
|
318
|
-
|
|
319
|
-
shortcutManager.syncFromURL();
|
|
320
|
-
shortcutManager.get();
|
|
321
|
-
|
|
322
|
-
populateLanguagePanel();
|
|
323
|
-
applyLanguage(currentLang);
|
|
324
|
-
widgetContainer.classList.add('hidden');
|
|
325
|
-
});
|
|
1
|
+
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 data.shortcuts || []; } 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 = atob(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=${btoa(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') => { 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 = 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 t = translations[currentLang] || translations['en']; const b = [ { name: t.search, url: 'https://isai.kr', icon: 'ph-sparkle' }, { name: t.question, url: 'https://isai.kr/#chat', icon: 'ph-question-mark' }, { name: t.characterChat, url: 'https://zoai.oduc.kr/ko/character/select', icon: 'ph-chats-circle' }, { name: t.translate, url: 'https://translato.isai.kr/', icon: 'ph-translate' }, { name: t.tarot, url: 'https://tarot.isai.kr/', icon: 'ph-cards' }, { name: t.blog, url: 'https://blog.099.kr', icon: 'ph-article-medium' }, { name: t.novel, url: 'https://ranovel.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.2.2/__ra.png' }, { name: t.psychology, url: 'https://simpong.oduc.kr/', img: 'http://cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' }, { name: t.forum, url: 'https://logig.im', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.3.9/_______________logig.png' }, { name: t.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' }, ]; listContainer.innerHTML = ''; const c = shortcutManager.get(); let d = [...b]; d.splice(2, 0, ...c); d.forEach(e => { const f = c.some(g => g.url === e.url); const h = f && !e.icon && !e.img ? 'local' : 'default'; listContainer.appendChild(createIconItem(e, h)); }); 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
|
@@ -7,7 +7,7 @@ const widgetHTML = `
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
<div id="fhl-toast-notification" class="fhl-toast"></div>
|
|
10
|
-
<div
|
|
10
|
+
<div id="fhl-widget-container" class="hidden">
|
|
11
11
|
<div class="fhl-indicator-track" id="fhl-indicator-track">
|
|
12
12
|
<div class="fhl-scroll-indicator" id="fhl-scroll-indicator"></div>
|
|
13
13
|
</div>
|
|
@@ -27,10 +27,10 @@ const widgetHTML = `
|
|
|
27
27
|
<div class="fhl-search-results" id="fhl-search-results"></div>
|
|
28
28
|
<div class="fhl-fixed-panel">
|
|
29
29
|
<a href="#" id="fhl-search-add-btn" class="fhl-fixed-item"><i class="ph-bold ph-code"></i></a>
|
|
30
|
-
<a href="https://isai.kr/#chat" id="fhl-fixed-
|
|
30
|
+
<a href="https://isai.kr/#chat" id="fhl-fixed-question" class="fhl-fixed-item"><i class="ph-bold ph-question"></i></a>
|
|
31
31
|
<a href="https://zoai.oduc.kr/ko/character/select" id="fhl-fixed-chat" class="fhl-fixed-item"><i class="ph-bold ph-chats-circle"></i></a>
|
|
32
32
|
<a href="https://gig.snapp.im/" id="fhl-fixed-ad" class="fhl-fixed-item"><i class="ph-bold ph-currency-circle-dollar"></i></a>
|
|
33
|
-
<a href="https://webstore.isai.kr/" id="fhl-fixed-
|
|
33
|
+
<a href="https://webstore.isai.kr/" id="fhl-fixed-info" class="fhl-fixed-item"><i class="ph-bold ph-info"></i></a>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
@@ -38,7 +38,6 @@ const widgetHTML = `
|
|
|
38
38
|
<div id="fhl-language-panel" class="fhl-language-panel"></div>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
|
-
|
|
42
41
|
`;
|
|
43
42
|
|
|
44
43
|
// 3. appContainer가 실제로 존재하는지 확인한 후,
|
package/ws_css.css
CHANGED
|
@@ -1,51 +1 @@
|
|
|
1
|
-
|
|
2
|
-
.fhl-widget-wrapper *, .fhl-widget-wrapper *::before, .fhl-widget-wrapper *::after { box-sizing: border-box; }
|
|
3
|
-
.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; }
|
|
4
|
-
.fhl-widget-container.hidden { opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none; }
|
|
5
|
-
.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; }
|
|
6
|
-
.fhl-widget-container.scrollable .fhl-indicator-track { opacity: 1; }
|
|
7
|
-
.fhl-widget-container.search-mode .fhl-indicator-track { display: none; }
|
|
8
|
-
.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; }
|
|
9
|
-
.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); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; transition: all 0.3s ease-in-out; pointer-events: auto; overflow: visible; }
|
|
10
|
-
.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; }
|
|
11
|
-
.fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.1); }
|
|
12
|
-
.fhl-icon-display i { font-size: 22px; }
|
|
13
|
-
.fhl-list-container .fhl-icon-display { display: inline-flex; width: auto; padding: 0 8px 0 0; border-radius: 20px; background-color: transparent; }
|
|
14
|
-
.fhl-list-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.08); }
|
|
15
|
-
.fhl-list-container .fhl-icon-display:hover .fhl-icon-circle { background-color: transparent; }
|
|
16
|
-
.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; }
|
|
17
|
-
.fhl-list-container .fhl-icon-display:hover .fhl-item-name { max-width: 150px; opacity: 1; }
|
|
18
|
-
.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; }
|
|
19
|
-
.fhl-icon-circle img { width: 24px; height: 24px; object-fit: cover; border-radius: 50%; }
|
|
20
|
-
.fhl-icon-bar { display: flex; align-items: center; gap: 8px; width: 100%; transition: opacity 0.3s ease; }
|
|
21
|
-
.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; }
|
|
22
|
-
.fhl-list-container::-webkit-scrollbar { display: none; }
|
|
23
|
-
.fhl-list-container > .fhl-icon-display { scroll-snap-align: center; }
|
|
24
|
-
.fhl-widget-wrapper.search-mode { height: 280px; align-items: flex-start; border-radius: 20px; }
|
|
25
|
-
.fhl-widget-wrapper.search-mode .fhl-icon-bar { opacity: 0; pointer-events: none; }
|
|
26
|
-
.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; }
|
|
27
|
-
.fhl-widget-wrapper.search-mode .fhl-search-view { opacity: 1; visibility: visible; }
|
|
28
|
-
.fhl-search-input-wrapper { display: flex; align-items: center; flex-shrink: 0; margin-bottom: 10px; }
|
|
29
|
-
.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; }
|
|
30
|
-
.fhl-search-input:focus { border-bottom-color: #007bff; }
|
|
31
|
-
.fhl-search-content { display: flex; flex-grow: 1; overflow: hidden; }
|
|
32
|
-
.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; }
|
|
33
|
-
.fhl-search-results::-webkit-scrollbar { display: none; }
|
|
34
|
-
.fhl-search-results .fhl-icon-display { background-color: transparent; }
|
|
35
|
-
.fhl-search-results .fhl-icon-display:hover .fhl-icon-circle { background-color: rgba(0, 0, 0, 0.1); }
|
|
36
|
-
.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; 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; }
|
|
37
|
-
.fhl-item-tooltip.visible { opacity: 1; visibility: visible; bottom: -32px; }
|
|
38
|
-
.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; }
|
|
39
|
-
.fhl-fixed-item { display: flex; align-items: center; justify-content: center; width: 42px; height: 42px; border-radius: 10px; text-decoration: none; color: #444; cursor: pointer; transition: background-color 0.2s ease; }
|
|
40
|
-
.fhl-fixed-item:hover { background-color: rgba(0, 0, 0, 0.08); }
|
|
41
|
-
.fhl-fixed-item i { font-size: 24px; }
|
|
42
|
-
.fhl-item-controls { position: absolute; bottom: -20px; left: 50%; 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; }
|
|
43
|
-
.fhl-list-container .fhl-icon-display:hover .fhl-item-controls { opacity: 1; visibility: visible; pointer-events: auto; bottom: -13px; }
|
|
44
|
-
.fhl-control-btn { background: none; border: none; color: white; cursor: pointer; font-size: 16px; line-height: 1; padding: 2px; font-family: monospace; }
|
|
45
|
-
.fhl-control-btn:hover { color: #007bff; }
|
|
46
|
-
.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; }
|
|
47
|
-
.fhl-toast.visible { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
|
48
|
-
.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; }
|
|
49
|
-
.fhl-language-panel.visible { opacity: 1; visibility: visible; pointer-events: auto; }
|
|
50
|
-
.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; }
|
|
51
|
-
.fhl-language-item:hover { background-color: rgba(0,0,0,0.1); }
|
|
1
|
+
.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; 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; 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; } #fhl-widget-container .fhl-item-controls { position: absolute; bottom: -20px; left: 50%; 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 .fhl-icon-display:hover .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-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); } .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); }
|