cdnhost 2.5.7 → 2.5.9

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.5.7",
3
+ "version": "2.5.9",
4
4
  "description": "cdnhost",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/ws_cdn.js CHANGED
@@ -1,14 +1,7 @@
1
1
 
2
2
  document.addEventListener('DOMContentLoaded', function() {
3
3
 
4
- const languages = [
5
- { code: 'ko', name: '한국어' }, { code: 'en', name: 'English' }, { code: 'es', name: 'Español' },
6
- { code: 'hi', name: 'हिन्दी' }, { code: 'ar', name: 'العربية' }, { code: 'de', name: 'Deutsch' },
7
- { code: 'fr', name: 'Français' }, { code: 'pt', name: 'Português' }, { code: 'bn', name: 'বাংলা' },
8
- { code: 'ja', name: '日本語' }, { code: 'ru', name: 'Русский' }, { code: 'zh', name: '简体中文' },
9
- { code: 'th', name: 'ไทย' }, { code: 'vi', name: 'Tiếng Việt' }, { code: 'id', name: 'Indonesia' },
10
- { code: 'tr', name: 'Türkçe' }, { code: 'ur', name: 'اردو' }
11
- ];
4
+ 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: 'اردو' }];
12
5
 
13
6
  let fullApiItemsData = null;
14
7
  let currentDisplayItems = [];
@@ -31,13 +24,74 @@
31
24
  const languageBtn = document.getElementById('fhl-language-btn');
32
25
  const languagePanel = document.getElementById('fhl-language-panel');
33
26
 
34
- const fixedQuestionBtn = document.getElementById('fhl-fixed-question');
27
+ // [수정됨] `fhl-fixed-question` -> `fhl-fixed-image`로 되돌리고, 올바른 ID를 참조합니다.
28
+ const fixedImageBtn = document.getElementById('fhl-fixed-image');
35
29
  const fixedChatBtn = document.getElementById('fhl-fixed-chat');
36
30
  const fixedAdBtn = document.getElementById('fhl-fixed-ad');
37
31
 
38
32
  let toastTimeout = null;
39
33
  let currentLang = localStorage.getItem('fhl-widget-lang') || 'ko';
40
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
+
41
95
  const showToast = (message) => {
42
96
  clearTimeout(toastTimeout);
43
97
  toastElement.textContent = message;
@@ -61,7 +115,7 @@
61
115
  searchClose.setAttribute('aria-label', t.closeSearch);
62
116
  languageBtn.setAttribute('aria-label', t.changeLanguage);
63
117
  searchAddBtn.setAttribute('aria-label', 'Copy Widget Code');
64
- fixedQuestionBtn.setAttribute('aria-label', t.question);
118
+ fixedImageBtn.setAttribute('aria-label', t.images); // ✨ [수정됨]
65
119
  fixedChatBtn.setAttribute('aria-label', t.characterChat);
66
120
  fixedAdBtn.setAttribute('aria-label', t.ads);
67
121
 
@@ -102,15 +156,31 @@
102
156
  }
103
157
  };
104
158
 
105
- const createIconItem = (item, type = 'default') => {
159
+ const createIconItem = (item, type = 'default') => {
106
160
  const link = document.createElement('a');
107
- link.href = item.url;
161
+
162
+ if (type === 'search') {
163
+ const paramString = shortcutManager.getAsParam();
164
+ let finalUrl = item.url;
165
+ if (paramString) {
166
+ try {
167
+ let newUrl = new URL(item.url);
168
+ const [key, value] = paramString.split('=');
169
+ newUrl.searchParams.set(key, value);
170
+ finalUrl = newUrl.toString();
171
+ } catch (e) { console.error('Invalid URL for search result link:', item.url); }
172
+ }
173
+ link.href = finalUrl;
174
+ } else {
175
+ link.href = item.url;
176
+ }
177
+
108
178
  link.className = 'fhl-icon-display';
109
179
  link.setAttribute('aria-label', item.name);
110
180
 
111
181
  if (type === 'search') {
112
182
  link.addEventListener('mousedown', () => {
113
- fetch('https://isai.kr/update_view_count.php', {
183
+ fetch('update_view_count.php', {
114
184
  method: 'POST',
115
185
  headers: { 'Content-Type': 'application/json' },
116
186
  body: JSON.stringify({ url: item.url })
@@ -122,16 +192,16 @@ const createIconItem = (item, type = 'default') => {
122
192
  const iconCircle = document.createElement('div');
123
193
  iconCircle.className = 'fhl-icon-circle';
124
194
 
125
- if (item.img) {
195
+ if (item.icon) {
196
+ const icon = document.createElement('i');
197
+ icon.className = `ph-bold ${item.icon}`;
198
+ iconCircle.appendChild(icon);
199
+ } else if (item.img) {
126
200
  const customImage = document.createElement('img');
127
201
  customImage.src = item.img;
128
202
  customImage.alt = item.name;
129
203
  customImage.onerror = () => { customImage.style.display = 'none'; };
130
204
  iconCircle.appendChild(customImage);
131
- } else if (item.icon) {
132
- const icon = document.createElement('i');
133
- icon.className = `ph-bold ${item.icon}`;
134
- iconCircle.appendChild(icon);
135
205
  } else {
136
206
  const favicon = document.createElement('img');
137
207
  favicon.src = `https://www.google.com/s2/favicons?sz=64&domain_url=${item.url}`;
@@ -153,16 +223,9 @@ const createIconItem = (item, type = 'default') => {
153
223
  currentTooltip.classList.add('visible');
154
224
  const widgetRect = widgetWrapper.getBoundingClientRect();
155
225
  const tooltipRect = currentTooltip.getBoundingClientRect();
156
- let offsetX = 0;
157
- const padding = 5;
158
- if (tooltipRect.left < widgetRect.left) {
159
- offsetX = widgetRect.left - tooltipRect.left + padding;
160
- } else if (tooltipRect.right > widgetRect.right) {
161
- offsetX = widgetRect.right - tooltipRect.right - padding;
162
- }
163
- if (offsetX !== 0) {
164
- currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`;
165
- }
226
+ let offsetX = 0; const padding = 5;
227
+ if (tooltipRect.left < widgetRect.left) { offsetX = widgetRect.left - tooltipRect.left + padding; } else if (tooltipRect.right > widgetRect.right) { offsetX = widgetRect.right - tooltipRect.right - padding; }
228
+ if (offsetX !== 0) { currentTooltip.style.transform = `translateX(calc(-50% + ${offsetX}px))`; }
166
229
  });
167
230
  link.addEventListener('mouseleave', (e) => {
168
231
  const currentTooltip = e.currentTarget.querySelector('.fhl-item-tooltip');
@@ -185,11 +248,10 @@ const createIconItem = (item, type = 'default') => {
185
248
  deleteBtn.innerHTML = '&times;';
186
249
  deleteBtn.title = '삭제';
187
250
  deleteBtn.addEventListener('click', (e) => {
188
- e.preventDefault();
189
- e.stopPropagation();
190
- let storedItems = JSON.parse(localStorage.getItem('fhl-custom-icons')) || [];
251
+ e.preventDefault(); e.stopPropagation();
252
+ let storedItems = shortcutManager.get();
191
253
  storedItems = storedItems.filter(stored => stored.url !== item.url);
192
- localStorage.setItem('fhl-custom-icons', JSON.stringify(storedItems));
254
+ shortcutManager.set(storedItems);
193
255
  initialRender();
194
256
  });
195
257
  const moveBtn = document.createElement('button');
@@ -197,15 +259,14 @@ const createIconItem = (item, type = 'default') => {
197
259
  moveBtn.innerHTML = '&gt;';
198
260
  moveBtn.title = '오른쪽으로 이동';
199
261
  moveBtn.addEventListener('click', (e) => {
200
- e.preventDefault();
201
- e.stopPropagation();
202
- let storedItems = JSON.parse(localStorage.getItem('fhl-custom-icons')) || [];
262
+ e.preventDefault(); e.stopPropagation();
263
+ let storedItems = shortcutManager.get();
203
264
  const currentIndex = storedItems.findIndex(stored => stored.url === item.url);
204
265
  if (currentIndex > -1) {
205
266
  const newIndex = (currentIndex + 1) % storedItems.length;
206
267
  const [movedItem] = storedItems.splice(currentIndex, 1);
207
268
  storedItems.splice(newIndex, 0, movedItem);
208
- localStorage.setItem('fhl-custom-icons', JSON.stringify(storedItems));
269
+ shortcutManager.set(storedItems);
209
270
  initialRender();
210
271
  }
211
272
  });
@@ -246,7 +307,7 @@ const createIconItem = (item, type = 'default') => {
246
307
  searchInput.focus();
247
308
  if (fullApiItemsData === null) {
248
309
  try {
249
- const response = await fetch('https://isai.kr/appapi2.php');
310
+ const response = await fetch('appapi2.php');
250
311
  if (!response.ok) throw new Error('API 응답 오류');
251
312
  fullApiItemsData = await response.json();
252
313
  } catch (error) {
@@ -274,14 +335,18 @@ const createIconItem = (item, type = 'default') => {
274
335
  ];
275
336
 
276
337
  listContainer.innerHTML = '';
277
- const storedItems = JSON.parse(localStorage.getItem('fhl-custom-icons')) || [];
338
+ const storedItems = shortcutManager.get();
278
339
  let combinedItems = [...initialItemsData];
279
340
  combinedItems.splice(2, 0, ...storedItems);
341
+
280
342
  combinedItems.forEach(item => {
281
343
  const isLocal = storedItems.some(stored => stored.url === item.url);
282
- listContainer.appendChild(createIconItem(item, isLocal ? 'local' : 'default'));
344
+ const itemType = isLocal && !item.icon && !item.img ? 'local' : 'default';
345
+ listContainer.appendChild(createIconItem(item, itemType));
283
346
  });
347
+
284
348
  checkScrollability();
349
+ updateAllWidgetLinks();
285
350
  };
286
351
 
287
352
  const updateScrollIndicator = () => {
@@ -384,13 +449,13 @@ const createIconItem = (item, type = 'default') => {
384
449
  });
385
450
 
386
451
 
452
+
387
453
 
388
- (() => { const _0x1a2b = async _0x3c4d => { _0x3c4d.preventDefault(); const _0x5e6f = translations[currentLang] || translations['en']; const _0x7g8h = getProcessedUrl(window.location.href); let _0x9i0j = document.title || _0x5e6f.currentPage; try { const _0xklmn = await fetch('https://isai.kr/get_title.php?url=' + encodeURIComponent(_0x7g8h)); if (_0xklmn.ok) { const _0xopqr = await _0xklmn.json(); _0xopqr.title && (_0x9i0j = _0xopqr.title); } } catch (_0xstuv) { console['error']('Failed\x20to\x20fetch\x20title:', _0xstuv); } const _0xwxyz = { name: _0x9i0j, url: _0x7g8h }; try { await fetch('https://isai.kr/register_app.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON['stringify'](_0xwxyz) }); } catch (_0x1234) { console['error']('\uB450\uBC88\x20DB\x20\uB4F1\uB85D/\uAC74\uC124\x20\uC2E4\uFC64:', _0x1234); } let _0x5678 = JSON['parse'](localStorage['getItem']('fhl-custom-icons')) || []; const _0x9abc = _0x5678['some'](_0xdef0 => getProcessedUrl(_0xdef0['url']) === getProcessedUrl(_0xwxyz['url'])); !_0x9abc ? ( _0x5678['push'](_0xwxyz), localStorage['setItem']('fhl-custom-icons', JSON['stringify'](_0x5678)), initialRender(), widgetWrapper['classList']['contains']('search-mode') && closeSearch() ) : showToast(_0x5e6f['alreadyAdded']); }; window['addCurrentPageToWidget'] = _0x1a2b; })();
389
-
454
+ const addCurrentPageToWidget=async e=>{e.preventDefault();const _s=["get_title.php?url=","ok","json","title","POST","Content-Type","application/json","register_app.php","DB 등록/갱신 실패:","Failed to fetch title:","some","shift","push","length","get","set","preventDefault","search-mode","contains","classList","closeSearch","initialRender","alreadyAdded"],_=(i)=>_s[i];try{const a=(translations&&translations[currentLang])?translations[currentLang]:translations.en,b=getProcessedUrl(window.location.href);let c=document.title||a.currentPage;try{const d=await fetch(`${_(0)}${encodeURIComponent(b)}`);if(d&&d.status&&d.status>=200&&d.status<300){const eJson=await d[_(2)]();if(eJson&&eJson[_(3)])c=eJson[_(3)];}}catch(errFetchTitle){console.error(_(9),errFetchTitle);}const newIcon={name:c,url:b},sm=shortcutManager;let stored=sm[_(14)]();if(!Array.isArray(stored))stored=[];const isAdded=stored[_(10)](it=>getProcessedUrl(it.url)===getProcessedUrl(newIcon.url));if(!isAdded){if(stored[_(13)]>=10)stored[_(11)]();stored[_(12)](newIcon);sm[_(15)](stored);try{await fetch(_(7),{method:_(4),headers:{[_(5)]:_(6)},body:JSON.stringify(newIcon)});}catch(errDb){console.error(_(8),errDb);}if(typeof window[_(20)]==="function")window[_(20)]();const ww=widgetWrapper;if(ww&&ww[_(19)]&&ww[_(19)][_(18)](_(17)))if(typeof closeSearch==="function")closeSearch();}else showToast(a[_(21)]);}catch(fatal){console.error("addCurrentPageToWidget error:",fatal);}};
390
455
 
391
456
  const copyWidgetScript = (e) => {
392
457
  e.preventDefault();
393
- 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>";
458
+ const textToCopy = "<script src='https://cdn.jsdelivr.net/npm/cdnhost@latest/ws_cdn.js'><\/script>";
394
459
  const t = translations[currentLang] || translations['en'];
395
460
 
396
461
  const fallbackCopy = () => {
@@ -425,6 +490,9 @@ const createIconItem = (item, type = 'default') => {
425
490
  addToHomeBtn.addEventListener('click', addCurrentPageToWidget);
426
491
  searchAddBtn.addEventListener('click', copyWidgetScript);
427
492
 
493
+ shortcutManager.syncFromURL();
494
+ shortcutManager.get();
495
+
428
496
  populateLanguagePanel();
429
497
  applyLanguage(currentLang);
430
498
  widgetContainer.classList.add('hidden');
package/ws_cdnhtml.js CHANGED
@@ -5,6 +5,7 @@ const appContainer = document.getElementById('app');
5
5
  // 이렇게 하면 모든 id, class, 속성들이 완벽하게 보존됩니다.
6
6
  const widgetHTML = `
7
7
 
8
+
8
9
  <div id="fhl-toast-notification" class="fhl-toast"></div>
9
10
  <div class="fhl-widget-container" id="fhl-widget-container">
10
11
  <div class="fhl-indicator-track" id="fhl-indicator-track">
@@ -14,6 +15,7 @@ const widgetHTML = `
14
15
  <div class="fhl-icon-bar" id="fhl-icon-bar">
15
16
  <button class="fhl-icon-display" id="fhl-search-trigger"><i class="ph-bold ph-magnifying-glass"></i></button>
16
17
  <div class="fhl-list-container" id="fhl-list-container"></div>
18
+ <!-- ✨ [수정됨] 언어 버튼이 다시 추가되었습니다. -->
17
19
  <button class="fhl-icon-display" id="fhl-language-btn"><i class="ph-bold ph-globe"></i></button>
18
20
  <a href="#" id="fhl-add-to-home-btn" class="fhl-icon-display"><i class="ph-bold ph-download-simple"></i></a>
19
21
  </div>
@@ -24,9 +26,10 @@ const widgetHTML = `
24
26
  </div>
25
27
  <div class="fhl-search-content">
26
28
  <div class="fhl-search-results" id="fhl-search-results"></div>
29
+ <!-- ✨ [수정됨] 모든 고정 아이콘에 ID가 추가되고, 아이콘이 원래대로 복원되었습니다. -->
27
30
  <div class="fhl-fixed-panel">
28
- <a href="#" id="fhl-search-add-btn" class="fhl-fixed-item"><i class="ph-bold ph-code"></i></a>
29
- <a href="https://isai.kr/#chat" id="fhl-fixed-question" class="fhl-fixed-item"><i class="ph-bold ph-question"></i></a>
31
+ <a href="#" id="fhl-search-add-btn" class="fhl-fixed-item"><i class="ph-bold ph-download-simple"></i></a>
32
+ <a href="#" id="fhl-fixed-image" class="fhl-fixed-item"><i class="ph-bold ph-image"></i></a>
30
33
  <a href="https://zoai.oduc.kr/ko/character/select" id="fhl-fixed-chat" class="fhl-fixed-item"><i class="ph-bold ph-smiley"></i></a>
31
34
  <a href="https://gig.snapp.im/" id="fhl-fixed-ad" class="fhl-fixed-item"><i class="ph-bold ph-megaphone"></i></a>
32
35
  </div>
@@ -36,6 +39,7 @@ const widgetHTML = `
36
39
  <div id="fhl-language-panel" class="fhl-language-panel"></div>
37
40
  </div>
38
41
 
42
+
39
43
  `;
40
44
 
41
45
  // 3. appContainer가 실제로 존재하는지 확인한 후,
package/ws_css.css CHANGED
@@ -1,42 +1,23 @@
1
-
2
1
  .fhl-widget-wrapper *, .fhl-widget-wrapper *::before, .fhl-widget-wrapper *::after { box-sizing: border-box; }
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
- }
2
+ .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; }
9
3
  .fhl-widget-container.hidden { opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none; }
10
4
  .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; }
11
5
  .fhl-widget-container.scrollable .fhl-indicator-track { opacity: 1; }
12
6
  .fhl-widget-container.search-mode .fhl-indicator-track { display: none; }
13
7
  .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; }
14
- .fhl-widget-wrapper {
15
- position: relative; display: flex; align-items: center; width: 100%; max-width: 460px; height: 52px; padding: 7px 10px; border-radius: 26px;
16
- background-color: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
17
- box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
18
- transition: all 0.3s ease-in-out; pointer-events: auto;
19
- overflow: visible;
20
- }
8
+ .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; }
21
9
  .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; }
22
10
  .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.1); }
23
11
  .fhl-icon-display i { font-size: 22px; }
24
12
  .fhl-list-container .fhl-icon-display { display: inline-flex; width: auto; padding: 0 8px 0 0; border-radius: 20px; background-color: transparent; }
25
13
  .fhl-list-container .fhl-icon-display:hover { background-color: rgba(0, 0, 0, 0.08); }
26
14
  .fhl-list-container .fhl-icon-display:hover .fhl-icon-circle { background-color: transparent; }
27
- .fhl-item-name {
28
- font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden;
29
- max-width: 0; opacity: 0; transition: max-width 0.3s ease-in-out, opacity 0.2s ease-in-out 0.05s;
30
- }
15
+ .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; }
31
16
  .fhl-list-container .fhl-icon-display:hover .fhl-item-name { max-width: 150px; opacity: 1; }
32
17
  .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; }
33
18
  .fhl-icon-circle img { width: 24px; height: 24px; object-fit: cover; border-radius: 50%; }
34
19
  .fhl-icon-bar { display: flex; align-items: center; gap: 8px; width: 100%; transition: opacity 0.3s ease; }
35
- .fhl-list-container {
36
- 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;
37
- 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);
38
- padding-bottom: 30px; margin-bottom: -30px; overflow-y: visible;
39
- }
20
+ .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; }
40
21
  .fhl-list-container::-webkit-scrollbar { display: none; }
41
22
  .fhl-list-container > .fhl-icon-display { scroll-snap-align: center; }
42
23
  .fhl-widget-wrapper.search-mode { height: 280px; align-items: flex-start; border-radius: 20px; }
@@ -57,58 +38,13 @@
57
38
  .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; }
58
39
  .fhl-fixed-item:hover { background-color: rgba(0, 0, 0, 0.08); }
59
40
  .fhl-fixed-item i { font-size: 24px; }
60
- .fhl-item-controls {
61
- position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); display: flex; gap: 4px;
62
- background-color: rgba(0,0,0,0.7); border-radius: 10px; padding: 2px 4px; opacity: 0; visibility: hidden;
63
- transition: all 0.2s ease; pointer-events: none; z-index: 20;
64
- }
41
+ .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; }
65
42
  .fhl-list-container .fhl-icon-display:hover .fhl-item-controls { opacity: 1; visibility: visible; pointer-events: auto; bottom: -13px; }
66
43
  .fhl-control-btn { background: none; border: none; color: white; cursor: pointer; font-size: 16px; line-height: 1; padding: 2px; font-family: monospace; }
67
44
  .fhl-control-btn:hover { color: #007bff; }
68
- .fhl-toast {
69
- position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%) translateY(10px);
70
- background-color: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px 16px; border-radius: 18px;
71
- font-size: 14px; z-index: 1001; opacity: 0; visibility: hidden; pointer-events: none;
72
- transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s;
73
- }
45
+ .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; }
74
46
  .fhl-toast.visible { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
75
- .fhl-language-panel {
76
- position: absolute;
77
- bottom: 100%;
78
- left: 50%;
79
- transform: translateX(-50%);
80
- margin-bottom: 15px;
81
- background-color: rgba(255, 255, 255, 0.95);
82
- backdrop-filter: blur(10px);
83
- -webkit-backdrop-filter: blur(10px);
84
- border-radius: 12px;
85
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
86
- padding: 8px;
87
- display: grid;
88
- grid-template-columns: 1fr 1fr;
89
- gap: 4px;
90
- opacity: 0;
91
- visibility: hidden;
92
- pointer-events: none;
93
- transition: opacity 0.3s ease, transform 0.3s ease;
94
- transform-origin: bottom center;
95
- }
96
- .fhl-language-panel.visible {
97
- opacity: 1;
98
- visibility: visible;
99
- pointer-events: auto;
100
- }
101
- .fhl-language-item {
102
- display: block;
103
- padding: 6px 12px;
104
- font-size: 14px;
105
- color: #333;
106
- text-decoration: none;
107
- border-radius: 8px;
108
- transition: background-color 0.2s ease;
109
- cursor: pointer;
110
- text-align: center;
111
- }
112
- .fhl-language-item:hover {
113
- background-color: rgba(0,0,0,0.1);
114
- }
47
+ .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; }
48
+ .fhl-language-panel.visible { opacity: 1; visibility: visible; pointer-events: auto; }
49
+ .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; }
50
+ .fhl-language-item:hover { background-color: rgba(0,0,0,0.1); }