isaikr 0.0.1

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.
Files changed (51) hide show
  1. package/README.md +35 -0
  2. package/cdn/api.js +19 -0
  3. package/cdn/character.js +254 -0
  4. package/cdn/chat.js +33 -0
  5. package/cdn/code-editor.js +1131 -0
  6. package/cdn/community-compose.js +270 -0
  7. package/cdn/games/2048/index.html +12 -0
  8. package/cdn/games/breakout/index.html +13 -0
  9. package/cdn/games/clicker/index.html +26 -0
  10. package/cdn/games/flappy/index.html +11 -0
  11. package/cdn/games/memory/index.html +34 -0
  12. package/cdn/games/pong/index.html +13 -0
  13. package/cdn/games/reaction/index.html +38 -0
  14. package/cdn/games/runner/index.html +11 -0
  15. package/cdn/games/snake/index.html +11 -0
  16. package/cdn/games/tetris/index.html +14 -0
  17. package/cdn/games/whack/index.html +8 -0
  18. package/cdn/go.js +126 -0
  19. package/cdn/go2.js +127 -0
  20. package/cdn/header3_behavior.js +1167 -0
  21. package/cdn/header3_layout.js +1004 -0
  22. package/cdn/header3_layout.js.bak +1004 -0
  23. package/cdn/header3_style.css +3524 -0
  24. package/cdn/header3_style.css.bak +3514 -0
  25. package/cdn/lang.js +198 -0
  26. package/cdn/loading.js +143 -0
  27. package/cdn/loading2.js +144 -0
  28. package/cdn/local-model.js +2941 -0
  29. package/cdn/main.js +4 -0
  30. package/cdn/main_asset.js +1849 -0
  31. package/cdn/main_asset.js.bak +6999 -0
  32. package/cdn/main_index.css +287 -0
  33. package/cdn/re_board3.css +733 -0
  34. package/cdn/re_board3.js +734 -0
  35. package/cdn/re_chat_tts.js +652 -0
  36. package/cdn/re_local_runtime.js +2246 -0
  37. package/cdn/re_local_runtime.js.bak +2246 -0
  38. package/cdn/re_share.js +577 -0
  39. package/cdn/re_voice.js +542 -0
  40. package/cdn/utils.js +36 -0
  41. package/cdn/view.js +321 -0
  42. package/header3_behavior.js +804 -0
  43. package/header3_layout.js +998 -0
  44. package/header3_style.css +2740 -0
  45. package/index.js +0 -0
  46. package/lang.js +179 -0
  47. package/main_asset.js +2416 -0
  48. package/main_index.css +274 -0
  49. package/package.json +14 -0
  50. package/re_chat_tts.js +1419 -0
  51. package/re_voice.js +430 -0
@@ -0,0 +1,734 @@
1
+ /**
2
+ * RE:BOARD 3.0 - Global Advanced AI Recommendation Engine
3
+ * Feature: Absolute Tier System, Gender Banishment, Context Pattern Matching
4
+ * Default Tab Force: World News + Stable Random Advertisement & Infinite Scroll
5
+ */
6
+
7
+ // =========================================================================
8
+ // [1] 전역 헬퍼 및 DOM 유틸리티
9
+ // =========================================================================
10
+ let macyInstances = { store: null, gallery: null, forum: null, code: null, news: null };
11
+
12
+ function toggleSearchMode(a){const n=document.getElementById('main-nav-bar'),i=document.getElementById('store-search-input');if(n&&i){a?(n.classList.add('search-mode'),setTimeout(()=>i.focus(),50)):(n.classList.remove('search-mode'),i.value='',i.blur(),window.$boardApp&&window.$boardApp.searchQuery!==''&&window.$boardApp.execSearch(''));}}
13
+ function toggleNewsSearchMode(a){const n=document.getElementById('board-news-nav-bar'),i=document.getElementById('news-search-input');if(n&&i){a?(n.classList.add('search-mode'),setTimeout(()=>i.focus(),50)):(n.classList.remove('search-mode'),i.blur());}}
14
+ function toggleCodeSearchMode(a){const n=document.getElementById('board-code-nav-bar'),i=document.getElementById('code-search-input');if(n&&i){a?(n.classList.add('search-mode'),setTimeout(()=>i.focus(),50)):(n.classList.remove('search-mode'),i.blur());}}
15
+ function toggleGallerySearchMode(a){const n=document.getElementById('board-gallery-nav-bar'),i=document.getElementById('gallery-search-input');if(n&&i){a?(n.classList.add('search-mode'),setTimeout(()=>i.focus(),50)):(n.classList.remove('search-mode'),i.blur());}}
16
+ function toggleForumSearchMode(a){const n=document.getElementById('board-forum-nav-bar'),i=document.getElementById('forum-search-input');if(n&&i){a?(n.classList.add('search-mode'),setTimeout(()=>i.focus(),50)):(n.classList.remove('search-mode'),i.blur());}}
17
+ function toggleStoreSearchMode(a){const n=document.getElementById('board-store-nav-bar'),i=document.getElementById('store-search-input');if(n&&i){a?(n.classList.add('search-mode'),setTimeout(()=>i.focus(),50)):(n.classList.remove('search-mode'),i.blur());}}
18
+
19
+ function doSearch(k){if(window.$boardApp)window.$boardApp.execSearch(k);}
20
+ function previewAppIcon(i){if(i.files&&i.files[0]){const r=new FileReader();r.onload=e=>{const pv=document.getElementById('app-icon-preview'),ph=document.getElementById('app-icon-placeholder');if(pv){pv.src=e.target.result;pv.classList.remove('hidden');}if(ph)ph.classList.add('hidden');};r.readAsDataURL(i.files[0]);}}
21
+ async function runApp(id){window.scrollTo({top:0,behavior:'smooth'});if(typeof showToast==='function')showToast("Loading...");if(typeof loadAppDetails==='function')await loadAppDetails(id);}
22
+ window.runApp = runApp;
23
+
24
+ (function initNotice(){const e=document.getElementById('board-spirit-notice');if(!e)return;const n=(window.__RE_BOARD3_CONFIG__&&window.__RE_BOARD3_CONFIG__.boardSpiritNoticeItems)||[];const s=n[Math.floor(Math.random()*n.length)];if(s){if(s.text)e.textContent=s.text;if(s.url)e.dataset.href=s.url;}const g=()=>{if(e.dataset.href)window.location.href=e.dataset.href;};e.addEventListener('click',g);setTimeout(()=>{e.classList.add('show');setTimeout(()=>{e.classList.add('fade-out');setTimeout(()=>{e.classList.remove('show','fade-out');e.style.display='none';},600);},2000);},500);})();
25
+
26
+ function buildGalleryShareContext(item) {
27
+ const url = new URL(window.location.href);
28
+ const shareId = item && (item.share_id || item.id) ? String(item.share_id || item.id) : '';
29
+ if (shareId) url.searchParams.set('v', shareId);
30
+ return {
31
+ url: url.toString(),
32
+ image: String(item && item.image_url ? item.image_url : ''),
33
+ title: String((item && (item.nickname || item.safeIp || item.title || item.prompt)) || 'Gallery image'),
34
+ description: String((item && (item.content || item.prompt || item.title)) || '').trim(),
35
+ source: String((item && (item.nickname || item.safeIp)) || 'User')
36
+ };
37
+ }
38
+ function shareGalleryToSocial(network) {
39
+ const ctx = window.__RE_BOARD3_SHARE_CONTEXT__ || {};
40
+ const pageUrl = encodeURIComponent(ctx.url || window.location.href);
41
+ const title = encodeURIComponent(ctx.title || 'Gallery image');
42
+ const image = encodeURIComponent(ctx.image || '');
43
+ const description = encodeURIComponent(ctx.description || ctx.title || 'Gallery image');
44
+ const shareUrls = {
45
+ x: `https://twitter.com/intent/tweet?url=${pageUrl}&text=${title}`,
46
+ facebook: `https://www.facebook.com/sharer/sharer.php?u=${pageUrl}`,
47
+ pinterest: `https://pinterest.com/pin/create/button/?url=${pageUrl}&media=${image}&description=${description}`,
48
+ tumblr: `https://www.tumblr.com/widgets/share/tool?canonicalUrl=${pageUrl}&title=${title}&caption=${description}&content=${image}`
49
+ };
50
+ const target = shareUrls[String(network || '').toLowerCase()];
51
+ if (!target) return;
52
+ window.open(target, '_blank', 'noopener,noreferrer,width=640,height=720');
53
+ }
54
+ window.shareGalleryToSocial = shareGalleryToSocial;
55
+
56
+ // =========================================================================
57
+ // [2] Alpine.js 애플리케이션
58
+ // =========================================================================
59
+ document.addEventListener('alpine:init', () => {
60
+ Alpine.data('boardApp', () => ({
61
+ curTab: (window.__RE_BOARD3_CONFIG__&&window.__RE_BOARD3_CONFIG__.initialBoardTab)||'news',
62
+ newsCategories: (window.__RE_BOARD3_CONFIG__&&window.__RE_BOARD3_CONFIG__.newsCategories)||[],
63
+
64
+ galleryCategories:[
65
+ {key:'all', name:'All', icon:'ri-apps-line', q:''},
66
+ {key:'anime', name:'Anime', icon:'ri-user-smile-line', q:'anime'},
67
+ {key:'realistic', name:'Realistic', icon:'ri-camera-line', q:'realistic'},
68
+ {key:'girl', name:'Girl', icon:'ri-women-line', q:'girl'},
69
+ {key:'boy', name:'Boy', icon:'ri-men-line', q:'boy'},
70
+ {key:'fantasy', name:'Fantasy', icon:'ri-magic-line', q:'fantasy'},
71
+ {key:'scifi', name:'Sci-Fi', icon:'ri-rocket-line', q:'sci-fi'},
72
+ {key:'cyberpunk', name:'Cyberpunk', icon:'ri-cpu-line', q:'cyberpunk'},
73
+ {key:'landscape', name:'Landscape', icon:'ri-landscape-line', q:'landscape'},
74
+ {key:'animal', name:'Animal', icon:'ri-bear-smile-line', q:'animal'},
75
+ {key:'mecha', name:'Mecha', icon:'ri-robot-2-line', q:'mecha robot'},
76
+ {key:'3d', name:'3D', icon:'ri-box-3-line', q:'3d'},
77
+ {key:'sketch', name:'Sketch', icon:'ri-pencil-line', q:'sketch'}
78
+ ],
79
+ forumCategories:[
80
+ {key:'all', name:'All', icon:'ri-apps-line', q:''},
81
+ {key:'general', name:'General', icon:'ri-chat-1-line', q:'general'},
82
+ {key:'humor', name:'Humor', icon:'ri-emotion-laugh-line', q:'humor'},
83
+ {key:'info', name:'Info', icon:'ri-information-line', q:'info'},
84
+ {key:'review', name:'Review', icon:'ri-star-line', q:'review'},
85
+ {key:'qa', name:'Q&A', icon:'ri-question-answer-line', q:'question'},
86
+ {key:'tip', name:'Tip', icon:'ri-lightbulb-flash-line', q:'tip'},
87
+ {key:'news', name:'News', icon:'ri-newspaper-line', q:'news'},
88
+ {key:'discussion', name:'Discussion', icon:'ri-discuss-line', q:'discussion'},
89
+ {key:'feedback', name:'Feedback', icon:'ri-feedback-line', q:'feedback'},
90
+ {key:'event', name:'Event', icon:'ri-calendar-event-line', q:'event'},
91
+ {key:'notice', name:'Notice', icon:'ri-pushpin-line', q:'notice'},
92
+ {key:'suggestion', name:'Suggestion', icon:'ri-mail-send-line', q:'suggestion'}
93
+ ],
94
+ storeCategories:[
95
+ {key:'all', name:'All', icon:'ri-apps-line', q:''},
96
+ {key:'general', name:'General', icon:'ri-apps-ai-line', q:'general'},
97
+ {key:'character', name:'Character', icon:'ri-chat-ai-line', q:'character'},
98
+ {key:'image', name:'Image', icon:'ri-image-edit-line', q:'image'},
99
+ {key:'writing', name:'Writing', icon:'ri-quill-pen-line', q:'writing'},
100
+ {key:'music', name:'Music', icon:'ri-music-line', q:'music'},
101
+ {key:'video', name:'Video', icon:'ri-video-ai-line', q:'video'},
102
+ {key:'code', name:'Code', icon:'ri-code-box-line', q:'code'},
103
+ {key:'game', name:'Game', icon:'ri-gamepad-line', q:'game'},
104
+ {key:'finance', name:'Finance', icon:'ri-money-dollar-circle-line', q:'finance'},
105
+ {key:'education', name:'Education', icon:'ri-book-open-line', q:'education'},
106
+ {key:'design', name:'Design', icon:'ri-brush-line', q:'design'},
107
+ {key:'tool', name:'Tool', icon:'ri-tools-line', q:'tool'}
108
+ ],
109
+ codeCategories:[
110
+ {key:'all', name:'All', icon:'ri-apps-line', q:''},
111
+ {key:'frontend', name:'Frontend', icon:'ri-layout-masonry-line', q:'frontend'},
112
+ {key:'backend', name:'Backend', icon:'ri-server-line', q:'backend'},
113
+ {key:'ai', name:'AI', icon:'ri-brain-line', q:'ai'},
114
+ {key:'database', name:'Database', icon:'ri-database-2-line', q:'database'},
115
+ {key:'script', name:'Script', icon:'ri-file-code-line', q:'script'},
116
+ {key:'tool', name:'Tool', icon:'ri-tools-line', q:'tool'},
117
+ {key:'snippet', name:'Snippet', icon:'ri-scissors-cut-line', q:'snippet'},
118
+ {key:'library', name:'Library', icon:'ri-book-3-line', q:'library'},
119
+ {key:'framework', name:'Framework', icon:'ri-layout-4-line', q:'framework'},
120
+ {key:'game', name:'Game', icon:'ri-gamepad-line', q:'game'},
121
+ {key:'config', name:'Config', icon:'ri-settings-4-line', q:'config'},
122
+ {key:'other', name:'Other', icon:'ri-more-2-line', q:'other'}
123
+ ],
124
+
125
+ newsQuery: '',
126
+ selectedNewsCategoryQ: '',
127
+ hasInitialNewsSearch: !!(window.__RE_BOARD3_CONFIG__&&window.__RE_BOARD3_CONFIG__.hasInitialNewsSearch),
128
+
129
+ marketMode: '', selectedMarketSymbol: '', marketHeatmapActive: false,
130
+ marketHeatmapLocale: (window.__RE_BOARD3_CONFIG__&&window.__RE_BOARD3_CONFIG__.marketHeatmapLocale)||'en',
131
+ cryptoHeatmapLocale: 'en', marketHeatmapSource: 'SPX500',
132
+ marketSymbolCatalog: {
133
+ stocks:[{name:'AAPL',symbol:'NASDAQ:AAPL'},{name:'MSFT',symbol:'NASDAQ:MSFT'},{name:'AMZN',symbol:'NASDAQ:AMZN'},{name:'GOOGL',symbol:'NASDAQ:GOOGL'},{name:'NVDA',symbol:'NASDAQ:NVDA'},{name:'META',symbol:'NASDAQ:META'},{name:'TSLA',symbol:'NASDAQ:TSLA'}],
134
+ gold:[{name:'XAUUSD',symbol:'OANDA:XAUUSD'},{name:'XAGUSD',symbol:'OANDA:XAGUSD'},{name:'GLD',symbol:'AMEX:GLD'}],
135
+ crypto:[{name:'BTCUSDT',symbol:'BINANCE:BTCUSDT'},{name:'ETHUSDT',symbol:'BINANCE:ETHUSDT'},{name:'SOLUSDT',symbol:'BINANCE:SOLUSDT'},{name:'XRPUSDT',symbol:'BINANCE:XRPUSDT'},{name:'DOGEUSDT',symbol:'BINANCE:DOGEUSDT'}],
136
+ commodities:[{name:'USOIL',symbol:'TVC:USOIL'},{name:'UKOIL',symbol:'TVC:UKOIL'},{name:'NATGAS',symbol:'NYMEX:NG1!'}]
137
+ },
138
+
139
+ selectedCodeCategory: 'all', selectedGalleryCategory: 'all', selectedForumCategory: 'all', selectedStoreCategory: 'all',
140
+ currentTag: new URLSearchParams(window.location.search).get('tag')||'',
141
+ searchQuery: '', storeQuery: '', galleryQuery: '', forumQuery: '', codeQuery: '',
142
+
143
+ items: { widgets:[], store:[], gallery:[], forum:[], code:[], news:[], products:[] },
144
+ page: { store:1, gallery:1, forum:1, code:1, news:1 },
145
+ loading: { store:false, gallery:false, forum:false, code:false, news:false },
146
+ end: { store:false, gallery:false, forum:false, code:false, news:false },
147
+ warmupDone: { store:false, gallery:false, forum:false, code:false, news:false },
148
+ initialInterestSortDone: { store:false, gallery:false, forum:false, code:false, news:false },
149
+
150
+ interestProfile: {}, productProfile: {}, marketProfile: {}, itemProfile: {},
151
+ galleryModal: { open: false, item: null },
152
+
153
+ decodeEscapedText(val) {
154
+ if(typeof val!=='string'||!val)return val||''; if(!val.includes('\\u'))return val;
155
+ let v=val.replace(/\\\\u/g,'\\u').replace(/\\u([0-9a-fA-F]{4})/g,(_,x)=>String.fromCharCode(parseInt(x,16)));
156
+ return v.replace(/\\n/g,'\n').replace(/\\"/g,'"').replace(/\\\\/g,'\\');
157
+ },
158
+ normalizeCategoryStrings() {
159
+ if(Array.isArray(this.newsCategories)) this.newsCategories=this.newsCategories.map(c=>({...c,name:this.decodeEscapedText(c.name),q:this.decodeEscapedText(c.q)}));
160
+ if(Array.isArray(this.codeCategories)) this.codeCategories=this.codeCategories.map(c=>({...c,name:this.decodeEscapedText(c.name)}));
161
+ },
162
+
163
+ // --- Market Widget ---
164
+ getNewsMarketMode(q) {
165
+ q=String(q||'').toLowerCase(); if(!q)return'';
166
+ if(/(crypto|bitcoin|btc|ethereum|eth|xrp|sol|doge|코인|가상화폐|암호화폐)/i.test(q))return'crypto';
167
+ if(/(금|골드|은|xau|gold|xag|silver)/i.test(q))return'gold';
168
+ if(/(원자재|원유|wti|brent|천연가스|구리|oil|gas|copper)/i.test(q))return'commodities';
169
+ if(/(주식|증시|코스피|나스닥|s&p|sp500|stock|market|nasdaq|aapl|nvda|msft|tsla)/i.test(q))return'stocks';
170
+ return '';
171
+ },
172
+ syncMarketWidgetMode(fReset=false) {
173
+ const pM=this.marketMode; this.marketMode=this.getNewsMarketMode(this.newsQuery||this.selectedNewsCategoryQ);
174
+ if(!this.marketMode){ this.selectedMarketSymbol=''; this.marketHeatmapActive=false; return; }
175
+ let syms=this.marketSymbolCatalog[this.marketMode]||[];
176
+ if(!this.supportsHeatmapMode()) this.marketHeatmapActive=false;
177
+ if(fReset||!syms.some(s=>s.symbol===this.selectedMarketSymbol)){
178
+ const sortedSyms = syms.slice().sort((a,b) => (this.marketProfile[b.symbol]||0) - (this.marketProfile[a.symbol]||0));
179
+ this.selectedMarketSymbol=sortedSyms.length?sortedSyms[0].symbol:'';
180
+ }
181
+ if(this.supportsHeatmapMode()){ this.marketHeatmapActive=true; this.$nextTick(()=>this.renderMarketHeatmapWidget(fReset||pM!==this.marketMode)); }
182
+ },
183
+ shouldShowMarketWidget(){ return false; },
184
+ supportsHeatmapMode(m=null){ return (m||this.marketMode)==='stocks'||(m||this.marketMode)==='crypto'; },
185
+ shouldShowHeatmapWidget(){ return this.supportsHeatmapMode()&&this.marketHeatmapActive; },
186
+ getActiveMarketSymbols(){ return this.marketSymbolCatalog[this.marketMode]||[]; },
187
+ getMarketSymbolToken(s){ return String(s?.name||'').slice(0,3).toUpperCase() || 'TV'; },
188
+ getMarketSymbolAvatar(s){ const t=this.getMarketSymbolToken(s); return `https://ui-avatars.com/api/?name=${encodeURIComponent(t)}&background=111827&color=fff&bold=true&format=svg&size=64`; },
189
+ showMarketHeatmap(){ if(this.supportsHeatmapMode()){ this.marketHeatmapActive=true; this.$nextTick(()=>this.renderMarketHeatmapWidget(true)); } },
190
+ setMarketSymbol(s){
191
+ if(s){
192
+ this.selectedMarketSymbol=s; if(this.supportsHeatmapMode())this.marketHeatmapActive=false;
193
+ this.marketProfile[s] = Math.min(1000, (this.marketProfile[s] || 0) + 1);
194
+ localStorage.setItem('isai_market_profile_v1', JSON.stringify(this.marketProfile));
195
+ }
196
+ },
197
+ isActiveMarketSymbol(s){ return !this.marketHeatmapActive&&this.selectedMarketSymbol===s; },
198
+ renderMarketHeatmapWidget(f=false){
199
+ if(!this.supportsHeatmapMode())return; const h=this.$refs.marketHeatmapWidget, m=String(this.marketMode||''); if(!h||(!f&&h.dataset.loaded==='1'&&h.dataset.mode===m))return;
200
+ h.innerHTML=''; h.dataset.loaded='0'; h.dataset.mode=m;
201
+ const iframe = document.createElement('iframe');
202
+ const params = new URLSearchParams({
203
+ mode: 'heatmap',
204
+ marketMode: m,
205
+ locale: this.marketHeatmapLocale || (m === 'crypto' ? this.cryptoHeatmapLocale || 'en' : 'en'),
206
+ source: this.marketHeatmapSource || 'SPX500'
207
+ });
208
+ iframe.src = `/tradingview_frame.php?${params.toString()}`;
209
+ iframe.loading = 'lazy';
210
+ iframe.title = 'TradingView heatmap';
211
+ iframe.referrerPolicy = 'no-referrer-when-downgrade';
212
+ iframe.style.width = '100%';
213
+ iframe.style.height = '100%';
214
+ iframe.style.minHeight = '100%';
215
+ iframe.style.border = '0';
216
+ iframe.style.display = 'block';
217
+ iframe.onload=()=>{h.dataset.loaded='1'; h.dataset.mode=m; this.$nextTick(()=>this.fixMarketHeatmapHeight());};
218
+ h.appendChild(iframe);
219
+ setTimeout(()=>this.fixMarketHeatmapHeight(), 600);
220
+ setTimeout(()=>this.fixMarketHeatmapHeight(), 1600);
221
+ },
222
+ fixMarketHeatmapHeight(){
223
+ const h=this.$refs.marketHeatmapWidget;
224
+ if(!h)return;
225
+ h.querySelectorAll('.tradingview-widget-container,.tradingview-widget-container__widget,iframe').forEach(el=>{
226
+ el.style.width='100%';
227
+ el.style.height='100%';
228
+ el.style.minHeight='100%';
229
+ el.style.display='block';
230
+ });
231
+ },
232
+ getTradingViewChartUrl(){ const fb={stocks:'SP:SPX',gold:'OANDA:XAUUSD',crypto:'BINANCE:BTCUSDT',commodities:'TVC:USOIL'}[this.marketMode]||'SP:SPX'; const params=new URLSearchParams({mode:'chart',marketMode:this.marketMode||'stocks',symbol:this.selectedMarketSymbol||fb}); return `/tradingview_frame.php?${params.toString()}`; },
233
+
234
+ // =========================================================================
235
+ // [강력 최적화] AI 절대 티어(Tier) 시스템
236
+ // =========================================================================
237
+ calculateItemTier(tb, i, targetKey) {
238
+ if (!targetKey || targetKey === 'all') return 2;
239
+
240
+ const itemText = String(`${i?.title||''} ${i?.cleanTitle||''} ${i?.content||''} ${i?.snippet||''} ${i?.prompt||''} ${i?.keywords||''} ${i?.listText||''} ${i?.category||''}`).toLowerCase();
241
+
242
+ let isMatch = i?.category ? i.category.toLowerCase().includes(targetKey) : false;
243
+ let isConflict = false;
244
+
245
+ const regexes = {
246
+ boy: /\b(1boy|boys?|man|men|male|guy|handsome|chico|ni[ñn]o|hombre|var[oó]n|masculino|guapo)\b|(남자|소년|남성|남캐|男|男子|少年|男性|イケメン|लड़का|आदमी|पुरुष)/i,
247
+ girl: /\b(1girl|girls?|woman|women|female|lady|beautiful|maid|waifu|chica|ni[ñn]a|mujer|hembra|femenina|hermosa|linda)\b|(여자|소녀|여성|여캐|女|女子|少女|女性|美少女|メイド|かわいい|लड़की|महिला|औरत|सुंदर|प्यारी)/i,
248
+ anime: /\b(anime|manga|toon|waifu|2d|illustration|dibujo animado)\b|(애니|만화|일러스트|アニメ|マンガ|イラスト|二次元|एनिमे|मंगा|कार्टून)/i,
249
+ realistic: /\b(realistic|photo|cinematic|real|raw|photorealistic|realista|fotograf[ií]a)\b|(실사|사진|포토|리얼|リアル|写真|実写|シネマティック|यथार्थवादी|फोटो|असली)/i,
250
+ fantasy: /\b(fantasy|magic|dragon|elf|fantas[ií]a)\b|(판타지|마법|엘프|ファンタジー|魔法|कल्पना|जादू)/i,
251
+ scifi: /\b(sci-fi|cyberpunk|space|robot|mecha)\b|(sf|사이버펑크|우주|메카|로봇|サイバーパンク|宇宙|ロボット|अंतरिक्ष|रोबोट)/i,
252
+ landscape: /\b(landscape|scenery|background|nature|paisaje|naturaleza)\b|(풍경|배경|자연|風景|背景|自然|परिदृश्य|प्रकृति)/i,
253
+ cyberpunk: /\b(cyberpunk|neon|synthwave|futuristic|ciberpunk)\b|(사이버펑크|네온|미래|サイバーパンク|ネオン|미래도시)/i,
254
+ animal: /\b(animal|pet|cat|dog|bird|animales)\b|(동물|고양이|강아지|개|새|펫|動物|ペット|猫|犬|जानवर|पालतू)/i,
255
+ mecha: /\b(mecha|robot|cyborg|meca)\b|(메카|로봇|사이보그|メカ|ロボット|サイボーグ|मेचा|रोबोट)/i,
256
+ '3d': /\b(3d|render|octane|unreal|blender|c4d)\b|(3디|렌더링|3DCG)/i,
257
+ sketch: /\b(sketch|drawing|pencil|boceto|dibujo)\b|(스케치|드로잉|연필|데생|スケッチ|デッサン|鉛筆|स्केच|ड्राइंग)/i,
258
+ qa: /\b(qna|q&a|how to|help|question|pregunta|ayuda|duda)\b|\?|(질문|도움|어떻게|궁금|알려|質問|助けて|教えて|प्रश्न|सवाल|मदद)/i,
259
+ humor: /\b(humor|funny|meme|lol|haha|gracioso|jaja|divertido)\b|(유머|웃긴|밈|ㅋㅋㅋ|ㅎㅎㅎ|재밌|폭소|ユーモア|面白い|笑|草|www|हास्य|मज़ाकिया|मीम)/i,
260
+ review: /\b(review|evaluate|recommend|bought|rese[ñn]a|opini[oó]n)\b|(리뷰|후기|평가|추천|사용기|써본|샀|구매|장단점|レビュー|感想|評価|おすすめ|購入|समीक्षा|राय)/i,
261
+ info: /\b(info|tip|guide|tutorial|consejo|gu[ií]a)\b|(팁|정보|지식|꿀팁|알아보기|방법|가이드|정리|情報|ヒント|ガイド|コツ|जानकारी|सुझाव)/i,
262
+ discussion: /\b(discussion|debate|opinion|discusión)\b|(토론|의견|생각|논의|議論|意見|चर्चा|बहस)/i,
263
+ news: /\b(news|article|press|journal|noticias)\b|(뉴스|기사|보도|소식|속보|ニュース|記事|報道|समाचार)/i,
264
+ feedback: /\b(feedback|report|comentarios)\b|(피드백|건의|리포트|오류|피드백|フィードバック|보고|प्रतिक्रिया)/i,
265
+ event: /\b(event|contest|giveaway|evento)\b|(이벤트|행사|대회|참여|공모전|イベント|コンテスト|행사|आयोजन)/i,
266
+ notice: /\b(notice|announcement|update|aviso)\b|(공지|안내|업데이트|공지사항|알림|お知らせ|通知|सूचना)/i,
267
+ suggestion: /\b(suggest|idea|proposal|sugerencia)\b|(건의|제안|아이디어|건의사항|提案|アイデア|सुझाव)/i,
268
+ character: /\b(character|bot|ai_chat|chat|persona|personaje)\b|(챗봇|캐릭터|페르소나|대화|キャラクター|ボット|チャット|चरित्र|बॉट|चैट)/i,
269
+ image: /\b(image|art|photo|draw|generate|imagen|arte)\b|(이미지|그림|사진|생성|합성|画像|アート|描く|छवि|चित्र)/i,
270
+ writing: /\b(write|blog|story|novel|text|escritura)\b|(글쓰기|블로그|소설|작성|글|스토리|執筆|ブログ|小説|लिखना|कहानी)/i,
271
+ music: /\b(music|audio|song|sound|suno|m[uú]sica)\b|(음악|노래|오디오|사운드|수노|音楽|曲|オーディオ|संगीत|गाना)/i,
272
+ video: /\b(video|motion|animate|v[ií]deo)\b|(비디오|영상|동영상|모션|애니메이트|ビデオ|動画|वीडियो)/i,
273
+ education: /\b(edu|learn|study|school|educaci[oó]n)\b|(교육|공부|학습|강의|배우기|학교|教育|学習|学ぶ|शिक्षा|अध्ययन)/i,
274
+ design: /\b(design|ui|ux|layout|dise[ñn]o)\b|(디자인|디자이너|레이아웃|꾸미기|デザイン|レイアウト|डिज़ाइन)/i,
275
+ code: /\b(code|dev|script|program|c[oó]digo|desarrollo)\b|(개발|코드|프로그래밍|코드생성|코딩|コード|開発|スクリプト|プログラム|कोड|विकास)/i,
276
+ database: /\b(db|sql|mysql|nosql|mongo|postgres|database)\b|(데이터베이스|디비|데이터|データベース|डेटाबेस)/i,
277
+ library: /\b(lib|library|package|npm|pip|librer[ií]a)\b|(라이브러리|패키지|모듈|ライブラリ|パッケージ|पुस्तकालय)/i,
278
+ framework: /\b(framework|react|vue|angular|django|spring|laravel)\b|(프레임워크|프레임웍|フレームワーク|फ्रेमवर्क)/i,
279
+ config: /\b(config|setup|setting|json|yaml|env|configuraci[oó]n)\b|(설정|환경|세팅|구성|設定|コンフィグ|सेटिंग)/i,
280
+ finance: /\b(finance|money|stock|crypto|finanzas)\b|(금융|주식|돈|코인|경제|비트코인|증시|金融|株|仮想通貨|वित्त|पैसा)/i,
281
+ game: /\b(game|play|gaming|juego)\b|(게임|오락|플레이|ゲーム|プレイ|खेल)/i,
282
+ tool: /\b(tool|utility|herramienta|utilidad)\b|(도구|유틸|툴|ツール|ユーティリティ|उपकरण)/i,
283
+ other: /\b(other|misc|etc|otros)\b|(기타|잡동사니|그외|その他|기타등등|अन्य)/i
284
+ };
285
+
286
+ if (regexes[targetKey] && regexes[targetKey].test(itemText)) isMatch = true;
287
+
288
+ if (tb === 'gallery') {
289
+ const hasBoy = regexes.boy.test(itemText);
290
+ const hasGirl = regexes.girl.test(itemText);
291
+ if (targetKey === 'boy' && hasGirl) isConflict = true;
292
+ if (targetKey === 'girl' && hasBoy) isConflict = true;
293
+ }
294
+
295
+ if (isConflict) return 0;
296
+ if (isMatch) return 2;
297
+ return 1;
298
+ },
299
+
300
+ scoreByInterestBase(tb, i) {
301
+ let s = 0;
302
+ const itemText = String(`${i?.title||''} ${i?.cleanTitle||''} ${i?.content||''} ${i?.snippet||''} ${i?.listText||''} ${i?.prompt||''} ${i?.category||''} ${i?.keywords||''} ${i?.description||''} ${i?.source||''}`).toLowerCase();
303
+ Object.keys(this.interestProfile).forEach(k => {
304
+ if(k.startsWith('kw_') && itemText.includes(k.replace('kw_',''))) {
305
+ s += (this.interestProfile[k] * 10);
306
+ }
307
+ });
308
+ const uid = i?.id || i?.link;
309
+ if (uid && this.itemProfile[`${tb}_${uid}`]) {
310
+ s += (Number(this.itemProfile[`${tb}_${uid}`]) * 100);
311
+ }
312
+ return s;
313
+ },
314
+
315
+ getInterestSortTargetKey(tb) {
316
+ if (tb === 'gallery') return this.selectedGalleryCategory || 'all';
317
+ if (tb === 'forum') return this.selectedForumCategory || 'all';
318
+ if (tb === 'store') return this.selectedStoreCategory || 'all';
319
+ if (tb === 'code') return this.selectedCodeCategory || 'all';
320
+ if (tb === 'news') return this.selectedNewsCategoryQ || '';
321
+ return 'all';
322
+ },
323
+
324
+ sortItemsByInterest(tb, l) {
325
+ if (!Array.isArray(l) || l.length < 2) return l;
326
+
327
+ const targetKey = this.getInterestSortTargetKey(tb);
328
+ return [...l].sort((a, b) => {
329
+ const tierA = this.calculateItemTier(tb, a, targetKey);
330
+ const tierB = this.calculateItemTier(tb, b, targetKey);
331
+ if (tierA !== tierB) return tierB - tierA;
332
+
333
+ const scoreA = this.scoreByInterestBase(tb, a) + Number(a?.sort_order || 0);
334
+ const scoreB = this.scoreByInterestBase(tb, b) + Number(b?.sort_order || 0);
335
+ if (scoreA !== scoreB) return scoreB - scoreA;
336
+
337
+ const timeA = Date.parse(a?.date || a?.created_at || '') || 0;
338
+ const timeB = Date.parse(b?.date || b?.created_at || '') || 0;
339
+ if (timeA !== timeB) return timeB - timeA;
340
+
341
+ const getId = x => x?.id ? Number(x.id) : (x?.link ? parseInt(String(x.link).replace(/\D/g, '').slice(0,8)) : 0);
342
+ return getId(b) - getId(a);
343
+ });
344
+ },
345
+
346
+ applyInterestSort(tb){
347
+ if(['store','code','forum','gallery','products'].includes(tb)) {
348
+ this.items[tb] = [...this.sortItemsByInterest(tb, this.items[tb])];
349
+ }
350
+ },
351
+
352
+ trackItem(tb, i){
353
+ if(!i || (!i.id && !i.link)) return;
354
+ const uid = i.id || i.link;
355
+ const k = `${tb}_${uid}`;
356
+ this.itemProfile[k] = Math.min(1000, (this.itemProfile[k] || 0) + 1);
357
+ localStorage.setItem('isai_item_profile_v1', JSON.stringify(this.itemProfile));
358
+ setTimeout(() => { if (this.items[tb]?.length) { this.updateMacy(tb); } }, 300);
359
+ },
360
+ trackInterest(tb, i, w=1){
361
+ const text = String(`${i?.title||''} ${i?.cleanTitle||''} ${i?.content||''} ${i?.snippet||''} ${i?.listText||''} ${i?.prompt||''} ${i?.category||''} ${i?.keywords||''} ${i?.description||''} ${i?.source||''}`).toLowerCase();
362
+ const words = text.replace(/[^a-z0-9가-힣_]+/g, ' ').split(/\s+/).filter(x=>x.length>1).slice(0,10);
363
+ let u = false;
364
+ words.forEach(k => {
365
+ const pKey = `kw_${k}`;
366
+ this.interestProfile[pKey] = Math.min(500, Math.max(0, (this.interestProfile[pKey]||0) + w));
367
+ u = true;
368
+ });
369
+ if(u){
370
+ localStorage.setItem('isai_interest_profile_v1',JSON.stringify(this.interestProfile));
371
+ clearTimeout(this.sponsorSearchTimer);
372
+ this.sponsorSearchTimer = setTimeout(() => this.loadSearchProducts?.(), 250);
373
+ }
374
+ },
375
+
376
+ productPriceLabel(i){ const cur=String(i?.currency||'KRW').toUpperCase(); const n=Number(i?.price||0); return cur==='USD'?'$'+n.toLocaleString(undefined,{maximumFractionDigits:2}):Math.round(n).toLocaleString()+'원'; },
377
+ scoreProduct(i){ return this.scoreByInterestBase('products',i)+(Number(this.productProfile[i?.id]||0)*100)+(Number(i?.sort_order||0)); },
378
+
379
+ isProductSearchMode() {
380
+ // 카테고리 클릭이 아닌, 실제 검색어가 입력된 상태인지 탭별로 정확히 구분
381
+ if (this.curTab === 'news') return !!this.newsQuery && this.newsQuery !== this.selectedNewsCategoryQ;
382
+ if (this.curTab === 'store') return !!this.storeQuery;
383
+ if (this.curTab === 'forum') return !!this.forumQuery;
384
+ if (this.curTab === 'code') return !!this.codeQuery;
385
+ if (this.curTab === 'gallery') return !!this.galleryQuery;
386
+ return !!this.searchQuery;
387
+ },
388
+ getVisibleProducts(){
389
+ const limit = this.isProductSearchMode() ? 24 : 3;
390
+ return Array.isArray(this.items.products)
391
+ ? [...this.items.products]
392
+ .sort((a, b) => {
393
+ const timeA = Date.parse(a?.created_at || a?.date || '') || 0;
394
+ const timeB = Date.parse(b?.created_at || b?.date || '') || 0;
395
+ if (timeA !== timeB) return timeB - timeA;
396
+ return Number(b?.id || 0) - Number(a?.id || 0);
397
+ })
398
+ .slice(0, limit)
399
+ : [];
400
+ },
401
+ getVisibleNewsItems(){ return Array.isArray(this.items.news) ? [...this.items.news].sort((a,b)=> (Date.parse(b?.date || b?.created_at || '') || 0) - (Date.parse(a?.date || a?.created_at || '') || 0) || ((Number(b?.id||0) || 0) - (Number(a?.id||0) || 0))) :[]; },
402
+
403
+ getMixedItems(tb) {
404
+ const visibleItems = tb === 'news' ? this.getVisibleNewsItems() : (this.items[tb] || []);
405
+ if (!visibleItems.length) return [];
406
+
407
+ const original = visibleItems.map((item, idx) => ({type: tb, item, key: `${tb}-${item.id || item.link || idx}`}));
408
+ const products = this.getVisibleProducts().map((item) => ({type: 'product', item, key: `product-${item.id}`}));
409
+
410
+ if (!products.length) return original;
411
+
412
+ const out = [];
413
+ let pQueue = [...products];
414
+ const isSearch = this.isProductSearchMode();
415
+
416
+ // 시드 기반 난수 생성 (화면 깜빡임 완벽 방지)
417
+ const getSeed = (str) => {
418
+ let h = 0; for(let i=0; i<str.length; i++) h = Math.imul(31, h) + str.charCodeAt(i) | 0; return Math.abs(h);
419
+ };
420
+
421
+ original.forEach((entry, idx) => {
422
+ out.push(entry);
423
+ let seed = getSeed(entry.key);
424
+ let insertCount = 0;
425
+
426
+ if (isSearch) {
427
+ // [검색 모드] 상품이 최대 33개이므로 일반 게시물 사이마다 1~2개씩 촘촘하게 섞음
428
+ insertCount = (seed % 10) < 4 ? 2 : 1; // 40% 확률로 2개, 60% 확률로 1개 삽입
429
+ } else {
430
+ // [일반 모드] 기본 광고 3개이므로 2, 6, 11번째 위치와 드물게(30%) 1개씩 삽입
431
+ let forceInsert = (idx === 2 || idx === 6 || idx === 11);
432
+ if (forceInsert || (idx >= 1 && (seed % 10) < 3)) {
433
+ insertCount = 1;
434
+ }
435
+ }
436
+
437
+ // 큐에 상품이 남아있고 넣어야 할 카운트가 있으면 빼서 삽입
438
+ while (pQueue.length > 0 && insertCount > 0) {
439
+ out.push(pQueue.shift());
440
+ insertCount--;
441
+ }
442
+ });
443
+
444
+ // 위에서 다 섞지 못하고 남은 상품/광고가 있다면 맨 뒤에 추가
445
+ while(pQueue.length > 0) {
446
+ out.push(pQueue.shift());
447
+ }
448
+ return out;
449
+ },
450
+
451
+ setGalleryCat(c){
452
+ this.selectedGalleryCategory = c.key;
453
+ const hadSearchQuery = !!String(this.galleryQuery || '').trim();
454
+ this.galleryQuery = '';
455
+ document.getElementById('gallery-search-input').value='';
456
+ toggleGallerySearchMode(false);
457
+
458
+ if (hadSearchQuery || !this.items.gallery.length) {
459
+ this.page.gallery=1;
460
+ this.end.gallery=false;
461
+ this.items.gallery=[];
462
+ this.loadData('gallery');
463
+ } else {
464
+ this.applyInterestSort('gallery');
465
+ this.updateMacy('gallery');
466
+ }
467
+ this.loadSearchProducts();
468
+ },
469
+ submitGallerySearch(k){ this.galleryQuery=String(k||'').trim(); this.selectedGalleryCategory=''; this.items.gallery=[]; this.page.gallery=1; this.end.gallery=false; this.loadData('gallery'); this.loadSearchProducts(); },
470
+ resetGalleryCategory(){ this.selectedGalleryCategory='all'; this.galleryQuery=''; document.getElementById('gallery-search-input').value=''; toggleGallerySearchMode(false); this.items.gallery=[]; this.page.gallery=1; this.end.gallery=false; this.loadData('gallery'); this.loadSearchProducts(); },
471
+
472
+ setForumCat(c){
473
+ this.selectedForumCategory = c.key; this.forumQuery = '';
474
+ document.getElementById('forum-search-input').value=''; toggleForumSearchMode(false);
475
+ this.page.forum=1; this.end.forum=false; this.items.forum=[];
476
+ this.applyInterestSort('forum');
477
+ this.updateMacy('forum');
478
+ this.loadData('forum'); this.loadSearchProducts();
479
+ },
480
+ submitForumSearch(k){ this.forumQuery=String(k||'').trim(); this.selectedForumCategory=''; this.items.forum=[]; this.page.forum=1; this.end.forum=false; this.loadData('forum'); this.loadSearchProducts(); },
481
+ resetForumCategory(){ this.selectedForumCategory='all'; this.forumQuery=''; document.getElementById('forum-search-input').value=''; toggleForumSearchMode(false); this.items.forum=[]; this.page.forum=1; this.end.forum=false; this.loadData('forum'); this.loadSearchProducts(); },
482
+
483
+ setStoreCat(c){
484
+ this.selectedStoreCategory = c.key; this.storeQuery = '';
485
+ document.getElementById('store-search-input').value=''; toggleStoreSearchMode(false);
486
+ this.page.store=1; this.end.store=false; this.items.store=[];
487
+ this.applyInterestSort('store');
488
+ this.updateMacy('store');
489
+ this.loadData('store'); this.loadSearchProducts();
490
+ },
491
+ submitStoreSearch(k){ this.storeQuery=String(k||'').trim(); this.selectedStoreCategory=''; this.items.store=[]; this.page.store=1; this.end.store=false; this.loadData('store'); this.loadSearchProducts(); },
492
+ resetStoreCategory(){ this.selectedStoreCategory='all'; this.storeQuery=''; document.getElementById('store-search-input').value=''; toggleStoreSearchMode(false); this.items.store=[]; this.page.store=1; this.end.store=false; this.loadData('store'); this.loadSearchProducts(); },
493
+
494
+ setNewsCat(c){
495
+ this.selectedNewsCategoryQ = this.newsQuery = c.q;
496
+ this.syncMarketWidgetMode(true); window.history.replaceState({},'','/');
497
+ this.items.news =[]; this.items.products =[];
498
+ this.updateMacy('news');
499
+ document.getElementById('news-search-input').value=''; toggleNewsSearchMode(false);
500
+ this.page.news=1; this.end.news=false; this.loadData('news'); this.loadSearchProducts();
501
+ },
502
+ submitNewsSearch(k){ k=String(k||'').trim(); if(!k)return this.resetNewsCategory(); this.selectedNewsCategoryQ=''; this.newsQuery=k; this.syncMarketWidgetMode(true); window.history.replaceState({},'',`/search/${encodeURIComponent(k)}`); this.items.news=[]; this.items.products=[]; this.page.news=1; this.end.news=false; toggleNewsSearchMode(false); this.loadData('news'); this.loadSearchProducts(); },
503
+ resetNewsCategory(){ if(this.newsCategories.length)this.setNewsCat(this.newsCategories[0]); },
504
+
505
+ setCodeCat(c){
506
+ this.selectedCodeCategory = c.key; this.codeQuery = '';
507
+ document.getElementById('code-search-input').value=''; toggleCodeSearchMode(false);
508
+ this.page.code=1; this.end.code=false; this.items.code=[];
509
+ this.applyInterestSort('code');
510
+ this.updateMacy('code');
511
+ this.loadData('code'); this.loadSearchProducts();
512
+ },
513
+ submitCodeSearch(k){ this.codeQuery=String(k||'').trim(); this.selectedCodeCategory=''; this.items.code=[]; this.page.code=1; this.end.code=false; this.loadData('code'); this.loadSearchProducts(); },
514
+ resetCodeCategory(){ this.selectedCodeCategory='all'; this.codeQuery=''; document.getElementById('code-search-input').value=''; toggleCodeSearchMode(false); this.items.code=[]; this.page.code=1; this.end.code=false; this.loadData('code'); this.loadSearchProducts(); },
515
+
516
+ openStoreItem(i){ if(i){ this.trackInterest('store', i); this.trackItem('store', i); const appId = i.id || i.app_id || i.appId; if(appId && typeof window.runApp==='function') window.runApp(appId); } },
517
+ openForumItem(i){ if(i){ this.trackInterest('forum', i); this.trackItem('forum', i); window.location.href='https://isai.kr/view/'+i.id; } },
518
+ openNewsGate(i){ if(i?.link){ this.trackInterest('news', i); this.trackItem('news', i); window.location.href=`/news_gate/?u=${encodeURIComponent(i.link)}&t=${encodeURIComponent(i.cleanTitle||i.title||'News')}&s=${encodeURIComponent(i.snippet||'')}`; } },
519
+ openProduct(i){ if(i){ this.trackInterest('products', i); this.trackItem('products', i); if(i.link_url) window.open(i.link_url,'_blank','noopener'); } },
520
+ async openCodeItem(i){ if(!i)return; this.trackInterest('code', i); this.trackItem('code', i); if(i.id){ window.location.href='/play_run/?id='+i.id; return; } if(i.run_url)window.open(i.run_url,'_blank','noopener'); else if(i.gist_url)window.open(i.gist_url,'_blank','noopener'); },
521
+ openCodeComposer(){ window.scrollTo({top:0,behavior:'smooth'}); if(typeof window.setMode==='function')window.setMode('code'); },
522
+
523
+ openGalleryItem(i){
524
+ if(i){
525
+ this.trackInterest('gallery', i);
526
+ this.trackItem('gallery', i);
527
+ fetch('re_store.php?action=increase_view&id='+i.id).catch(e=>{});
528
+ this.galleryModal.item = i; this.galleryModal.open = true;
529
+ window.__RE_BOARD3_SHARE_CONTEXT__ = buildGalleryShareContext(i);
530
+ const nextUrl = new URL(window.location.href); nextUrl.searchParams.set('v', i.share_id || i.id); window.history.pushState({}, '', nextUrl.toString());
531
+ }
532
+ },
533
+ closeGalleryModal() {
534
+ this.galleryModal.open = false; setTimeout(() => { this.galleryModal.item = null; }, 300);
535
+ window.__RE_BOARD3_SHARE_CONTEXT__ = null;
536
+ const nextUrl = new URL(window.location.href); nextUrl.searchParams.delete('v'); window.history.pushState({}, '', nextUrl.pathname + nextUrl.search);
537
+ },
538
+
539
+ async openCharacterChat(i) {
540
+ if(!i?.image_url) return;
541
+ this.trackInterest('gallery', i);
542
+ this.trackItem('gallery', i);
543
+ this.closeGalleryModal();
544
+ document.body.classList.remove('is-store-menu-open');
545
+ const p = String(i.content || i.prompt || i.title || '').replace(/2x2\s*grid/gi, '').replace(/\s+/g, ' ').trim();
546
+ if (typeof window.startCharacterImageChat === 'function') {
547
+ await window.startCharacterImageChat(i.id, i.image_url, p, (i.persona_name || i.nickname || i.safeIp || 'Character').slice(0,80), (i.persona_personality || '').slice(0,300));
548
+ }
549
+ },
550
+
551
+ getSortedNewsCategories(){ return this.newsCategories.slice(); },
552
+ getSortedGalleryCategories(){ return this.galleryCategories.slice(); },
553
+ getSortedForumCategories(){ return this.forumCategories.slice(); },
554
+ getSortedStoreCategories(){ return this.storeCategories.slice(); },
555
+ getSortedCodeCategories(){ return this.codeCategories.slice(); },
556
+
557
+ refreshInterestLayout(t){ this.$nextTick(() => { this.applyInterestSort(t); this.updateMacy(t); setTimeout(() => { this.applyInterestSort(t); this.updateMacy(t); }, 150); }); },
558
+ async warmupAiPool(t){ if(t!=='gallery'||this.warmupDone[t])return; this.warmupDone[t]=true; for(let i=0;i<2&&!this.end[t];i++)await this.loadData(t); this.refreshInterestLayout(t); this.initialInterestSortDone[t]=true; },
559
+
560
+ // =========================================================================
561
+ // [초기화 및 무한 스크롤 옵저버 셋업]
562
+ // =========================================================================
563
+ async init() {
564
+ window.$boardApp=this;
565
+ this.normalizeCategoryStrings();
566
+
567
+ this.interestProfile = JSON.parse(localStorage.getItem('isai_interest_profile_v1')||'{}');
568
+ this.productProfile = JSON.parse(localStorage.getItem('isai_product_profile_v1')||'{}');
569
+ this.marketProfile = JSON.parse(localStorage.getItem('isai_market_profile_v1')||'{}');
570
+ this.itemProfile = JSON.parse(localStorage.getItem('isai_item_profile_v1')||'{}');
571
+
572
+ if(!this.hasInitialNewsSearch) {
573
+ if (this.newsCategories && this.newsCategories.length > 0) {
574
+ this.selectedNewsCategoryQ = this.newsQuery = this.newsCategories[0].q;
575
+ }
576
+ } else {
577
+ this.selectedNewsCategoryQ = this.newsQuery = window.__RE_BOARD3_CONFIG__.initialNewsQuery || '';
578
+ }
579
+
580
+ this.syncMarketWidgetMode(true);
581
+
582
+ const urlParams = new URLSearchParams(window.location.search);
583
+ const tabParam = urlParams.get('tab');
584
+ if(['news','forum','store','gallery','code'].includes(tabParam)) this.curTab = tabParam;
585
+ const vParam = urlParams.get('v');
586
+ if (vParam) {
587
+ this.curTab = 'gallery';
588
+ fetch(`re_store.php?action=get_post&id=${vParam}`).then(r => r.json()).then(j => {
589
+ const item = j.data || j; if(item && (item.id || item.share_id)) this.openGalleryItem(item);
590
+ }).catch(e=>{});
591
+ }
592
+
593
+ await this.loadData(this.curTab);
594
+ await this.loadSearchProducts();
595
+ await this.warmupAiPool(this.curTab);
596
+ this.refreshInterestLayout(this.curTab);
597
+
598
+ // [무한 스크롤 완벽 지원] 감지 범위(rootMargin)를 600px로 대폭 늘려서 끊김없이 로드되도록 처리
599
+ const obs = new IntersectionObserver((entries) => {
600
+ entries.forEach(x => {
601
+ if (x.isIntersecting) {
602
+ const tab = x.target.dataset.tab;
603
+ if (tab === this.curTab && !this.loading[tab] && !this.end[tab]) {
604
+ this.loadData(tab);
605
+ }
606
+ }
607
+ });
608
+ }, { rootMargin: '600px', threshold: 0.01 });
609
+
610
+ this.$nextTick(() => {
611
+ document.querySelectorAll('.loading-sentinel').forEach(el => {
612
+ el.style.display = 'block';
613
+ el.style.minHeight = '1px'; // 강제 영역 확보로 관찰 보장
614
+ obs.observe(el);
615
+ });
616
+ });
617
+ },
618
+
619
+ async switchTab(t){
620
+ this.curTab=t;
621
+ if(t==='news'){this.syncMarketWidgetMode(false);}
622
+ this.loadSearchProducts();
623
+ window.dispatchEvent(new CustomEvent('board-tab-change',{detail:{tab:t}}));
624
+ if(!this.items[t].length){ await this.loadData(t); await this.warmupAiPool(t); if(t!=='gallery')this.initialInterestSortDone[t]=true; }
625
+ this.refreshInterestLayout(t);
626
+ },
627
+
628
+ getTopInterestKeywords(limit=5){
629
+ return Object.entries(this.interestProfile || {})
630
+ .filter(([key, value]) => key.startsWith('kw_') && Number(value) > 0)
631
+ .sort((a, b) => Number(b[1]) - Number(a[1]))
632
+ .slice(0, limit)
633
+ .map(([key]) => key.replace(/^kw_/, ''))
634
+ .filter(Boolean);
635
+ },
636
+
637
+ getSponsorSearchQuery(base=''){
638
+ const terms = [];
639
+ String(base || '').split(/\s+/).forEach(v => {
640
+ const t = v.trim();
641
+ if (t && t.toLowerCase() !== 'all') terms.push(t);
642
+ });
643
+ this.getTopInterestKeywords(5).forEach(t => {
644
+ if (!terms.some(x => x.toLowerCase() === t.toLowerCase())) terms.push(t);
645
+ });
646
+ return terms.slice(0, 8).join(' ');
647
+ },
648
+
649
+ async loadSearchProducts(){
650
+ let q = '';
651
+ const isSearchMode = this.isProductSearchMode();
652
+ if (isSearchMode && this.curTab === 'news') q = this.newsQuery;
653
+ else if (isSearchMode && this.curTab === 'store') q = this.storeQuery;
654
+ else if (isSearchMode && this.curTab === 'forum') q = this.forumQuery;
655
+ else if (isSearchMode && this.curTab === 'code') q = this.codeQuery;
656
+ else if (isSearchMode && this.curTab === 'gallery') q = this.galleryQuery;
657
+
658
+ q = String(q || (isSearchMode ? this.searchQuery : '') || '').trim();
659
+ if(q === 'all') q = '';
660
+
661
+ try{
662
+ const productLimit = isSearchMode ? 24 : 3;
663
+ const lang = String(window.ISAI_SERVER_I18N?.locale || window.__RE_BOARD3_CONFIG__?.marketHeatmapLocale || 'all').trim();
664
+ const r = await fetch(`/re_products.php?action=list_products&q=${encodeURIComponent(q)}&limit=${productLimit}&lang=${encodeURIComponent(lang)}`);
665
+ const j = await r.json();
666
+ this.items.products = Array.isArray(j?.data) ? j.data :[];
667
+ this.$nextTick(()=>this.updateMacy(this.curTab));
668
+ }catch(e){ this.items.products=[]; }
669
+ },
670
+
671
+ async loadData(t) {
672
+ if (this.loading[t]||this.end[t]) return;
673
+ this.loading[t] = true;
674
+ try {
675
+ let u='';
676
+ if(t==='news') { const nl = window.__RE_BOARD3_CONFIG__?.newsLang || {}; u=`/re_board3.php?action=get_news&q=${encodeURIComponent(this.newsQuery)}&hl=${encodeURIComponent(nl.hl||'ko')}&gl=${encodeURIComponent(nl.gl||'KR')}&ceid=${encodeURIComponent(nl.ceid||'KR:ko')}`; }
677
+ else if(t==='gallery') u=`re_store.php?action=list_posts&type=gallery&page=${this.page[t]}&q=${encodeURIComponent(this.galleryQuery||'')}`;
678
+ else if(t==='store') u=`re_store.php?action=list_apps&page=${this.page[t]}&q=${encodeURIComponent(this.storeQuery||this.searchQuery||'')}`;
679
+ else if(t==='forum') u=`re_store.php?action=list_posts&type=forum&page=${this.page[t]}${this.currentTag?'&tag='+encodeURIComponent(this.currentTag):''}&q=${encodeURIComponent(this.forumQuery||'')}`;
680
+ else if(t==='code') u=`re_store.php?action=list_code_publish&page=${this.page[t]}&limit=24&q=${encodeURIComponent(this.codeQuery||'')}`;
681
+
682
+ const res = await fetch(u);
683
+ const d = (await res.json()).data||[];
684
+ if(!d.length) {
685
+ this.end[t]=true;
686
+ } else {
687
+ const pd = d.map(i=>{
688
+ i.title=this.decodeEscapedText(i.title); i.content=this.decodeEscapedText(i.content); i.snippet=this.decodeEscapedText(i.snippet);
689
+ if(i.image_url)i.image_url=i.image_url.replace(/(imgur\.com\/[a-zA-Z0-9]+)\.(jpg|png|webp)/i, (m,p1,p2)=>p1+'l.'+p2);
690
+ i.avatar=`https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(i.nickname||i.source||i.title||'U')}&backgroundColor=transparent`;
691
+ if(t==='forum'){ i.listText=String(i.content||i.title||'').replace(/<[^>]*>?/gm,' ').trim().slice(0, 100); }
692
+ else if(t==='news'){ i.cleanTitle=(i.title?.split(' - ')[0])||i.title; }
693
+ return i;
694
+ });
695
+
696
+ const existingKeys = new Set(this.items[t].map(x => x.id || x.link));
697
+ const newItems = pd.filter(x => !existingKeys.has(x.id || x.link));
698
+
699
+ // Alpine.js 배열 반응성을 100% 보장하기 위해 push 대신 재할당
700
+ if(newItems.length > 0) {
701
+ const wasEmpty = this.items[t].length === 0;
702
+ const currentPage = this.page[t];
703
+ this.items[t] = [...this.items[t], ...newItems];
704
+ this.applyInterestSort(t);
705
+ if(t==='news') this.end[t]=true; else this.page[t]++;
706
+ if (wasEmpty || currentPage <= 1) {
707
+ this.refreshInterestLayout(t);
708
+ } else {
709
+ this.$nextTick(() => { this.updateMacy(t); setTimeout(() => { this.updateMacy(t); }, 150); });
710
+ }
711
+ } else {
712
+ this.end[t] = true;
713
+ }
714
+ }
715
+ } catch(e){} finally { this.loading[t]=false; }
716
+ },
717
+
718
+ updateMacy(t) {
719
+ this.$nextTick(() => {
720
+ if (!macyInstances[t]) {
721
+ macyInstances[t] = Macy({
722
+ container: `#${t}-feed`,
723
+ trueOrder: true, waitForImages: true, margin: 16, columns: 6,
724
+ breakAt: { 1600: 6, 1200: 4, 900: 3, 640: 2 }
725
+ });
726
+ macyInstances[t].runOnImageLoad(() => macyInstances[t].recalculate(true, true), true);
727
+ } else {
728
+ macyInstances[t].recalculate(true, true);
729
+ }
730
+ });
731
+ },
732
+ parseTags(t){ return String(t||'').replace(/<[^>]*>?/gm,'').replace(/(^|\s)#([a-zA-Z0-9가-힣_]+)/g,(m,p,tag)=>`${p}<span class="inline-block bg-gray-100 dark:bg-gray-800 text-[10px] px-2 py-0.5 rounded-full cursor-pointer font-bold" onclick="event.stopPropagation(); window.location.href='/?tag=${encodeURIComponent(tag)}'">#${tag}</span>`); }
733
+ }));
734
+ });