cdnhost 2.6.4 → 2.6.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdnhost",
3
- "version": "2.6.4",
3
+ "version": "2.6.7",
4
4
  "description": "cdnhost",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/ws_cdn.js CHANGED
@@ -1,14 +1,15 @@
1
-
1
+
2
2
  document.addEventListener('DOMContentLoaded', function() {
3
3
  if (window.FHLWidgetInitialized) return;
4
4
  window.FHLWidgetInitialized = true;
5
5
 
6
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;
7
+
8
8
  let currentDisplayItems = [];
9
9
  let currentPage = 1;
10
10
  const ITEMS_PER_PAGE = 30;
11
11
  let isLoadingNextPage = false;
12
+ let searchTimeout = null;
12
13
 
13
14
  const widgetContainer = document.getElementById('fhl-widget-container');
14
15
  const widgetWrapper = document.getElementById('fhl-widget-wrapper');
@@ -25,12 +26,14 @@
25
26
  const languageBtn = document.getElementById('fhl-language-btn');
26
27
  const languagePanel = document.getElementById('fhl-language-panel');
27
28
 
28
- const fixedImageBtn = document.getElementById('fhl-fixed-image');
29
+ const fixedQuestionBtn = document.getElementById('fhl-fixed-question');
29
30
  const fixedChatBtn = document.getElementById('fhl-fixed-chat');
30
31
  const fixedAdBtn = document.getElementById('fhl-fixed-ad');
32
+ const fixedInfoBtn = document.getElementById('fhl-fixed-info');
31
33
 
32
34
  let toastTimeout = null;
33
35
  let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko';
36
+ const API_BASE_URL = 'https://isai.kr';
34
37
 
35
38
  const shortcutManager = {
36
39
  key: 'fhl-custom-icons-with-expiry',
@@ -62,9 +65,7 @@
62
65
  const decodedData = atob(shortcutsParam);
63
66
  JSON.parse(decodedData);
64
67
  localStorage.setItem(shortcutManager.key, decodedData);
65
- } catch (e) {
66
- console.error("Failed to parse shortcuts from URL:", e);
67
- }
68
+ } catch (e) { console.error("Failed to parse shortcuts from URL:", e); }
68
69
  }
69
70
  },
70
71
  getAsParam: () => {
@@ -77,7 +78,7 @@
77
78
  const updateAllWidgetLinks = () => {
78
79
  const paramString = shortcutManager.getAsParam();
79
80
  if (!paramString) return;
80
- const allLinks = document.querySelectorAll('.fhl-icon-display, .fhl-fixed-item');
81
+ const allLinks = document.querySelectorAll('#fhl-widget-container .fhl-icon-display, #fhl-widget-container .fhl-fixed-item');
81
82
  allLinks.forEach(link => {
82
83
  if (link.tagName === 'A') {
83
84
  const originalHref = link.getAttribute('href');
@@ -95,194 +96,112 @@
95
96
  const showToast = (message) => { clearTimeout(toastTimeout); toastElement.textContent = message; toastElement.classList.add('visible'); toastTimeout = setTimeout(() => { toastElement.classList.remove('visible'); }, 2500); };
96
97
 
97
98
  const applyLanguage = (langCode) => {
98
- if (typeof translations === 'undefined') {
99
- console.error("Translations not loaded! Make sure lang.js is included correctly.");
100
- return;
101
- }
99
+ if (typeof translations === 'undefined') { console.error("Translations not loaded! Make sure lang.js is included correctly."); return; }
102
100
  const t = translations[langCode] || translations['en'];
103
-
104
101
  document.documentElement.lang = langCode;
105
-
106
102
  searchTrigger.setAttribute('aria-label', t.openSearch);
107
103
  addToHomeBtn.setAttribute('aria-label', t.addToWidget);
108
104
  searchClose.setAttribute('aria-label', t.closeSearch);
109
105
  languageBtn.setAttribute('aria-label', t.changeLanguage);
110
106
  searchAddBtn.setAttribute('aria-label', 'Copy Widget Code');
111
- fixedImageBtn.setAttribute('aria-label', t.images);
107
+ fixedQuestionBtn.setAttribute('aria-label', t.question);
112
108
  fixedChatBtn.setAttribute('aria-label', t.characterChat);
113
109
  fixedAdBtn.setAttribute('aria-label', t.ads);
114
-
110
+ fixedInfoBtn.setAttribute('aria-label', t.info);
115
111
  searchInput.placeholder = t.searchPlaceholder;
116
-
117
112
  initialRender();
118
113
  };
119
114
 
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
- };
115
+ 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); }); };
116
+ const getProcessedUrl = (fullUrl) => { try { const urlObj = new URL(fullUrl); return `${urlObj.protocol}//${urlObj.hostname}`; } catch (e) { return fullUrl; } };
142
117
 
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
118
  const createIconItem = (item, type = 'default') => {
157
119
  const link = document.createElement('a');
158
-
159
120
  if (type === 'search') {
160
121
  const paramString = shortcutManager.getAsParam();
161
122
  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
- }
123
+ 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); } }
170
124
  link.href = finalUrl;
171
- } else {
172
- link.href = item.url;
173
- }
174
-
125
+ } else { link.href = item.url; }
175
126
  link.className = 'fhl-icon-display';
176
127
  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
-
128
+ 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'; }
129
+ const iconCircle = document.createElement('div'); iconCircle.className = 'fhl-icon-circle';
130
+ if (item.icon) { const icon = document.createElement('i'); icon.className = `ph-bold ${item.icon}`; iconCircle.appendChild(icon); }
131
+ 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); }
132
+ 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); }
206
133
  link.appendChild(iconCircle);
207
-
208
134
  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
-
135
+ 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);
136
+ 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))`; } });
137
+ link.addEventListener('mouseleave', (e) => { const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip'); if (!currentTooltip) return; currentTooltip.classList.remove('visible'); currentTooltip.style.transform = 'translateX(-50%)'; });
138
+ } 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); }
236
139
  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 = '&times;';
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 = '&gt;';
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);
140
+ const controls = document.createElement('div'); controls.className = 'fhl-item-controls';
141
+ const deleteBtn = document.createElement('button'); deleteBtn.className = 'fhl-control-btn'; deleteBtn.innerHTML = '&times;'; deleteBtn.title = '삭제';
142
+ deleteBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); let storedItems = shortcutManager.get(); storedItems = storedItems.filter(stored => stored.url !== item.url); shortcutManager.set(storedItems); initialRender(); });
143
+ const moveBtn = document.createElement('button'); moveBtn.className = 'fhl-control-btn'; moveBtn.innerHTML = '&gt;'; moveBtn.title = '오른쪽으로 이동';
144
+ 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(); } });
145
+ controls.appendChild(deleteBtn); controls.appendChild(moveBtn); link.appendChild(controls);
269
146
  }
270
147
  return link;
271
148
  };
272
149
 
150
+ 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; };
151
+
152
+ const renderInitialResults = (items) => {
153
+ searchResultsContainer.innerHTML = '';
154
+ currentPage = 1;
155
+ currentDisplayItems = items;
156
+ appendNextPage();
157
+ };
273
158
 
159
+ const performSearch = async (query = '') => {
160
+ try {
161
+ const response = await fetch(`${API_BASE_URL}/appapi2.php?query=${encodeURIComponent(query)}`);
162
+ if (!response.ok) throw new Error('API 응답 오류');
163
+ const results = await response.json();
164
+ renderInitialResults(results);
165
+ } catch (error) {
166
+ console.error('앱 목록 로드 실패:', error);
167
+ searchResultsContainer.innerHTML = '<p>목록 로드 실패</p>';
168
+ }
169
+ };
274
170
 
171
+ const openSearch = () => {
172
+ widgetContainer.classList.add('search-mode');
173
+ widgetWrapper.classList.add('search-mode');
174
+ searchInput.focus();
175
+ performSearch();
176
+ };
275
177
 
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:'NOVEL',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',icon:'ph-chats-circle'},{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
-
178
+ const initialRender = () => {
179
+ const t = translations[currentLang] || translations['en'];
180
+ const b = [
181
+ { name: t.search, url: 'https://isai.kr', icon: 'ph-sparkle' },
182
+ { name: t.question, url: 'https://isai.kr/#chat', icon: 'ph-question-mark' },
183
+ { name: t.characterChat, url: 'https://zoai.oduc.kr/ko/character/select', icon: 'ph-chats-circle' },
184
+ { name: t.translate, url: 'https://translato.isai.kr/', icon: 'ph-translate' },
185
+ { name: t.tarot, url: 'https://tarot.isai.kr/', icon: 'ph-cards' },
186
+ { name: t.blog, url: 'https://blog.099.kr', icon: 'ph-article-medium' },
187
+ { name: t.novel, url: 'https://ranovel.kr/', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.2.2/__ra.png' },
188
+ { name: t.psychology, url: 'https://simpong.oduc.kr/', img: 'http://cdn.jsdelivr.net/npm/cdnhost@2.2.0/_simpong.png' },
189
+ { name: t.forum, url: 'https://logig.im', img: 'https://cdn.jsdelivr.net/npm/cdnhost@2.3.9/_______________logig.png' },
190
+ { name: t.ads, url: 'https://gig.snapp.im/', icon: 'ph-currency-circle-dollar' },
191
+ ];
192
+ listContainer.innerHTML = '';
193
+ const c = shortcutManager.get();
194
+ let d = [...b];
195
+ d.splice(2, 0, ...c);
196
+ d.forEach(e => {
197
+ const f = c.some(g => g.url === e.url);
198
+ const h = f && !e.icon && !e.img ? 'local' : 'default';
199
+ listContainer.appendChild(createIconItem(e, h));
200
+ });
201
+ checkScrollability();
202
+ updateAllWidgetLinks();
203
+ };
204
+
286
205
  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
206
  const checkScrollability = () => { const isScrollable = listContainer.scrollWidth > listContainer.clientWidth; widgetContainer.classList.toggle('scrollable', isScrollable); if (isScrollable) updateScrollIndicator(); };
288
207
  let isDragging = false;
@@ -293,17 +212,45 @@
293
212
  let hideTimeout = null;
294
213
  widgetContainer.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); });
295
214
  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); });
215
+
216
+ document.addEventListener('click', (e) => {
217
+ if (!languagePanel.contains(e.target) && !languageBtn.contains(e.target)) {
218
+ languagePanel.classList.remove('visible');
219
+ }
220
+ if (widgetContainer.contains(e.target)) {
221
+ return;
222
+ }
223
+ if (widgetContainer.classList.contains('hidden')) {
224
+ setTimeout(() => {
225
+ widgetContainer.classList.remove('hidden');
226
+ clearTimeout(hideTimeout);
227
+ hideTimeout = setTimeout(() => {
228
+ widgetContainer.classList.add('hidden');
229
+ }, 6000);
230
+ }, 50);
231
+ }
232
+ });
233
+
297
234
  listContainer.addEventListener('scroll', updateScrollIndicator);
298
235
  window.addEventListener('resize', checkScrollability);
299
236
  searchTrigger.addEventListener('click', openSearch);
300
237
  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); });
238
+
239
+ searchInput.addEventListener('input', function() {
240
+ clearTimeout(searchTimeout);
241
+ const query = this.value.toLowerCase().trim();
242
+ searchTimeout = setTimeout(() => {
243
+ performSearch(query);
244
+ }, 300);
245
+ });
246
+
302
247
  searchResultsContainer.addEventListener('scroll', () => { const isAtBottom = searchResultsContainer.scrollTop + searchResultsContainer.clientHeight >= searchResultsContainer.scrollHeight - 10; if (isAtBottom) { appendNextPage(); } });
303
248
  searchClose.addEventListener('click', closeSearch);
304
249
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && widgetWrapper.classList.contains('search-mode')) { closeSearch(); } });
305
250
 
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)};
251
+
252
+ const _0x3e4a = ['preventDefault', 'en', 'currentPage', 'get_title.php?url=', 'title', 'Failed to fetch title:', 'name', 'url', 'some', 'shift', 'push', 'POST', 'Content-Type', 'application/json', 'register_app.php', 'DB 등록/갱신 실패:', 'search-mode', 'alreadyAdded', 'json', 'ok', 'error', 'classList', 'contains', 'log']; (function(_0x1a8c3d, _0x3e4a3f) { const _0x58ab4a = function(_0x2e8f1c) { while (--_0x2e8f1c) { _0x1a8c3d['push'](_0x1a8c3d['shift']()); } }; _0x58ab4a(++_0x3e4a3f); }(_0x3e4a, 0x18a)); const _0x5a8d = function(_0x1a8c3d, _0x3e4a3f) { _0x1a8c3d = _0x1a8c3d - 0x0; let _0x58ab4a = _0x3e4a[_0x1a8c3d]; return _0x58ab4a; }; const addCurrentPageToWidget = async (_0x1b4c5d) => { _0x1b4c5d[_0x5a8d('0x0')](); const _0x5a7b8e = translations[currentLang] || translations[_0x5a8d('0x1')]; const _0x2a9e3f = getProcessedUrl(window.location.href); let _0x4d2a1c = document[_0x5a8d('0x4')] || _0x5a7b8e[_0x5a8d('0x2')]; try { const _0x4f12f8 = await fetch(`${API_BASE_URL}/${_0x5a8d('0x3')}${encodeURIComponent(_0x2a9e3f)}`); if (_0x4f12f8[_0x5a8d('0x13')]) { const _0x39f2c3 = await _0x4f12f8[_0x5a8d('0x12')](); if (_0x39f2c3[_0x5a8d('0x4')]) { _0x4d2a1c = _0x39f2c3[_0x5a8d('0x4')]; } } } catch (_0x5c4d5d) { console[_0x5a8d('0x14')](_0x5a8d('0x5'), _0x5c4d5d); } const _0x4d5b2e = { [_0x5a8d('0x6')]: _0x4d2a1c, [_0x5a8d('0x7')]: _0x2a9e3f }; let _0x2b8e4f = shortcutManager.get(); if (!_0x2b8e4f[_0x5a8d('0x8')]((_0x5b3e9a) => getProcessedUrl(_0x5b3e9a[_0x5a8d('0x7')]) === getProcessedUrl(_0x4d5b2e[_0x5a8d('0x7')]))) { if (_0x2b8e4f.length >= 0xa) { _0x2b8e4f[_0x5a8d('0x9')](); } _0x2b8e4f[_0x5a8d('0xa')](_0x4d5b2e); shortcutManager.set(_0x2b8e4f); try { await fetch(`${API_BASE_URL}/${_0x5a8d('0xe')}`, { 'method': _0x5a8d('0xb'), 'headers': { [_0x5a8d('0xc')]: _0x5a8d('0xd') }, 'body': JSON.stringify(_0x4d5b2e) }); } catch (_0x3e1b1b) { console[_0x5a8d('0x14')](_0x5a8d('0xf'), _0x3e1b1b); } initialRender(); if (widgetWrapper[_0x5a8d('0x15')][_0x5a8d('0x16')](_0x5a8d('0x10'))) { closeSearch(); } } else { showToast(_0x5a7b8e[_0x5a8d('0x11')]); } };
253
+
307
254
 
308
255
  const copyWidgetScript = (e) => {
309
256
  e.preventDefault();
@@ -321,5 +268,4 @@
321
268
 
322
269
  populateLanguagePanel();
323
270
  applyLanguage(currentLang);
324
- widgetContainer.classList.add('hidden');
325
271
  });
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 class="fhl-widget-container" id="fhl-widget-container">
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-image" class="fhl-fixed-item"><i class="ph-bold ph-question-mark"></i></a>
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-ad" class="fhl-fixed-item"><i class="ph-bold ph-info"></i></a>
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>
package/ws_css.css CHANGED
@@ -1,51 +1,57 @@
1
1
 
2
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; }
3
+ #fhl-widget-container {
4
+ position: fixed; z-index: 1000; bottom: 20px; left: 50%;
5
+ transform: translateX(-50%) translateY(0); display: flex; flex-direction: column; align-items: center;
6
+ width: calc(100% - 30px); max-width: 460px; pointer-events: auto;
7
+ transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
8
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
9
+ }
10
+ #fhl-widget-container.hidden { opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none; }
11
+ #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; }
12
+ #fhl-widget-container.scrollable .fhl-indicator-track { opacity: 1; }
13
+ #fhl-widget-container.search-mode .fhl-indicator-track { display: none; }
14
+ #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; }
15
+ #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; }
16
+ #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; }
17
+ #fhl-widget-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.1); }
18
+ #fhl-widget-container .fhl-icon-display i { font-size: 22px; }
19
+ #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; }
20
+ #fhl-widget-container .fhl-list-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.08); }
21
+ #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-icon-circle { background-color: transparent; }
22
+ #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; }
23
+ #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-item-name { max-width: 150px; opacity: 1; }
24
+ #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; }
25
+ #fhl-widget-container .fhl-icon-circle img { width: 24px; height: 24px; object-fit: cover; border-radius: 50%; }
26
+ #fhl-widget-container .fhl-icon-bar { display: flex; align-items: center; gap: 8px; width: 100%; transition: opacity 0.3s ease; }
27
+ #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; }
28
+ #fhl-widget-container .fhl-list-container::-webkit-scrollbar { display: none; }
29
+ #fhl-widget-container .fhl-list-container > .fhl-icon-display { scroll-snap-align: center; }
30
+ #fhl-widget-container .fhl-widget-wrapper.search-mode { height: 280px; align-items: flex-start; border-radius: 20px; }
31
+ #fhl-widget-container .fhl-widget-wrapper.search-mode .fhl-icon-bar { opacity: 0; pointer-events: none; }
32
+ #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; }
33
+ #fhl-widget-container .fhl-widget-wrapper.search-mode .fhl-search-view { opacity: 1; visibility: visible; }
34
+ #fhl-widget-container .fhl-search-input-wrapper { display: flex; align-items: center; flex-shrink: 0; margin-bottom: 10px; }
35
+ #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; }
36
+ #fhl-widget-container .fhl-search-input:focus { border-bottom-color: #007bff; }
37
+ #fhl-widget-container .fhl-search-content { display: flex; flex-grow: 1; overflow: hidden; }
38
+ #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; }
39
+ #fhl-widget-container .fhl-search-results::-webkit-scrollbar { display: none; }
40
+ #fhl-widget-container .fhl-search-results .fhl-icon-display { background-color: transparent; }
41
+ #fhl-widget-container .fhl-search-results .fhl-icon-display:hover .fhl-icon-circle { background-color: rgba(0, 0, 0, 0.1); }
42
+ #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; }
43
+ #fhl-widget-container .fhl-item-tooltip.visible { opacity: 1; visibility: visible; bottom: -32px; }
44
+ #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; }
45
+ #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; }
46
+ #fhl-widget-container .fhl-fixed-item:hover { background-color: rgba(0, 0, 0, 0.08); }
47
+ #fhl-widget-container .fhl-fixed-item i { font-size: 24px; }
48
+ #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; }
49
+ #fhl-widget-container .fhl-list-container .fhl-icon-display:hover .fhl-item-controls { opacity: 1; visibility: visible; pointer-events: auto; bottom: -13px; }
50
+ #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; }
51
+ #fhl-widget-container .fhl-control-btn:hover { color: #007bff; }
52
+ #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; }
53
+ #fhl-widget-container .fhl-language-panel.visible { opacity: 1; visibility: visible; pointer-events: auto; }
54
+ #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; }
55
+ #fhl-widget-container .fhl-language-item:hover { background-color: rgba(0,0,0,0.1); }
46
56
  .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); }
57
+ .fhl-toast.visible { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }