PageSourceSearch

https://couponai.co.il/

html couponai.co.il collected 2026-09-24 15:01:38 UTC 749,440 bytes, 12,638 lines download raw bytes

1<!DOCTYPE html>
2<html lang="he" dir="rtl">
3<head>
4<meta charset="UTF-8"/>
5<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6<!-- Google Consent Mode v2 — must run before any Google scripts -->
7<script>
8window.dataLayer = window.dataLayer || [];
9function gtag(){dataLayer.push(arguments);}
10(function(){
11  var consent = localStorage.getItem('couponai_cookie_consent');
12  var granted = consent === 'granted';
13  gtag('consent', 'default', {
14    ad_storage:         granted ? 'granted' : 'denied',
15    analytics_storage:  granted ? 'granted' : 'denied',
16    ad_user_data:       granted ? 'granted' : 'denied',
17    ad_personalization: granted ? 'granted' : 'denied'
18  });
19})();
20gtag('js', new Date());
21gtag('config', 'G-5E92N5ZJGV', { send_page_view: true });
22</script>
22
23<!-- Google Analytics 4 -->
vendor: 64 bytes, lines 23-24
23
24<script async src="https://www.googletagmanager.com/gtag/js?id=
24G-5E92N5ZJGV
vendor: 12 bytes, line 24
24"></script>
25<!-- Google AdSense -->
26<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8550253923281063" crossorigin="anonymous"></script>
26
27<!-- AI scraping protection -->
28<meta name="robots" content="noai, noimageai">
29<meta name="googlebot" content="noai">
30<meta name="CCBot" content="noindex, nofollow">
31<meta name="GPTBot" content="noindex, nofollow">
32<script>
33// Early mobile-preview detection — runs before CSS is applied
34(function(){
35  var m=(location.search||'').match(/[?&]pw=(\d+)/);
36  if(!m)return;
37  var pw=+m[1];
38  // Override viewport width so @media queries fire correctly
39  var vp=document.querySelector('meta[name="viewport"]');
40  if(vp)vp.content='width='+pw+',initial-scale=1';
41  // Store for the later IIFE
42  window._previewWidth=pw;
43})();
44</script>
44
45<title>קופון AI – קודי קופון עדכניים לחנויות ישראליות</title>
46<meta name="description" content="קופון AI - קודי קופון עדכניים לחנויות ישראליות ואתרים בינלאומיים. הנחות אמיתיות מטלגרם ניוזלטרים ועוד - בחינם. חסכו בקניות עם אלפי קופונים שמתעדכנים אוטומטית 24/7. כל ההנחות במקום אחד - בחינם!"/>
47<link rel="canonical" href="https://couponai.co.il/"/>
48<link rel="icon" type="image/png" href="/favicon.png"/>
49<link rel="apple-touch-icon" href="/favicon.png"/>
50<!-- Open Graph -->
51<meta property="og:type" content="website"/>
52<meta property="og:url" content="https://couponai.co.il/"/>
53<meta property="og:title" content="קופון AI – קודי קופון עדכניים לחנויות ישראליות"/>
54<meta property="og:description" content="קודי קופון עדכניים לחנויות ישראליות ואתרים בינלאומיים. הנחות אמיתיות מטלגרם ניוזלטרים ועוד - בחינם. אלפי קופונים שמתעדכנים אוטומטית 24/7."/>
55<meta property="og:image" content="https://couponai.co.il/og_image.png"/>
56<meta property="og:locale" content="he_IL"/>
57<!-- Twitter Card -->
58<meta name="twitter:card" content="summary_large_image"/>
59<meta name="twitter:title" content="קופון AI – קודי קופון עדכניים לחנויות ישראליות"/>
60<meta name="twitter:description" content="קודי קופון עדכניים לחנויות ישראליות ואתרים בינלאומיים. הנחות אמיתיות מטלגרם ניוזלטרים ועוד - בחינם."/>
61<meta name="twitter:image" content="https://couponai.co.il/og_image.png"/>
62<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet"/>
63<script type="application/ld+json">
64{
65  "@context": "https://schema.org",
66  "@type": "WebSite",
67  "name": "קופון AI",
68  "url": "https://couponai.co.il",
69  "description": "קודי קופון עדכניים לחנויות ישראליות ואתרים בינלאומיים. הנחות אמיתיות מטלגרם, ניוזלטרים ועוד — בחינם.",
70  "inLanguage": "he-IL",
71  "potentialAction": {
72    "@type": "SearchAction",
73    "target": {
74      "@type": "EntryPoint",
75      "urlTemplate": "https://couponai.co.il/?q={search_term_string}"
76    },
77    "query-input": "required name=search_term_string"
78  }
79}
80</script>
vendor: 1 bytes, line 80
80
81<script type="application/ld+json">
82{
83  "@context": "https://schema.org",
84  "@type": "Organization",
85  "name": "קופון AI",
86  "url": "https://couponai.co.il",
87  "logo": "https://couponai.co.il/og_image.png",
88  "contactPoint": {
89    "@type": "ContactPoint",
90    "contactType": "customer support",
91    "url": "https://couponai.co.il/contact"
92  }
93}
94</script>
vendor: 1 bytes, line 94
94
95<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
95
96<style>
97*{box-sizing:border-box;margin:0;padding:0}
98:root{
99  --black:#0a0a0a;--white:#fafaf8;--accent:#00e87a;--accent2:#ffe45e;
100  --gray1:#111;--gray2:#1a1a1a;--gray3:#2a2a2a;--gray4:#3d3d3d;
101  --gray5:#888;--gray6:#bbb;
102  --radius:12px;--radiuslg:20px;
103  --nav-bg:rgba(10,10,10,0.92);
104}
105/* ── Light mode overrides ── */
106body.light{
107  --black:#f5f5f3;--white:#111110;--accent:#00b85e;--accent2:#c49800;
108  --gray1:#ebebea;--gray2:#e2e2e0;--gray3:#d0d0ce;--gray4:#b0b0ae;
109  --gray5:#606060;--gray6:#383838;
110  --nav-bg:rgba(245,245,243,0.92);
111}
112body{font-family:'DM Sans',sans-serif;background:var(--black);color:var(--white);min-height:100vh;overflow-x:hidden;direction:rtl;transition:background 0.25s,color 0.25s}
113
114.nav{display:flex;align-items:center;justify-content:space-between;padding:18px 32px;border-bottom:1px solid var(--gray3);position:sticky;top:0;background:var(--nav-bg);backdrop-filter:blur(12px);z-index:100;transition:background 0.25s}
115.nav-logo{font-family:'Syne',sans-serif;font-size:20px;font-weight:800;letter-spacing:-0.5px}
116.nav-logo span{color:var(--accent)}
117.nav-links{display:flex;gap:8px}
118.nav-link{padding:7px 16px;border-radius:8px;font-size:13px;cursor:pointer;color:var(--gray6);transition:all 0.15s;border:none;background:transparent;font-family:'DM Sans',sans-serif}
119.nav-link:hover{color:var(--white);background:var(--gray3)}
120.nav-link.active{color:var(--white);background:var(--gray3)}
121.theme-toggle{width:36px;height:36px;border-radius:50%;border:1px solid var(--gray3);background:var(--gray2);color:var(--gray6);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.15s;flex-shrink:0}
122.theme-toggle:hover{border-color:var(--accent);color:var(--accent)}
123
124/* ── Mobile nav ── */
125.burger-btn{display:none;width:36px;height:36px;border-radius:8px;border:1px solid var(--gray3);background:var(--gray2);color:var(--gray6);cursor:pointer;align-items:center;justify-content:center;flex-shrink:0}
126.burger-btn:hover{border-color:var(--accent);color:var(--accent)}
127.nav-drawer{position:fixed;top:0;right:0;height:100%;width:240px;background:var(--gray1);border-left:1px solid var(--gray3);z-index:999;transform:translateX(100%);transition:transform 0.25s ease;display:flex;flex-direction:column;padding:20px 0}
128.nav-drawer.open{transform:translateX(0)}
129.nav-drawer-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px 14px;border-bottom:1px solid var(--gray3);margin-bottom:8px}
130.nav-drawer-header span{font-family:'Syne',sans-serif;font-size:15px;font-weight:700;color:var(--white)}
131.nav-drawer-close{width:36px;height:36px;border-radius:8px;border:1px solid var(--gray3);background:var(--gray2);color:var(--gray6);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
132.nav-drawer-close:hover{color:var(--white);border-color:var(--accent);background:var(--gray3)}
133.nav-drawer .nav-link{display:block;width:100%;text-align:right;padding:12px 20px;border-radius:0;border:none;background:transparent;font-size:15px;color:var(--gray6);cursor:pointer;font-family:'DM Sans',sans-serif;transition:background 0.15s,color 0.15s}
134.nav-drawer .nav-link:hover,.nav-drawer .nav-link.active{background:var(--gray3);color:var(--white)}
135.nav-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:998}
136.nav-backdrop.show{display:block}
137@media(max-width:640px){
138  .nav{padding:12px 16px;display:grid;grid-template-columns:36px 1fr 36px;align-items:center}
139  .nav-links{display:none}
140  .burger-btn{display:flex}
141  .nav-logo{order:2;text-align:center}
142  .nav>.theme-toggle{order:3}
143  .nav>.burger-btn{order:1}
144}
145body.is-mobile .nav{padding:12px 16px;display:grid;grid-template-columns:36px 1fr 36px;align-items:center}
146body.is-mobile .nav-links{display:none}
147body.is-mobile .burger-btn{display:flex}
148body.is-mobile .nav-logo{order:2;text-align:center}
149body.is-mobile .nav>.theme-toggle{order:3}
150body.is-mobile .nav>.burger-btn{order:1}
151
152/* ── Mobile homepage ── */
153@media(max-width:640px){
154  #pub-store-daily .daily-row{flex-direction:column!important}
155  #pub-store-grid{grid-template-columns:repeat(3,1fr)!important;gap:10px!important}
156  .pub-hp-wrap{padding:0 14px!important}
157}
158body.is-mobile #pub-store-daily .daily-row{flex-direction:column!important}
159body.is-mobile #pub-store-grid{grid-template-columns:repeat(3,1fr)!important;gap:10px!important}
160body.is-mobile .pub-hp-wrap{padding:0 14px!important}
161
162.page{display:none}
163.page.active{display:block}
164
165.hero{padding:32px 32px 40px;text-align:center;position:relative;overflow:hidden}
166.hero-bg{position:absolute;inset:0;background:radial-gradient(ellipse 60% 40% at 50% 0%,rgba(0,232,122,0.08) 0%,transparent 70%);pointer-events:none}
167.hero-badge{display:inline-flex;align-items:center;gap:6px;background:var(--gray2);border:1px solid var(--gray3);border-radius:20px;padding:5px 14px;font-size:12px;color:var(--gray6);margin-bottom:24px}
168.hero-badge span{width:6px;height:6px;background:var(--accent);border-radius:50%;display:inline-block;animation:pulse 2s infinite}
169@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
170.hero h1{font-family:'Syne',sans-serif;font-size:52px;font-weight:800;line-height:1.05;letter-spacing:-2px;margin-bottom:16px}
171.hero h1 em{color:var(--accent);font-style:normal}
172.hero p{font-size:17px;color:var(--gray5);max-width:480px;margin:0 auto 0;line-height:1.6;font-weight:300}
173
174.search-box{max-width:860px;margin:0 auto 48px;position:relative}
175.search-box input{width:100%;padding:22px 24px 22px 150px;background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radiuslg);font-size:18px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;transition:border-color 0.2s;direction:rtl}
176.search-box input::placeholder{color:var(--gray5)}
177.search-box input:focus{border-color:var(--accent)}
178.search-box button{position:absolute;
178left:8px;top:50%;transform:translateY(-50%);background:var(--accent);color:var(--black);border:none;border-radius:12px;padding:14px 22px;font-size:15px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif;display:flex;align-items:center;gap:7px}
179.search-mode-toggle{display:flex;justify-content:center;margin-top:10px}
180.mode-toggle-track{display:flex;background:var(--gray2);border:1px solid var(--gray3);border-radius:24px;padding:3px;gap:2px}
181.mode-toggle-opt{padding:5px 18px;border-radius:20px;font-size:13px;font-weight:500;cursor:pointer;border:none;background:transparent;color:var(--gray5);font-family:'DM Sans',sans-serif;transition:all 0.18s;display:flex;align-items:center;gap:5px;white-space:nowrap}
182.mode-toggle-opt:hover{color:var(--white)}
183.mode-toggle-opt.active{background:var(--accent);color:var(--black);font-weight:600}
184.mode-toggle-opt.active-deep{background:var(--gray3);color:var(--white);font-weight:600}
185.mode-badge{display:inline-flex;align-items:center;background:rgba(255,255,255,0.15);border-radius:8px;padding:1px 6px;font-size:10px;font-weight:700;letter-spacing:0.3px}
186.sort-tabs{display:flex;gap:6px;padding:0 32px;margin-bottom:14px;flex-wrap:wrap}
187.sort-tab{padding:5px 14px;border:1px solid var(--gray3);border-radius:20px;font-size:12px;background:transparent;color:var(--gray5);cursor:pointer;font-family:'DM Sans',sans-serif;transition:all 0.15s}
188.sort-tab.on{background:var(--gray3);color:var(--white);border-color:var(--gray4)}
189
190.stats-row{display:flex;justify-content:center;gap:40px;margin-bottom:60px;flex-wrap:wrap}
191.stat{text-align:center}
192.stat-n{font-family:system-ui,sans-serif;font-size:42px;font-weight:800;color:var(--white);line-height:1}
193.stat-l{font-size:14px;color:var(--gray5);margin-top:6px}
194
195.section-label{font-family:'Syne',sans-serif;font-size:24px;font-weight:700;color:var(--white);margin-bottom:20px;padding:0 32px}
196.coupon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;padding:0 32px 60px;max-width:1300px;margin-left:auto;margin-right:auto}
197
198.coupon-card{background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radius);padding:16px;transition:all 0.2s;position:relative;overflow:hidden;text-align:right}
199.coupon-card:hover{border-color:var(--gray4);transform:translateY(-2px)}
200.coupon-card.featured{border-color:rgba(0,232,122,0.3)}
201.coupon-card.featured::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--accent)}
202.cc-store-row{display:flex;align-items:center;gap:8px;margin-bottom:12px;flex-direction:row-reverse;justify-content:flex-end}
203.cc-logo{width:32px;height:32px;border-radius:8px;background:#fff;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:var(--gray6);overflow:hidden}
204.cc-logo img{width:100%;height:100%;object-fit:contain}
205.cc-store{font-size:13px;font-weight:500}
206.cc-discount{font-family:system-ui,sans-serif;font-size:26px;font-weight:800;letter-spacing:-0.5px;margin-bottom:8px;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
207.cc-notes{font-size:13px;color:var(--gray5);margin-bottom:10px;line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
208.cc-code-row{display:flex;align-items:center;gap:6px;margin-bottom:12px;flex-direction:row-reverse}
209/* cc-code is a grid so masked+revealed share the same cell — same height always */
210.cc-code{font-family:monospace;font-size:13px;background:var(--gray3);border:1px solid var(--gray4);border-radius:6px;padding:10px 12px;flex:1;color:var(--gray6);letter-spacing:1.5px;text-align:left;direction:ltr;overflow:hidden;touch-action:manipulation;display:grid;position:relative;transition:border-color 0.2s,box-shadow 0.2s,background 0.2s,transform 0.1s}
211.cc-code:active{transform:scale(0.98)}
212/* Pre-reveal: Solid Fill green CTA button */
213.cc-code-row:not(.revealed) .cc-code{background:#00e87a;border-color:#00e87a;cursor:pointer}
214.cc-code-row:not(.revealed) .cc-code:hover{filter:brightness(1.08)}
215.cc-code-row:not(.revealed) .cc-code .cc-code-dots{color:rgba(0,0,0,0.4);animation:none}
216.cc-code-row:not(.revealed) .cc-code .cc-code-end{color:#003d1f}
217.cc-code-row:not(.revealed) .cc-code .cc-code-eye{color:rgba(0,0,0,0.45);opacity:1}
218/* Post-reveal: copy-friendly neutral state */
219.cc-code-row.revealed .cc-code{background:var(--gray3)!important;border-color:rgba(0,232,122,0.35)!important;filter:none!important;cursor:pointer}
220.cc-code-row.revealed .cc-code:hover{background:var(--gray4)!important;border-color:rgba(0,232,122,0.7)!important;box-shadow:0 0 0 3px rgba(0,232,122,0.12)}
221.cc-copy{background:var(--gray3);border:1px dashed var(--gray4);border-radius:6px;color:var(--accent);cursor:pointer;padding:0 12px;display:flex;align-items:center;justify-content:center;align-self:stretch;opacity:0.8;transition:opacity 0.15s,border-color 0.2s,background 0.2s,box-shadow 0.2s,transform 0.1s;touch-action:manipulation}
222.cc-copy:hover{opacity:1;border-color:rgba(0,232,122,0.7);background:rgba(0,232,122,0.15);box-shadow:0 0 0 3px rgba(0,232,122,0.12);transform:scale(1.05)}
223.cc-copy:active{transform:scale(0.96)}
224.cc-code-row.revealed .cc-copy{border-style:solid;border-color:rgba(0,232,122,0.5);background:rgba(0,232,122,0.08);opacity:1}
225.cc-code-row.revealed .cc-copy:hover{border-color:rgba(0,232,122,0.7);background:rgba(0,232,122,0.2);box-shadow:0 0 0 3px rgba(0,232,122,0.12);transform:scale(1.05)}/* Both states in the same grid cell — no height difference */
226.cc-code-masked,.cc-code-revealed{grid-area:1/1;display:flex;align-items:center}
227/* Masked: dots + last 2 chars on left, eye icon on right */
228.cc-code-masked{gap:0;cursor:pointer;transition:opacity 0.15s ease;justify-content:space-between}
229.cc-code-masked.hiding{opacity:0;pointer-events:none}
230/* Peek group: dots+last2 stay flush together */
231.cc-code-peek{display:flex;align-items:center;gap:0}
232/* Eye icon on right of masked state */
233.cc-code-eye{color:var(--gray5);display:flex;align-items:center;flex-shrink:0;transition:color .2s;opacity:.7}
234.cc-code:hover .cc-code-eye{color:var(--accent);opacity:1}
235/* Breathing dots */
236@keyframes cc-dots-breathe{0%,100%{opacity:0.35}50%{opacity:0.7}}
237.cc-code-dots{color:rgba(255,255,255,0.45);font-size:16px;font-weight:700;letter-spacing:3px;white-space:nowrap;animation:cc-dots-breathe 2.4s ease-in-out infinite}
238.cc-code-end{color:var(--accent);font-weight:800;font-size:16px;letter-spacing:3px}
239/* Revealed — crossfades over the masked layer */
240.cc-code-revealed{opacity:0;transform:translateY(3px);pointer-events:none;color:var(--accent);font-weight:800;letter-spacing:2px;font-size:16px;cursor:pointer;width:100%;transition:opacity 0.25s ease,transform 0.25s ease;font-family:monospace}
241.cc-code-revealed.visible{opacity:1;transform:translateY(0);pointer-events:auto}
242/* Full-field pop on reveal — ::after overlay sweeps in then fades */
243@keyframes cc-highlight-pop{0%{transform:scaleX(0);opacity:1}55%{transform:scaleX(1);opacity:1}100%{transform:scaleX(1);opacity:0}}
244.cc-highlight::after{content:'';position:absolute;inset:0;background:rgba(0,232,122,0.3);border-radius:6px;transform-origin:left;pointer-events:none;animation:cc-highlight-pop 0.55s ease-out forwards}
245/* Respect reduced-motion */
246@media(prefers-reduced-motion:reduce){
247  .cc-highlight::after{animation:none;opacity:0}
248  .cc-code-dots{animation:none}
249  .cc-code-revealed{transition:none}
250  .cc-code-masked{transition:none}
251  .coupon-card:hover{transform:none}
252}
253.shimmer-bg{background:linear-gradient(90deg,var(--gray3) 25%,var(--gray4) 50%,var(--gray3) 75%);background-size:200% 100%;animation:shimmer 1.6s infinite}
254@keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
255.tip-pulse{animation:tip-dot-bounce 1.2s infinite}
256@keyframes tip-dot-bounce{0%,80%,100%{transform:scale(0.5);opacity:0.3}40%{transform:scale(1);opacity:1}}
257.no-code-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:11px 12px;border-radius:8px;border:1px solid rgba(0,232,122,0.3);background:rgba(0,232,122,0.07);color:var(--accent);font-size:14px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif;transition:all 0.15s;margin-bottom:12px;text-align:center;box-sizing:border-box}
258.no-code-btn:hover{background:rgba(0,232,122,0.15);border-color:var(--accent)}
259.cc-footer{display:flex;justify-content:flex-start;align-items:center;gap:6px;flex-direction:row-reverse}
260.vote-btn{display:flex;align-items:center;gap:4px;border:none;border-radius:8px;padding:4px 10px;font-size:11px;font-weight:500;cursor:pointer;font-family:'DM Sans',sans-serif;transition:all 0.15s}
261.vote-up{background:rgba(0,232,122,0.12);color:var(--accent)}
262.vote-up:hover:not(:disabled){background:rgba(0,232,122,0.25)}
263.vote-down{background:rgba(255,80,80,0.1);color:#ff6060}
264.vote-down:hover:not(:disabled){background:rgba(255,80,80,0.22)}
265.vote-btn:disabled{opacity:0.45;cursor:default}
266.vote-btn.voted{opacity:0.55;cursor:default}
267.cc-age{display:none;position:absolute;top:10px;left:12px;font-size:10px;color:var(--gray5);background:var(--gray3);border-radius:6px;padding:2px 7px;direction:ltr}
268.cc-age.old{color:#ff9060;background:rgba(255,100,60,0.12)}
269body.is-staff .cc-age{display:block}
270.cc-newsletter-badge{display:inline-flex;align-items:center;gap:4px;background:rgba(255,228,94,0.12);border:1px solid rgba(255,228,94,0.35);border-radius:6px;padding:3px 9px;font-size:11px;color:var(--accent2);margin-bottom:8px;font-weight:500}
271
272/* ── Expired card styles (≤ −10 downvotes) ── */
273
274/* Style 1 – red diagonal ribbon */
275.exp-ribbon{overflow:hidden}
276.exp-ribbon::after{content:'פג תוקף';position:absolute;top:20px;left:-26px;width:96px;background:#e53935;color:#fff;font-size:9px;font-weight:700;padding:4px 0;text-align:center;transform:rotate(-45deg);letter-spacing:0.5px;z-index:5;pointer-events:none}
277/* Push age badge to right so ribbon doesn't cover it */
278.exp-ribbon .cc-age{left:auto;right:12px;z-index:6}
279
280/* Style 2 – gray fade + red badge */
281.exp-gray{opacity:0.42;filter:grayscale(0.3)}
282.exp-badge-red{display:inline-flex;align-items:center;gap:5px;background:rgba(229,57,53,0.15);border:1px solid rgba(229,57,53,0.3);color:#e57373;font-size:11px;font-weight:500;border-radius:20px;padding:4px 12px;margin-bottom:8px}
283
284/* Style 3 – red top border + warning row */
285.exp-border{border-top:3px solid #e53935 !important}
286.exp-warning{font-size:11px;color:#e57373;background:rgba(229,57,53,0.08);border-radius:6px;padding:4px 10px;margin-bottom:8px;display:flex;align-items:center;gap:5px}
287
288/* Style 4 – orange badge at bottom (no strikethrough) */
289.exp-badge-orange{display:inline-flex;align-items:center;gap:4px;background:rgba(255,152,0,0.15);border:1px solid rgba(255,152,0,0.3);color:#ffb74d;font-size:11px;font-weight:500;border-radius:20px;padding:3px 10px}
290
291/* Style 5 – collapsed card */
292.exp-collapsed{padding:10px 16px !important;cursor:pointer}
293.exp-collapsed .cc-collapsed-content{display:none}
294.exp-collapsed.open .cc-collapsed-content{display:block}
295.exp-collapsed-header{display:flex;justify-content:space-between;align-items:center;gap:8px}
296
297/* Demo section label */
298.demo-opt-label{margin-top:10px;text-align:center;font-size:11px;font-weight:600;color:var(--accent);border-top:1px solid var(--gray3);padding-top:8px}
299
300.cats{display:flex;gap:8px;padding:0 32px;flex-wrap:wrap;margin-bottom:24px}
301.cat-btn{padding:7px 16px;border:1px solid var(--gray3);border-radius:20px;font-size:12px;background:transparent;color:var(--gray5);cursor:pointer;transition:all 0.15s;font-family:'DM Sans',sans-serif}
302.cat-btn.on{background:var(--accent);color:var(--black);border-color:var(--accent);font-weight:500}
303.cat-btn:hover:not(.on){border-color:var(--gray4);color:var(--white)}
304
305.admin-layout{display:grid;grid-template-columns:1fr 220px;min-height:calc(100vh - 57px)}
306.sidebar{background:var(--gray1);border-left:1px solid var(--gray3);padding:20px 0;order:2}
307/* Mobile-only elements — hidden on desktop */
308#mob-backdrop{display:none}
309#mob-close-btn{display:none}
310#mob-menu-bar{display:none}
311.sidebar-section{padding:0 12px;margin-bottom:24px}
312.sidebar-label{font-size:10px;font-weight:500;letter-spacing:2px;color:var(--gray5);text-transform:uppercase;padding:0 12px;margin-bottom:8px;display:block}
313.sidebar-item{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;font-size:13px;
313cursor:pointer;color:var(--gray6);transition:all 0.15s;margin-bottom:2px;border:none;background:transparent;width:100%;text-align:right;font-family:'DM Sans',sans-serif;direction:rtl}
314.sidebar-item:hover{background:var(--gray3);color:var(--white)}
315.sidebar-item.on{background:var(--gray3);color:var(--white)}
316.sidebar-icon{width:16px;height:16px;border-radius:4px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:10px}
317.sidebar-count{margin-right:auto;background:var(--gray3);color:var(--gray5);font-size:10px;padding:2px 7px;border-radius:10px}
318.sidebar-item.on .sidebar-count{background:var(--gray4);color:var(--gray6)}
319
320.admin-main{padding:28px;overflow-y:auto;order:1}
321.admin-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;flex-direction:row-reverse}
322.admin-title{font-family:'Syne',sans-serif;font-size:22px;font-weight:700}
323.admin-actions{display:flex;gap:8px}
324.btn-sm{padding:8px 16px;border-radius:8px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:500}
325.btn-ghost{background:transparent;border:1px solid var(--gray3);color:var(--gray6)}
326.btn-ghost:hover{border-color:var(--gray4);color:var(--white)}
327.btn-primary{background:var(--accent);border:none;color:var(--black)}
328.btn-primary:hover{background:#00cc6a}
329
330.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px}
331.metric{background:var(--gray2);border:1px solid rgba(255,255,255,0.06);border-radius:20px;padding:20px 18px 16px;text-align:right;position:relative;overflow:hidden;transition:transform 0.15s}
332.metric:hover{transform:translateY(-2px)}
333.metric-icon{font-size:22px;margin-bottom:12px;display:block}
334.metric-val{font-family:system-ui,sans-serif;font-size:42px;font-weight:800;line-height:1;letter-spacing:-1px}
335.metric-label{font-size:11px;color:var(--gray5);margin-top:6px;text-transform:uppercase;letter-spacing:0.5px;font-weight:500}
336.metric-delta{font-size:11px;margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,0.06)}
337.metric-bar{height:3px;border-radius:2px;margin-top:14px;background:rgba(255,255,255,0.08)}
338.metric-bar-fill{height:100%;border-radius:2px;transition:width 0.6s ease}
339
340.sources-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:24px}
341.source-card{background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radius);padding:16px;text-align:right}
342.source-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;flex-direction:row-reverse}
343.source-name{font-size:13px;font-weight:500}
344.source-status{display:flex;align-items:center;gap:5px;font-size:11px}
345.source-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
346.dot-green{background:var(--accent);animation:pulse 2s infinite}
347.dot-amber{background:var(--accent2)}
348.source-stat{font-size:11px;color:var(--gray5)}
349.source-bar{height:3px;background:var(--gray3);border-radius:2px;margin-top:10px;overflow:hidden}
350.source-fill{height:100%;border-radius:2px;background:var(--accent)}
351
352.table-wrap{background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radius);overflow:hidden}
353.table-header{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;border-bottom:1px solid var(--gray3);flex-direction:row-reverse}
354.table-title{font-size:13px;font-weight:500}
355.table-search input{background:var(--gray3);border:none;border-radius:8px;padding:6px 12px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;width:180px;direction:rtl}
356table{width:100%;border-collapse:collapse}
357th{font-size:10px;font-weight:500;letter-spacing:1px;text-transform:uppercase;color:var(--gray5);padding:10px 18px;text-align:right;border-bottom:1px solid var(--gray3)}
358td{font-size:12px;padding:11px 18px;border-bottom:1px solid rgba(255,255,255,0.04);color:var(--gray6);vertical-align:middle;text-align:right}
359tr:last-child td{border-bottom:none}
360tr:hover td{background:rgba(255,255,255,0.02)}
361.td-store{color:var(--white);font-weight:500}
362.td-code{font-family:monospace;font-size:11px;background:var(--gray3);padding:2px 8px;border-radius:4px;letter-spacing:1px;color:var(--gray6);direction:ltr;display:inline-block}
363.td-actions{display:flex;gap:6px;justify-content:flex-end}
364.td-btn{background:var(--gray3);border:none;color:var(--gray5);border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif}
365.td-btn:hover{color:var(--white);background:var(--gray4)}
366.td-btn.danger:hover{color:#ff6060}
366
367.td-btn.page-active{background:var(--accent);color:var(--black);font-weight:600}
368
369.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:var(--accent);color:var(--black);padding:10px 20px;border-radius:20px;font-size:13px;font-weight:500;transition:transform 0.3s;pointer-events:none;z-index:999;font-family:'DM Sans',sans-serif}
370.toast.show{transform:translateX(-50%) translateY(0)}
371#search-btn{transition:background 0.2s;min-width:110px}
372#search-btn.loading{opacity:0.6;pointer-events:none;cursor:default}
373@keyframes spin{to{transform:rotate(360deg)}}
374.spinner{display:inline-block;width:11px;height:11px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:spin 0.7s linear infinite;margin-left:6px;vertical-align:-1px}
375.ai-banner{background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:16px;padding:18px 28px;margin:0 20px 28px;font-size:16px;color:var(--gray6);display:none;direction:rtl;text-align:right;line-height:1.6}
376.ai-banner.show{display:block}
377.ai-banner strong{color:var(--accent);font-size:17px}
378.thinking-dots span{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:tdot 1.2s infinite}
379.thinking-dots{display:inline-flex;gap:3px;align-items:center;vertical-align:middle;margin-left:4px}
380.thinking-dots span{width:5px;height:5px;border-radius:50%;background:var(--accent);animation:tdot 1.2s infinite}
381.thinking-dots span:nth-child(2){animation-delay:0.2s}
382.thinking-dots span:nth-child(3){animation-delay:0.4s}
383@keyframes tdot{0%,80%,100%{opacity:0.2;transform:scale(0.8)}40%{opacity:1;transform:scale(1)}}
384
385/* ── Mobile admin adaptations (≤ 640px) ──────────────────────────────────── */
386@media (max-width: 640px) {
387
388  /* Single column layout */
389  .admin-layout {
390    grid-template-columns: 1fr;
391    min-height: calc(100vh - 48px);
392    position: relative;
393  }
394
395  /* Sidebar → hidden slide-in drawer from the right */
396  .sidebar {
397    position: fixed;
398    top: 0; right: -280px;
399    width: 260px;
400    height: 100vh;
401    z-index: 1000;
402    border-left: 1px solid var(--gray3);
403    padding: 60px 0 20px;
404    transition: right 0.28s cubic-bezier(0.4,0,0.2,1);
405    box-shadow: none;
406    overflow-y: auto;
407  }
408  .sidebar.mob-open {
409    right: 0;
410    box-shadow: -8px 0 32px rgba(0,0,0,0.6);
411  }
412
413  /* Backdrop overlay */
414  #mob-backdrop {
415    position: fixed;
416    inset: 0;
417    background: rgba(0,0,0,0.5);
418    z-index: 999;
419  }
420  #mob-backdrop.show { display: block; }
421
422  /* Hamburger button */
423  #mob-menu-btn {
424    display: flex;
425    align-items: center;
426    justify-content: center;
427    width: 36px; height: 36px;
428    background: var(--gray2);
429    border: 1px solid var(--gray3);
430    border-radius: 8px;
431    cursor: pointer;
432    font-size: 18px;
433    color: var(--white);
434    flex-shrink: 0;
435  }
436
437  /* Close button inside drawer */
438  #mob-close-btn {
439    display: flex !important;
440    position: absolute;
441    top: 14px; left: 14px;
442    width: 28px; height: 28px;
443    align-items: center; justify-content: center;
444    background: var(--gray3);
445    border: none;
446    border-radius: 6px;
447    color: var(--gray5);
448    font-size: 14px;
449    cursor: pointer;
450  }
451
452  /* Main area */
453  .admin-main { padding: 14px 12px; }
454
455  /* Show mobile menu bar */
456  #mob-menu-bar { display: flex !important; }
457
458  /* Header */
459  .admin-header {
460    flex-direction: column;
461    align-items: flex-end;
462    gap: 10px;
463    margin-bottom: 16px;
464  }
465  .admin-title { font-size: 17px; }
466
467  /* Metrics: 2×2 */
468  .metrics {
469    grid-template-columns: repeat(2, 1fr);
470    gap: 8px;
471    margin-bottom: 16px;
472  }
473  .metric { padding: 14px 12px 12px; border-radius: 14px; }
474  .metric-val { font-size: 30px; }
475  .metric-icon { font-size: 18px; margin-bottom: 8px; }
476
477  /* Sources grid: 1 column */
478  .sources-grid {
479    grid-template-columns: 1fr;
480    margin-bottom: 16px;
481  }
482
483  /* Analytics panels: stack vertically */
484  #view-dashboard > div[style*="grid-template-columns:1fr 1fr"] {
485    display: flex !important;
486    flex-direction: column !important;
487    gap: 12px !important;
488    margin: 14px 0 !important;
489  }
490
491  /* Analytics inner layout: stack chart + list */
492  #analytics-panel > div[style*="grid-template-columns:1fr 280px"],
493  #speed-panel    > div[style*="grid-template-columns:1fr 280px"] {
494    display: flex !important;
495    flex-direction: column !important;
496    gap: 12px !important;
497  }
498
499  /* Speed cards: 2 per row */
500  #speed-cards,
501  #analytics-grid {
502    grid-template-columns: repeat(2, 1fr) !important;
503  }
504
505  /* Tables: horizontal scroll */
506  .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
507  table { min-width: 480px; }
508
509  /* Table padding tighter */
510  th { padding: 8px 10px; font-size: 9px; }
511  td { padding: 8px 10px; font-size: 11px; }
512
513  /* Hide less important columns on mobile */
514  .col-source, .col-notes, .col-min,
515  th.col-source, td.col-source,
516  th.col-notes,  td.col-notes,
517  th.col-min,    td.col-min    { display: none; }
518
519  /* Table header search */
520  .table-header { flex-wrap: wrap; gap: 8px; padding: 10px 12px; }
521  .table-search input { width: 140px; }
522
523  /* Action buttons: smaller */
524  .td-btn { padding: 4px 8px; font-size: 10px; }
525
526  /* Admin search/filter row */
527  #adm-search-row { flex-wrap: wrap; gap: 6px; }
528  #adm-search-row input,
529  #adm-search-row select { font-size: 12px; }
530
531  /* Pagination */
532  #adm-pagination { font-size: 11px; gap: 4px; }
533}
534
535/* ── body.is-mobile — mirrors @media block for iframe preview ── */
536
537body.is-mobile .admin-layout {
538  grid-template-columns: 1fr;
539  min-height: calc(100vh - 48px);
540  position: relative;
541}
542body.is-mobile .sidebar {
543  position: fixed;
544  top: 0; right: -280px;
545  width: 260px;
546  height: 100vh;
547  z-index: 1000;
548  border-left: 1px solid var(--gray3);
549  padding: 60px 0 20px;
550  transition: right 0.28s cubic-bezier(0.4,0,0.2,1);
551  box-shadow: none;
552  overflow-y: auto;
553}
554body.is-mobile .sidebar.mob-open {
555  right: 0;
556  box-shadow: -8px 0 32px rgba(0,0,0,0.6);
557}
558body.is-mobile #mob-backdrop {
559  position: fixed;
560  inset: 0;
561  background: rgba(0,0,0,0.5);
562  z-index: 999;
563  display: none;
564}
565body.is-mobile #mob-backdrop.show { display: block; }
566body.is-mobile #mob-menu-btn {
567  display: flex;
568  align-items: center;
569  justify-content: center;
570  width: 36px; height: 36px;
571  background: var(--gray2);
572  border: 1px solid var(--gray3);
573  border-radius: 8px;
574  cursor: pointer;
575  font-size: 18px;
576  color: var(--white);
577  flex-shrink: 0;
578}
579body.is-mobile #mob-close-btn {
580  display: flex !important;
581  position: absolute;
582  top: 14px; left: 14px;
583  width: 28px; height: 28px;
584  align-items: center; justify-content: center;
585  background: var(--gray3);
586  border: none;
587  border-radius: 6px;
588  color: var(--gray5);
589  font-size: 14px;
590  cursor: pointer;
591}
592body.is-mobile .admin-main { padding: 14px 12px; }
593body.is-mobile #mob-menu-bar { display: flex !important; }
594body.is-mobile .admin-header {
595  flex-direction: column;
596  align-items: flex-end;
597  gap: 10px;
598  margin-bottom: 16px;
599}
600body.is-mobile .admin-title { font-size: 17px; }
601body.is-mobile .metrics {
602  grid-template-columns: repeat(2, 1fr);
603  gap: 8px;
604  margin-bottom: 16px;
605}
606body.is-mobile .metric { padding: 14px 12px 12px; border-radius: 14px; }
607body.is-mobile .metric-val { font-size: 30px; }
608body.is-mobile .metric-icon { font-size: 18px; margin-bottom: 8px; }
609body.is-mobile .sources-grid {
610  grid-template-columns: 1fr;
611  margin-bottom: 16px;
612}
613body.is-mobile #view-dashboard > div[style*="grid-template-columns:1fr 1fr"] {
614  display: flex !important;
615  flex-direction: column !important;
616  gap: 12px !important;
617  margin: 14px 0 !important;
618}
619body.is-mobile #analytics-panel > div[style*="grid-template-columns:1fr 280px"],
620body.is-mobile #speed-panel    > div[style*="grid-template-columns:1fr 280px"] {
621  display: flex !important;
622  flex-direction: column !important;
623  gap: 12px !important;
624}
625body.is-mobile #speed-cards,
626body.is-mobile #analytics-grid {
627  grid-template-columns: repeat(2, 1fr) !important;
628}
629body.is-mobile .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
630body.is-mobile table { min-width: 480px; }
631body.is-mobile th { padding: 8px 10px; font-size: 9px; }
632body.is-mobile td { padding: 8px 10px; font-size: 11px; }
633body.is-mobile .col-source, body.is-mobile .col-notes, body.is-mobile .col-min,
634body.is-mobile th.col-source, body.is-mobile td.col-source,
635body.is-mobile th.col-notes,  body.is-mobile td.col-notes,
636body.is-mobile th.col-min,    body.is-mobile td.col-min { display: none; }
637body.is-mobile .table-header { flex-wrap: wrap; gap: 8px; padding: 10px 12px; }
638body.is-mobile .table-search input { width: 140px; }
639body.is-mobile .td-btn { padding: 4px 8px; font-size: 10px; }
640body.is-mobile #adm-search-row { flex-wrap: wrap; gap: 6px; }
641body.is-mobile #adm-search-row input,
642body.is-mobile #adm-search-row select { font-size: 12px; }
643body.is-mobile #adm-pagination { font-size: 11px; gap: 4px; }
644
645/* ── Reveal modal ─────────────────────────────────────────────────────────── */
646#reveal-modal-overlay {
647  display: none; position: fixed; inset: 0;
648  background: rgba(0,0,0,0.55);
649  backdrop-filter: blur(3px);
650  -webkit-backdrop-filter: blur(3px);
651  z-index: 6000;
652  align-items: center; justify-content: center;
653}
654#reveal-modal-overlay.active { display: flex; }
655#reveal-modal {
656  background: #0c1f10;
657  border: 1px solid rgba(0,232,122,0.25);
658  border-radius: 20px;
659  width: 92%; max-width: 460px;
660  text-align: center; position: relative; overflow: hidden;
661  box-shadow: 0 12px 50px rgba(0,0,0,0.55);
662  animation: rm-in 0.22s ease;
663  direction: rtl;
664}
665@keyframes rm-in {
666  from { opacity:0; transform:scale(0.93) translateY(12px); }
667  to   { opacity:1; transform:scale(1)    translateY(0);    }
668}
669/* Header */
670#rm-header {
671  padding: 16px 20px 14px;
672  border-bottom: 1px solid rgba(255,255,255,0.07);
673  position: relative;
674}
675#rm-header-title {
676  font-family: system-ui, sans-serif; font-size: 17px; font-weight: 800;
677  color: #fff; letter-spacing: -0.3px; text-align: center;
678}
679#rm-close {
680  position: absolute; top: 50%; left: 16px; transform: translateY(-50%);
681  background: none; border: none; color: rgba(255,255,255,0.35);
682  font-size: 22px; cursor: pointer; line-height: 1; padding: 2px 4px;
683  transition: color 0.15s;
684}
685#rm-close:hover { color: #fff; }
686/* Body */
687#rm-body { padding: 20px 50px 16px; }
688#rm-logo {
689  height: 64px; width: fit-content; min-width: 64px; max-width: 220px;
690  border-radius: 14px; overflow: hidden; margin: 0 auto 14px;
691  background: rgba(255,255,255,0.07);
692  display: flex; align-items: center; justify-content: center;
693  box-shadow: 0 2px 10px rgba(0,0,0,0.3);
694}
695#rm-logo img { height: 100%; width: auto; max-width: 220px; display: block; object-fit: contain; }
696#rm-discount {
697  font-family: system-ui, sans-serif; font-size: 34px; font-weight: 800;
698  color: #fff; margin-bottom: 6px; direction: rtl; letter-spacing: -0.5px;
699}
700#rm-notes {
701  font-size: 14px; color: rgba(255,255,255,0.55);
702  margin-bottom: 14px; font-family: 'DM Sans', sans-serif;
703  line-height: 1.5; direction: rtl;
704}
705/* Code row */
706#rm-code-wrap {
707  display: flex; justify-content: center; margin-bottom: 18px;
708}
709#rm-code-wrap .cc-code-row {
710  gap: 10px; margin-bottom: 0;
711  min-width: 220px; max-width: 100%;
712}
713#rm-code-wrap .cc-code {
714  font-size: 28px !important; padding: 16px 22px; letter-spacing: 4px;
715  background: var(--gray3) !important;
716  border-color: rgba(0,232,122,0.5) !important;
717}
718#rm-code-wrap .cc-code .cc-code-revealed {
719  font-size: 28px !important; letter-spacing: 4px !important;
720}
721#rm-code-wrap .cc-copy {
722  border-style: solid; border-color: rgba(0,232,122,0.5);
723  background: rgba(0,232,122,0.08); opacity: 1;
724  width: 58px; height: auto; flex-shrink: 0;
725}
726#rm-code-wrap .cc-copy svg { width: 20px; height: 20px; }
727#rm-code-wrap .cc-copy:hover {
728  border-color: rgba(0,232,122,0.85); background: rgba(0,232,122,0.2);
729}
730/* Go button — narrow pill, centered */
731#rm-go-btn-wrap { text-align: center; margin-bottom: 20px; }
732#rm-go-btn {
733  display: block; width: fit-content; margin: 0 auto;
734  background: #00e87a; color: #000;
735  border: none; border-radius: 8px;
736  padding: 13px 36px; font-size: 15px; font-weight: 700;
737  cursor: pointer; text-decoration: none;
738  font-family: system-ui, sans-serif;
739  transition: filter 0.15s, transform 0.1s;
740}
741#rm-go-btn:hover { filter: brightness(1.1); transform: scale(1.02); }
742/* Vote row */
743#rm-vote-label {
744  font-size: 14px; color: rgba(255,255,255,0.5);
745  margin-bottom: 10px; font-family: 'DM Sans', sans-serif; font-weight: 500;
746}
747#rm-vote-row {
748  display: flex; justify-content: center; gap: 14px; margin-bottom: 4px;
749}
750#rm-vote-row .vote-btn { font-size: 14px; padding: 8px 20px; gap: 7px; }
751#rm-vote-row .vote-btn svg { width: 16px; height: 16px; }
752/* Footer — RTL: store link on left, views on right */
753#rm-footer {
754  padding: 11px 18px;
755  border-top: 1px solid rgba(255,255,255,0.07);
756  display: flex; align-items: center; justify-content: space-between;
757  gap: 10px;
758}
759#rm-views {
760  font-size: 11px; color: rgba(255,255,255,0.35);
761  font-family: 'DM Sans', sans-serif; white-space: nowrap;
762}
763#rm-more-link {
764  font-size: 12px; color: var(--accent);
765  font-family: 'DM Sans', sans-serif; font-weight: 600;
766  text-decoration: none; white-space: nowrap; transition: opacity 0.15s;
767  direction: rtl;
768}
769#rm-more-link:hover { opacity: 0.75; }
770/* Toast */
771#rm-toast {
772  position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%) translateY(12px);
773  background: #00e87a; color: #000; font-family: system-ui, sans-serif;
774  font-size: 13px; font-weight: 700; padding: 8px 20px; border-radius: 50px;
775  white-space: nowrap; pointer-events: none;
776  opacity: 0; transition: opacity 0.2s, transform 0.2s;
777}
778#rm-toast.visible {
779  opacity: 1; transform: translateX(-50%) translateY(0);
780}
781</style>
782</head>
783<body>
784
785<!-- PREPROD BANNER — injected by JS if ENVIRONMENT=preprod -->
786<div id="preprod-banner" style="display:none;position:fixed;top:0;left:0;right:0;z-index:99999;background:#f59e0b;color:#000;text-align:center;padding:6px 16px;font-size:12px;font-weight:700;letter-spacing:0.5px;box-shadow:0 2px 8px rgba(0,0,0,0.3)">
787  🚧 PRE-PROD — סביבת בדיקה בלבד · לא מחובר לאתר הרשמי
788</div>
789
790<div class="nav">
791  <div class="nav-logo" onclick="showPage('public')" style="cursor:pointer">קופון<span>AI</span></div>
792  <div class="nav-links" id="main-nav-links">
793    <button class="nav-link active" onclick="showPage('public')">ראשי</button>
794    <a href="/extension" class="nav-link">תוסף Chrome</a>
795    <a href="/about" class="nav-link">אודות</a>
796    <a href="/contact" class="nav-link">צור קשר</a>
797  </div>
798  <button class="theme-toggle" id="theme-btn" onclick="toggleTheme()" title="החלף מצב תצוגה">
799    <svg id="theme-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
800  </button>
801  <button class="burger-btn" onclick="_navDrawerOpen()" title="תפריט">
802    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
803  </button>
804</div>
805
806<!-- Mobile nav drawer -->
807<div class="nav-backdrop" id="nav-backdrop" onclick="_navDrawerClose()"></div>
808<div class="nav-drawer" id="nav-drawer">
809  <div class="nav-drawer-header">
810    <span>תפריט</span>
811    <button class="nav-drawer-close" onclick="_navDrawerClose()">→</button>
812  </div>
813  <button class="nav-link active" onclick="showPage('public');_navDrawerClose()">ראשי</button>
814  <a href="/extension" class="nav-link">תוסף Chrome</a>
815  <a href="/about" class="nav-link">אודות</a>
816  <a href="/contact" class="nav-link">צור קשר</a>
817</div>
818
819<!-- PUBLIC -->
820<div class="page active" id="page-public">
821  <div class="hero">
822    <div class="hero-bg"></div>
823    <div class="hero-badge"><span></span>מופעל על ידי AI - מתעדכן כל שעה</div>
824    <h1>כל הקופונים.<br><em>נמצאים אוטומטית.</em></h1>
825    <p>הבינה המלאכותית שלנו סורקת קבוצות טלגרם, ניוזלטרים ואתרי מבצעים 24/7 - כדי שלא תפספסו אף הנחה לפני הרכישה.</p>
826  </div>
827  <div class="stats-row">
828    <div class="stat"><div class="stat-n" id="stat-total">—</div><div class="stat-l">קופונים במאגר</div></div>
829    <div class="stat"><div class="stat-n" id="stat-stores">—</div><div class="stat-l">חנויות במאגר</div></div>
830    <div class="stat"><div class="stat-n">כל שעה</div><div class="stat-l">עדכון אוטומטי</div></div>
831    <div class="stat"><div class="stat-n" id="stat-pct">—</div><div class="stat-l">מאומתים כעובדים</div></div>
832  </div>
833  <div style="padding:0 32px 48px;display:flex;flex-direction:column;align-items:center">
834    <div class="search-box" style="margin:0;width:100%">
835      <input type="text" id="pub-search" placeholder="חפש לפי אתר או מותג. לדוגמא: ksp.co.il או נייק" onin
835put="filterPub()" onkeydown="if(event.key==='Enter')aiSearch()"/>
836      <button id="search-btn" onclick="aiSearch()"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="6"/><line x1="15" y1="15" x2="21" y2="21"/><path fill="currentColor" stroke="none" d="M17 1l.8 2.4 2.4.8-2.4.8L17 7.4l-.8-2.4L13.8 4.2l2.4-.8z"/><path fill="currentColor" stroke="none" d="M21 9.5l.5 1.5 1.5.5-1.5.5-.5 1.5-.5-1.5-1.5-.5 1.5-.5z"/></svg>חיפוש AI</button>
837    </div>
838    <div class="search-mode-toggle">
839      <div class="mode-toggle-track">
840        <button class="mode-toggle-opt active" id="mode-opt-fast" onclick="setSearchMode('fast')" title="חיפוש מהיר במאגר שלנו — תגובה מיידית">⚡ חיפוש AI</button>
841        <button class="mode-toggle-opt" id="mode-opt-ai" onclick="setSearchMode('ai')" title="סורק את האינטרנט בזמן אמת — תוצאות מקסימליות">🔍 חיפוש יסודי <span class="mode-badge">בטא</span></button>
842      </div>
843    </div>
844  </div>
845
846  <div class="ai-banner" id="ai-banner"></div>
847
848  <!-- Loading screen (shown while searching) -->
849  <div id="mini-game-wrap" style="display:none;margin:-8px 20px 0;padding:24px 20px;background:var(--gray2);border:1px solid var(--gray3);border-top:none;border-radius:0">
850
851    <!-- Skeleton cards -->
852    <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px;margin-bottom:20px" id="skel-grid">
853      <!-- skeletons injected by JS -->
854    </div>
855
856    <!-- Tips carousel -->
857    <div style="background:var(--gray3);border-radius:14px;padding:16px 24px">
858      <div style="display:flex;align-items:center;justify-content:center;gap:14px">
859        <div id="tip-icon-main" style="font-size:26px;flex-shrink:0">💡</div>
860        <div id="tip-text-main" style="font-size:15px;color:var(--gray6);line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></div>
861      </div>
862      <div style="margin-top:12px;display:flex;justify-content:center">
863        <div style="width:80px;height:2px;background:var(--gray4);border-radius:2px;overflow:hidden">
864          <div id="tip-progress-bar" style="height:2px;background:var(--accent);border-radius:2px;width:0%"></div>
865        </div>
866      </div>
867    </div>
868  </div>
869  <!-- Search results (shown after search, below game) -->
870  <div id="search-results-wrap" style="display:none;margin:-1px 20px 28px;background:var(--gray2);border:1px solid var(--gray3);border-top:none;border-radius:0 0 16px 16px;padding:20px 0 8px">
871    <div class="coupon-grid" id="search-results-grid" style="padding:0 16px"></div>
872  </div>
873
874  <!-- ── Store homepage (Layout F) ── -->
875  <div class="pub-hp-wrap" style="max-width:1100px;margin:0 auto;padding:0 32px">
876    <div id="pub-store-daily" style="margin-bottom:32px"></div>
877    <div id="pub-store-filter" style="display:none"></div>
878    <div style="height:1px;background:var(--gray3);margin-bottom:28px"></div>
879    <div id="pub-store-grid" style="display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:24px"></div>
880    <div style="text-align:center;margin-bottom:40px">
881      <p style="color:var(--gray5);font-size:15px;text-align:center;margin:0">ועוד מאוד חנויות מובילות...</p>
882    </div>
883  </div>
884
885</div>
886
887<!-- ADMIN -->
888<div class="page" id="page-admin">
889  <div class="admin-layout">
890    <div class="admin-main">
891      <!-- Mobile menu toggle — hidden on desktop via CSS -->
892      <div id="mob-menu-bar" style="align-items:center;justify-content:space-between;margin-bottom:14px">
893        <button id="mob-menu-btn" onclick="openMobMenu()">☰</button>
894        <span id="mob-view-title" style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700">דשבורד</span>
895      </div>
896      <div id="view-dashboard">
897        <div class="admin-header">
898          <div class="admin-actions">
899            <button class="btn-sm btn-ghost" onclick="adminLogout()" title="התנתק מהניהול" style="color:#ff6b6b;border-color:rgba(255,107,107,0.3)">🚪 יציאה</button>
900          </div>
901          <div class="admin-title">דשבורד</div>
902        </div>
903        <div class="metrics">
904          <div class="metric" style="background:linear-gradient(135deg,#0d2b1a,var(--gray2))">
905            <span class="metric-icon">🟢</span>
906            <div class="metric-val" id="adm-active" style="color:#00e87a">—</div>
907            <div class="metric-label">קופונים פעילים</div>
908            <div class="metric-delta" id="adm-active-delta" style="color:#00e87a">טוען...</div>
909            <div class="metric-bar"><div class="metric-bar-fill" id="adm-active-bar" style="width:0%;background:#00e87a"></div></div>
910          </div>
911          <div class="metric" style="background:linear-gradient(135deg,#0d1f2b,var(--gray2))">
912            <span class="metric-icon">🏪</span>
913            <div class="metric-val" id="adm-stores" style="color:#63b3ed">—</div>
914            <div class="metric-label">חנויות במעקב</div>
915            <div class="metric-delta" id="adm-unknown-delta" style="color:var(--gray5)">טוען...</div>
916            <div class="metric-bar"><div class="metric-bar-fill" id="adm-stores-bar" style="width:60%;background:#63b3ed"></div></div>
917          </div>
918          <div class="metric" style="background:linear-gradient(135deg,#2b1f0a,var(--gray2))">
919            <span class="metric-icon">✅</span>
920            <div class="metric-val" id="adm-pct" style="color:#ffe45e">—</div>
921            <div class="metric-label">מאומתים כעובדים</div>
922            <div class="metric-delta" id="adm-pct-delta" style="color:var(--gray5)">טוען...</div>
923            <div class="metric-bar"><div class="metric-bar-fill" id="adm-pct-bar" style="width:0%;background:#ffe45e"></div></div>
924          </div>
925          <div class="metric" style="background:linear-gradient(135deg,#1f0d2b,var(--gray2))">
926            <span class="metric-icon">🏷️</span>
927            <div class="metric-val" id="adm-total" style="color:#b794f4">—</div>
928            <div class="metric-label">סה"כ קופונים</div>
929            <div class="metric-delta" id="adm-inactive-delta" style="color:var(--gray5)">טוען...</div>
930            <div class="metric-bar"><div class="metric-bar-fill" id="adm-total-bar" style="width:80%;background:#b794f4"></div></div>
931          </div>
932        </div>
933        <!-- ── Analytics panels — side by side ──────────────────────────────── -->
934        <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin:24px 0">
935
936          <!-- 📊 Usage Analytics -->
937          <div id="analytics-panel-slot"><div id="analytics-panel" style="padding:20px 24px;background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radiuslg)">
938            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px">
939              <span style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;color:var(--white);white-space:nowrap">📊 אנליטיקס שימוש</span>
940              <div style="display:flex;gap:3px;background:var(--gray3);border-radius:10px;padding:3px;flex-shrink:0;flex-wrap:wrap">
941                <button class="atab" data-period="all"    onclick="setAtab(this,'all')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">הכל</button>
942                <button class="atab" data-period="year"   onclick="setAtab(this,'year')"   style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">שנתי</button>
943                <button class="atab" data-period="month"  onclick="setAtab(this,'month')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">חודשי</button>
944                <button class="atab on" data-period="week" onclick="setAtab(this,'week')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:var(--gray4);color:var(--white)">שבועי</button>
945                <button class="atab" data-period="day"    onclick="setAtab(this,'day')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">יומי</button>
946                <button class="atab" data-period="custom" onclick="setAtab(this,'custom')" data-dp-trigger style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">📅</button>
947              </div>
948            </div>
949            <div id="analytics-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:10px;margin-bottom:16px"></div>
950            <div style="background:var(--gray3);border-radius:12px;padding:16px 18px">
951              <div style="font-size:12px;font-weight:600;color:var(--gray6);margin-bottom:12px">📈 פעילות לאורך זמן</div>
952              <div style="position:relative;height:180px;width:100%;direction:ltr">
952<canvas id="chart-activity"></canvas></div>
953            </div>
954            <div style="margin-top:14px;background:var(--gray3);border-radius:12px;padding:14px 16px">
955              <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;gap:8px;flex-wrap:wrap">
956                <div style="font-size:12px;font-weight:600;color:var(--gray6)">🔍 חיפושים נפוצים</div>
957                <div style="display:flex;gap:2px;background:var(--gray2);border-radius:8px;padding:2px" id="popsearch-period-bar"></div>
958              </div>
959              <div style="display:flex;gap:2px;background:var(--gray2);border-radius:8px;padding:2px;margin-bottom:10px;width:fit-content">
960                <button id="tsTab-unique"  onclick="setTopSearchTab('unique')"  style="padding:3px 10px;border:none;border-radius:6px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:var(--gray4);color:var(--white)">ייחודי</button>
961                <button id="tsTab-grouped" onclick="setTopSearchTab('grouped')" style="padding:3px 10px;border:none;border-radius:6px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">מאוחד</button>
962              </div>
963              <div id="top-searches"></div>
964            </div>
965          </div></div><!-- /analytics-panel-slot -->
966
967          <!-- ⚡ Search Speed -->
968          <div id="speed-panel-slot"><div id="speed-panel" style="padding:20px 24px;background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radiuslg)">
969            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px">
970              <span style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;color:var(--white);white-space:nowrap">⚡ זמני חיפוש</span>
971              <div style="display:flex;gap:3px;background:var(--gray3);border-radius:10px;padding:3px;flex-shrink:0;flex-wrap:wrap">
972                <button class="stab" data-period="all"    onclick="setStab(this,'all')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">הכל</button>
973                <button class="stab" data-period="year"   onclick="setStab(this,'year')"   style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">שנתי</button>
974                <button class="stab" data-period="month"  onclick="setStab(this,'month')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">חודשי</button>
975                <button class="stab on" data-period="week" onclick="setStab(this,'week')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:var(--gray4);color:var(--white)">שבועי</button>
976                <button class="stab" data-period="day"    onclick="setStab(this,'day')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">יומי</button>
977                <button class="stab" data-period="custom" onclick="setStab(this,'custom')" data-dp-trigger style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">📅</button>
978              </div>
979            </div>
980            <!-- Search mode toggle -->
981            <div style="display:flex;gap:4px;margin-bottom:14px;background:var(--gray3);border-radius:10px;padding:3px;width:fit-content">
982              <button id="smode-ai"   onclick="setSpeedMode('ai')"   style="padding:5px 14px;border:none;border-radius:8px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;background:var(--gray4);color:var(--white)">⚡ חיפוש AI</button>
983              <button id="smode-fast" onclick="setSpeedMode('fast')" style="padding:5px 14px;border:none;border-radius:8px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">🔍 חיפוש יסודי בטא</button>
984            </div>
985            <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:10px;margin-bottom:16px" id="speed-cards"></div>
986            <div style="background:var(--gray3);border-radius:12px;padding:16px 18px">
987              <div style="font-size:12px;font-weight:600;color:var(--gray6);margin-bottom:12px" id="speed-chart-label">⏱️ זמן חיפוש לאורך זמן (שניות)</div>
988              <div style="position:relative;height:180px;width:100%;direction:ltr">
988<canvas id="chart-speed"></canvas></div>
989            </div>
990            <div style="margin-top:14px;background:var(--gray3);border-radius:12px;padding:14px 16px">
991              <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;gap:8px;flex-wrap:wrap">
992                <div style="font-size:12px;font-weight:600;color:var(--gray6)" id="slowest-label">🐢 החיפושים האיטיים ביותר</div>
993                <div style="display:flex;gap:2px;background:var(--gray2);border-radius:8px;padding:2px" id="slowest-period-bar"></div>
994              </div>
995              <div id="speed-slowest"></div>
996            </div>
997          </div></div><!-- /speed-panel-slot -->
998
999          <!-- 🎫 Popup Analytics -->
1000          <div id="popup-panel-slot"><div id="popup-panel" style="padding:20px 24px;background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radiuslg)">
1001            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px">
1002              <span style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;color:var(--white);white-space:nowrap">🎫 פופאפ קופונים</span>
1003              <div style="display:flex;gap:3px;background:var(--gray3);border-radius:10px;padding:3px;flex-shrink:0;flex-wrap:wrap">
1004                <button class="ptab" data-period="all"    onclick="setPopupTab(this,'all')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">הכל</button>
1005                <button class="ptab" data-period="year"   onclick="setPopupTab(this,'year')"   style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">שנתי</button>
1006                <button class="ptab" data-period="month"  onclick="setPopupTab(this,'month')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">חודשי</button>
1007                <button class="ptab on" data-period="week" onclick="setPopupTab(this,'week')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:var(--gray4);color:var(--white)">שבועי</button>
1008                <button class="ptab" data-period="day"    onclick="setPopupTab(this,'day')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">יומי</button>
1009                <button class="ptab" data-period="custom" onclick="setPopupTab(this,'custom')" data-dp-trigger style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">📅</button>
1010              </div>
1011            </div>
1012            <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:10px;margin-bottom:16px" id="popup-cards"></div>
1013            <div style="background:var(--gray3);border-radius:12px;padding:14px 16px">
1014              <div style="font-size:12px;font-weight:600;color:var(--gray6);margin-bottom:10px">🏆 חנויות עם הכי הרבה פופאפים</div>
1015              <div id="popup-top-stores"></div>
1016            </div>
1017          </div></div><!-- /popup-panel-slot -->
1018
1019          <div id="ext-panel-slot"><div id="ext-panel" style="padding:20px 24px;background:var(--gray2);border:1px solid var(--gray3);border-radius:var(--radiuslg)">
1020            <!-- Time filter -->
1021            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-wrap:wrap;gap:8px">
1022              <div style="font-size:14px;font-weight:700;color:var(--white)">תוסף Chrome — נתוני שימוש</div>
1023              <div style="display:flex;gap:4px">
1024                <button class="etab" data-period="all"    onclick="setEtab(this,'all')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">הכל</button>
1025                <button class="etab" data-period="month"  onclick="setEtab(this,'month')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">חודשי</button>
1026                <button class="etab on" data-period="week" onclick="setEtab(this,'week')"  style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:var(--gray4);color:var(--white)">שבועי</button>
1027                <button class="etab" data-period="day"    onclick="setEtab(this,'day')"    style="padding:4px 10px;border:none;border-radius:8px;font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;background:transparent;color:var(--gray5)">יומי</button>
1028              </div>
1029            </div>
1030
1031            <!-- Two stat panels side by side -->
1032            <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px">
1033              <div style="background:var(--gray3);border-radius:10px;padding:14px">
1034                <div style="font-size:12px;font-weight:600;color:var(--gray5);margin-bottom:10px">🧩 פופאפ תוסף</div>
1035                <div id="ext-stats-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:8px"></div>
1036              </div>
1037              <div style="background:var(--gray3);border-radius:10px;padding:14px">
1038                <div style="font-size:12px;font-weight:600;color:var(--gray5);margin-bottom:10px">🛒 פופאפ סל קניות</div>
1039                <div id="cart-stats-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:8px"></div>
1040              </div>
1041            </div>
1042
1043            <!-- Top stores (combined, with paging) -->
1044            <div style="border-top:1px solid var(--gray3);padding-top:16px;margin-bottom:20px">
1045              <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
1046                <div style="font-size:12px;font-weight:600;color:var(--gray5)">🏪 חנויות מובילות — ביקורים</div>
1047                <div style="display:flex;align-items:center;gap:6px">
1048                  <button id="ext-stores-prev" onclick="extStoresPage(-1)" style="background:var(--gray3);border:none;color:var(--gray5);border-radius:6px;padding:2px 8px;cursor:pointer;font-size:13px">‹</button>
1049                  <span id="ext-stores-page-label" style="font-size:11px;color:var(--gray5)"></span>
1050                  <button id="ext-stores-next" onclick="extStoresPage(1)"  style="background:var(--gray3);border:none;color:var(--gray5);border-radius:6px;padding:2px 8px;cursor:pointer;font-size:13px">›</button>
1051                </div>
1052              </div>
1053              <div id="ext-top-stores" style="display:flex;flex-direction:column;gap:6px"></div>
1054            </div>
1055
1056            <!-- Comparison chart -->
1057            <div style="border-top:1px solid var(--gray3);padding-top:16px">
1058              <div style="font-size:12px;font-weight:600;color:var(--gray5);margin-bottom:14px">📊 השוואה — פופאפ תוסף vs סל קניות</div>
1059              <div style="display:flex;gap:16px;margin-bottom:12px">
1060                <div style="display:flex;align-items:center;gap:6px"><div style="width:10px;height:10px;border-radius:2px;background:#00e87a"></div><div style="font-size:11px;color:var(--gray5)">פופאפ תוסף</div></div>
1061                <div style="display:flex;align-items:center;gap:6px"><div style="width:10px;height:10px;border-radius:2px;background:#60a5fa"></div><div style="font-size:11px;color:var(--gray5)">סל קניות</div></div>
1062              </div>
1063              <div id="ext-comparison-chart"></div>
1064            </div>
1065
1066          </div></div><!-- /ext-panel-slot -->
1067
1068        </div><!-- end analytics grid -->
1069        <div class="sources-grid">
1070          <div class="source-card">
1071            <div class="source-top"><div class="source-status"><div class="source-dot dot-green"></div><span style="color:var(--accent)">פעיל</span></div><div class="source-name">חיפוש AI</div></div>
1072            <div class="source-stat" id="src-search-stat">טוען...</div>
1073            <div class="source-bar"><div class="source-fill" id="src-search-bar" style="width:0%"></div></div>
1074          </div>
1075          <div class="source-card">
1076            <div class="source-top"><div class="source-status"><div class="source-dot dot-green"></div><span style="color:var(--accent)">פעיל</span></div><div class="source-name">בסיס הנתונים</div></div>
1077            <div class="source-stat" id="src-db-stat">טוען...</div>
1078            <div class="source-bar"><div class="source-fill" id="src-db-bar" style="width:0%"></div></div>
1079          </div>
1080          <div class="source-card">
1081            <div class="source-top"><div class="source-status"><div class="source-dot" id="src-tg-dot" style="background:var(--gray4)"></div><span id="src-tg-status" style="color:var(--gray5)">טוען...</span></div><div class="source-name">טלגרם / ניוזלטרים</div></div>
1082            <div class="source-stat" id="src-tg-stat" style="color:var(--gray5)">טוען...</div>
1083            <div class="source-bar"><div class="source-fill" id="src-tg-bar" style="width:0%"></div></div>
1084          </div>
1085          <div class="source-card" onclick="_goInstagram()" style="cursor:pointer">
1086            <div class="source-top"><div class="source-status"><div class="source-dot" id="src-ig-dot" style="background:var(--gray4)"></div><span id="src-ig-status" style="color:var(--gray5)">טוען...</span></div><div class="source-name">אינסטגרם</div></div>
1087            <div class="source-stat" id="src-ig-stat" style="color:var(--gray5)">טוען...</div>
1088            <div class="source-bar"><div class="source-fill" id="src-ig-bar" style="width:0%"></div></div>
1089          </div>
1090        </div>
1091        <div class="table-wrap">
1092          <div class="table-header">
1093            <div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
1094              <input type="text" placeholder="חיפוש..." oninput="filterAdmin(this.value)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 12px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;width:160px;direction:rtl"/>
1095              <select onchange="admSort(this)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1096                <option value="newest">🆕 הכי חדש</option>
1097                <option value="oldest">🕰 הכי ישן</option>
1098                <option value="name">🔤 שם חנות</option>
1099                <option value="disc-pct">% הנחה באחוזים</option>
1100                <option value="disc-ils">
1100₪ הנחה במטבע</option>
1101                <option value="source">🌐 מקור</option>
1102              </select>
1103              <select onchange="admCat(this)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1104                <option value="">כל הקטגוריות</option>
1105                <option value="Fashion">אופנה</option>
1106                <option value="Tech">טכנולוגיה</option>
1107                <option value="Food">אוכל</option>
1108                <option value="Travel">נסיעות</option>
1109                <option value="Vacation">חופשה</option>
1110                <option value="Home">בית</option>
1111                <option value="Beauty">יופי</option>
1112                <option value="Health">בריאות</option>
1113                <option value="Kids">ילדים</option>
1114                <option value="Sports">ספורט</option>
1115                <option value="Leisure">פנאי</option>
1116                <option value="Finance">פיננסי</option>
1117                <option value="Other">אחר</option>
1118              </select>
1119            </div>
1120            <div class="table-title">חילוצים אחרונים</div>
1121          </div>
1122          <table><thead><tr>
1123            <th style="width:36px;color:var(--gray5)">#</th><th style="width:28px"></th><th>חנות</th><th>קוד</th><th>הנחה</th><th class="col-notes">הערות</th><th>קטגוריה</th><th class="col-source">מקור</th><th class="col-source">איך נמצא</th><th>נמצא</th><th>פורסם</th><th>פעולות</th>
1124          </tr></thead><tbody id="admin-tbody"></tbody></table>
1125          <div id="admin-pagination"></div>
1126        </div>
1127      </div>
1128      <div id="view-coupons" style="display:none">
1129        <div class="admin-header">
1130          <div class="admin-actions"><button class="btn-sm btn-primary" onclick="openAddCouponModal()">+ הוסף ידנית</button></div>
1131          <div class="admin-title">כל הקופונים</div>
1132        </div>
1133        <div class="table-wrap">
1134          <div class="table-header">
1135            <div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
1136              <input type="text" placeholder="חיפוש..." oninput="filterAdmin(this.value)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 12px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;width:160px;direction:rtl"/>
1137              <select onchange="admSort(this)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1138                <option value="newest">🆕 הכי חדש</option>
1139                <option value="oldest">🕰 הכי ישן</option>
1140                <option value="name">🔤 שם חנות</option>
1141                <option value="disc-pct">% הנחה באחוזים</option>
1142                <option value="disc-ils">₪ הנחה במטבע</option>
1143                <option value="source">🌐 מקור</option>
1144              </select>
1145              <select onchange="admCat(this)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1146                <option value="">כל הקטגוריות</option>
1147                <option value="Fashion">אופנה</option><option value="Tech">טכנולוגיה</option>
1148                <option value="Food">אוכל</option><option value="Travel">נסיעות</option>
1149                <option value="Vacation">חופשה</option><option value="Home">בית</option>
1150                <option value="Beauty">יופי</option><option value="Health">בריאות</option>
1151                <option value="Kids">ילדים</option><option value="Sports">ספורט</option>
1152                <option value="Leisure">פנאי</option><option value="Finance">פיננסי</option>
1153                <option value="Other">אחר</option>
1154              </select>
1155              <select onchange="admDisc(this)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1156                <option value="">כל המקורות</option>
1157                <option value="primary">🔍 חיפוש משתמש</option>
1158                <option value="secondary">🔄 חיפוש משני</option>
1159                <option value="auto">🤖 אוטומטי</option>
1160                <option value="telegram">✈️ טלגרם</option>
1161              </select>
1162              <select onchange="admGeo(this)" style="background:var(--gray3);border:none;border-radius:8px;padding:6px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1163                <option value="all">🌐 כל האתרים</option>
1164                <option value="il">🇮🇱 ישראלי</option>
1165                <option value="intl">🌍 בינלאומי</option>
1166              </select>
1167            </div>
1168            <div class="table-title" id="adm-table-title">— קופונים</div>
1169          </div>
1170          <table><thead><tr><th style="width:36px;color:var(--gray5)">#</th><th style="width:28px"></th><th>חנות</th><th>קוד</th><th>הנחה</th><th>הערות</th><th>קטגוריה</th><th>מקור</th><th>איך נמצא</th><th>נמצא</th><th>פורסם</th><th>פעולות</th></tr></thead><tbody id="coupons-tbody"></tbody></table>
1171          <div id="coupons-pagination"></div>
1172        </div>
1173      </div>
1174      <div id="view-sources" style="display:none">
1175        <div class="admin-header">
1176          <div class="admin-actions"><button class="btn-sm btn-primary" onclick="addSourcePrompt('telegram')">+ הוסף ערוץ</button></div>
1177          <div class="admin-title">מקורות טלגרם</div>
1178        </div>
1179
1180        <!-- Userbot listener card -->
1181        <div id="userbot-card" style="margin:0 0 18px;background:var(--gray2);border:1px solid var(--gray3);border-radius:12px;padding:16px 20px;display:flex;align-items:center;justify-content:space-between;gap:16px">
1182          <div style="display:flex;align-items:center;gap:14px">
1183            <div id="userbot-dot" style="width:10px;height:10px;border-radius:50%;background:var(--gray4);flex-shrink:0;transition:background 0.3s"></div>
1184            <div>
1185              <div style="font-size:14px;font-weight:600;color:var(--white)">Telegram Listener</div>
1186              <div id="userbot-subtitle" style="font-size:12px;color:var(--gray5);margin-top:2px">טוען...</div>
1187            </div>
1188          </div>
1189          <div style="display:flex;align-items:center;gap:10px">
1190            <div id="userbot-stats" style="font-size:11px;color:var(--gray5);text-align:left"></div>
1191            <!-- Toggle switch -->
1192            <label style="display:flex;align-items:center;gap:8px;
1192cursor:pointer;user-select:none">
1193              <span id="userbot-toggle-label" style="font-size:12px;color:var(--gray5)">כבוי</span>
1194              <div style="position:relative;width:44px;height:24px" onclick="toggleUserbot()">
1195                <input type="checkbox" id="userbot-toggle" style="opacity:0;width:0;height:0;position:absolute">
1196                <div id="userbot-track" style="position:absolute;inset:0;border-radius:12px;background:var(--gray4);transition:background 0.2s;cursor:pointer"></div>
1197                <div id="userbot-thumb" style="position:absolute;top:3px;right:3px;width:18px;height:18px;border-radius:50%;background:white;transition:transform 0.2s;box-shadow:0 1px 4px rgba(0,0,0,0.3)"></div>
1198              </div>
1199            </label>
1200          </div>
1201        </div>
1202        <div class="table-wrap">
1203          <div class="table-header">
1204            <div class="table-title">ערוצים מחוברים</div>
1205            <select id="tg-sort" onchange="_renderTelegramTable(_srcData)" style="background:var(--gray3);border:none;border-radius:8px;padding:5px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1206              <option value="count">↓ הכי פעיל</option>
1207              <option value="recent">↓ עדכני</option>
1208              <option value="name">🔤 שם</option>
1209            </select>
1210          </div>
1211          <table><thead><tr><th style="width:36px;color:var(--gray5)">#</th><th>שם ערוץ</th><th>סה״כ קופונים</th><th style="color:#ffe45e">חדשים (7 ימים)</th><th>קופון אחרון</th><th>סטטוס</th><th>פעולות</th></tr></thead>
1212          <tbody id="tg-sources-tbody">
1213            <tr><td colspan="5" style="color:var(--gray5);text-align:center;padding:20px">טוען...</td></tr>
1214          </tbody></table>
1215        </div>
1216      </div>
1217
1218      <div id="view-coupon-sites" style="display:none">
1219        <div class="admin-header">
1220          <div class="admin-actions"><button class="btn-sm btn-primary" onclick="addTrustedSitePrompt()">+ הוסף אתר</button></div>
1221          <div class="admin-title">אתרי קופונים בלבד</div>
1222        </div>
1223        <div class="table-wrap">
1224          <div class="table-header">
1225            <div class="table-title">אתרים מהימנים לסריקת קופונים</div>
1226          </div>
1227          <table><thead><tr>
1228            <th style="width:36px;color:var(--gray5)">#</th>
1229            <th>שם</th>
1230            <th>כתובת</th>
1231            <th>תבנית URL לחנות</th>
1232            <th>חילוץ</th>
1233            <th>סטטוס</th>
1234            <th>פעולות</th>
1235          </tr></thead>
1236          <tbody id="coupon-sites-tbody">
1237            <tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:20px">טוען...</td></tr>
1238          </tbody></table>
1239        </div>
1240      </div>
1241
1242      <div id="view-automation-scans" style="display:none">
1243        <div class="admin-header">
1244          <div class="admin-actions"><button class="btn-sm btn-ghost" onclick="loadAutomationScans()">↺ רענן</button></div>
1245          <div class="admin-title">סריקות אוטומטיות</div>
1246        </div>
1247
1248        <!-- Job B: Trusted coupon sites -->
1249        <div class="table-wrap" style="margin-bottom:24px">
1250          <div class="table-header">
1251            <div class="table-title">🏷 Job B — סריקת אתרי קופונים (פעם בלילה)</div>
1252            <div style="display:flex;align-items:center;gap:14px">
1253              <span id="job-b-status" style="font-size:12px;color:var(--gray5)"></span>
1254              <label style="display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none">
1255                <span id="job-b-toggle-label" style="font-size:12px;color:var(--gray5)">טוען…</span>
1256                <div id="job-b-toggle-wrap" style="position:relative;width:44px;height:24px;opacity:0.4;pointer-events:none" onclick="toggleJobB()">
1257                  <input type="checkbox" id="job-b-toggle" style="opacity:0;width:0;height:0;position:absolute">
1258                  <div id="job-b-toggle-track" style="position:absolute;inset:0;border-radius:12px;background:var(--gray4);transition:background 0.2s;cursor:pointer"></div>
1259                  <div id="job-b-toggle-thumb" style="position:absolute;top:3px;right:3px;width:18px;height:18px;border-radius:50%;background:white;transition:transform 0.2s;box-shadow:0 1px 4px rgba(0,0,0,0.3)"></div>
1260                </div>
1261              </label>
1262            </div>
1263          </div>
1264
1265          <!-- Job B Config Panel -->
1266          <div style="display:flex;flex-wrap:wrap;gap:16px;padding:12px 16px 16px;border-bottom:1px solid var(--gray3)">
1267            <label style="display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--gray5);min-width:140px">
1268              <span>🔤 מקס׳ טוקנים לתגובה</span>
1269              <input id="cfg-max-tokens" type="number" min="100" max="8192" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;color:var(--white);font-size:13px;width:100%" value="1500">
1270            </label>
1271            <label style="display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--gray5);min-width:140px">
1272              <span>📄 מקס׳ עמודים לאתר</span>
1273              <input id="cfg-max-urls" type="number" min="5" max="500" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;color:var(--white);font-size:13px;width:100%" value="50">
1274            </label>
1275            <label style="display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--gray5);min-width:140px">
1276              <span>🔗 IL Coupon מקס׳ עמודים</span>
1277              <input id="cfg-il-coupon-max" type="number" min="5" max="500" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;color:var(--white);font-size:13px;width:100%" value="50">
1278            </label>
1279            <label style="display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--gray5);min-width:140px">
1280              <span>🔁 תקרה לאתר (קריאות)</span>
1281              <input id="cfg-per-site-cap" type="number" min="1" max="100" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;color:var(--white);font-size:13px;width:100%" value="15" oninput="updateJobBEstCost()">
1282            </label>
1283            <label style="display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--gray5);min-width:140px">
1284              <span>💵 תקרת עלות ($)</span>
1285              <input id="cfg-max-cost" type="number" min="0.5" max="50" step="0.5" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;color:var(--white);font-size:13px;width:100%" value="5" oninput="updateJobBEstCost()">
1286            </label>
1287            <div style="display:flex;flex-direction:column;justify-content:flex-end;gap:4px">
1288              <button class="btn-sm btn-primary" onclick="saveJobBConfig()" id="job-b-config-save-btn">שמור</button>
1289              <div id="job-b-config-status" style="font-size:11px;color:var(--gray5);display:none"></div>
1290              <div id="job-b-est-cost" style="font-size:11px;color:var(--gray5)"></div>
1291            </div>
1292          </div>
1293
1294          <div style="padding:12px 16px;font-size:13px;color:var(--gray5);line-height:1.7">
1295            סורק כל אתר ב<b style="color:var(--white)">אתרי קופונים בלבד</b> פעם בלילה ב-<b style="color:var(--white)">02:00 שעון ישראל</b>.<br>
1296            כל אתר נסרק פעם אחת — לא לפי חנות. מחלץ קופונים מכל הדפים הפעילים.
1297          </div>
1298          <div id="job-b-log" style="margin:0 16px 14px;padding:12px 16px;background:var(--gray2);border:1px solid var(--gray3);border-radius:10px;font-size:12px;display:none">
1299            <div style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:8px">
1300              <span><span style="color:var(--gray5)">ריצה אחרונה: </span><b id="job-b-log-time" style="color:var(--white)">—</b></span>
1301              <span><span style="color:var(--gray5)">סטטוס: </span><b id="job-b-log-status">—</b></span>
1302              <span><span style="color:var(--gray5)">אתרים שנסרקו: </span><b id="job-b-log-sites" style="color:var(--accent)">—</b></span>
1303              <span><span style="color:var(--gray5)">קופונים שנמצאו: </span><b id="job-b-log-coupons" style="color:var(--accent)">—</b></span>
1304              <span><span style="color:var(--gray5)">עלות אמש: </span><b id="job-b-log-cost" style="color:#fc8181">—</b></span>
1305            </div>
1306            <div id="job-b-log-errors" style="display:none">
1307              <div style="color:#fc8181;font-weight:600;margin-bottom:4px">שגיאות:</div>
1308              <ul id="job-b-log-errors-list" style="margin:0;padding-right:16px;color:#fc8181;line-height:1.8"></ul>
1309            </div>
1310          </div>
1311          <table><thead><tr>
1312            <th>#</th><th>אתר</th><th>שיטה</th>
1313            <th style="color:var(--gray5)">עמודים נסרקו</th>
1314            <th style="color:#ffe45e">קופונים חדשים</th>
1315            <th style="color:var(--gray5)">כבר קיים</th>
1316            <th>שגיאות</th>
1317            <th style="color:#f6ad55">טוקנים in/out</th>
1318            <th style="color:#fc8181">עלות $</th>
1319            <th>סטטוס</th>
1320          </tr></thead>
1321          <tbody id="job-b-tbody"><tr><td colspan="10" style="color:var(--gray5);text-align:center;padding:16px">טוען...</td></tr></tbody></table>
1322          <div style="padding:10px 16px 14px">
1323            <div style="font-size:11px;color:var(--gray5);margin-bottom:6px">היסטוריה (90 יום)</div>
1324            <div id="job-b-history" style="color:var(--gray5);font-size:12px">טוען...</div>
1325          </div>
1326        </div>
1327
1328        <!-- Job C: Per-store DDG -->
1329        <div class="table-wrap">
1330          <div class="table-header">
1331            <div class="table-title">🔍 Job C — סריקת חנויות DDG (פעם בלילה, 60 שנ׳ בין חנות)</div>
1332            <span id="job-c-status" style="font-size:12px;color:var(--gray5)"></span>
1333          </div>
1334          <div style="padding:12px 16px;font-size:13px;color:var(--gray5);line-height:1.7">
1335            מריץ <code style="background:var(--gray3);padding:2px 6px;border-radius:4px">discover(store)</code> לכל חנות פעילה, מסודר לפי <b style="color:var(--white)">last_scanned_at</b> — הישנות ביותר קודם.<br>
1336            מרווח של <b style="color:var(--white)">60 שניות</b> בין כל חנות למניעת חסימת DDG.
1337          </div>
1338          <div id="job-c-log" style="margin:0 16px 14px;padding:12px 16px;background:var(--gray2);border:1px solid var(--gray3);border-radius:10px;font-size:12px;display:none">
1339            <div style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:8px">
1340              <span><span style="color:var(--gray5)">ריצה אחרונה: </span><b id="job-c-log-time" style="color:var(--white)">—</b></span>
1341              <span><span style="color:var(--gray5)">סטטוס: </span><b id="job-c-log-status">—</b></span>
1342              <span><span style="color:var(--gray5)">חנויות שנסרקו: </span><b id="job-c-log-stores" style="color:var(--accent)">—</b></span>
1343              <span><span style="color:var(--gray5)">קופונים שנמצאו: </span><b id="job-c-log-coupons" style="color:var(--accent)">—</b></span>
1344            </div>
1345            <div id="job-c-log-errors" style="display:none">
1346              <div style="color:#fc8181;font-weight:600;margin-bottom:4px">שגיאות:</div>
1347              <ul id="job-c-log-errors-list" style="margin:0;padding-right:16px;color:#fc8181;line-height:1.8"></ul>
1348            </div>
1349          </div>
1350          <div style="padding:0 16px 14px;display:flex;gap:24px;flex-wrap:wrap">
1351            <div style="font-size:13px"><span style="color:var(--gray5)">סה״כ חנויות פעילות: </span><b id="job-c-total" style="color:var(--white)">—</b></div>
1352            <div style="font-size:13px"><span style="color:var(--gray5)">נסרקו היום: </span><b id="job-c-scanned-today" style="color:var(--accent)">—</b></div>
1353            <div style="font-size:13px"><span style="color:var(--gray5)">ממתינות לסריקה: </span><b id="job-c-pending" style="color:#f6c90e">—</b></div>
1354            <div style="font-size:13px"><span style="color:var(--gray5)">הבאה בתור: </span><b id="job-c-next" style="color:var(--gray5)">—</b></div>
1355          </div>
1356          <table><thead><tr>
1357            <th>#</th>
1358            <th>חנות</th>
1359            <th>סריקה אחרונה</th>
1360            <th style="color:var(--accent)">סה״כ קופונים</th>
1361            <th style="color:var(--gray5)">לפני סריקה</th>
1362            <th style="color:#ffe45e">קופונים חדשים</th>
1363            <th>סטטוס</th>
1364          </tr></thead>
1365          <tbody id="job-c-tbody"><tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:16px">טוען...</td></tr></tbody></table>
1366
1367          <!-- Daily summary -->
1368          <div style="margin:16px 0 0">
1369            <div style="font-size:12px;font-weight:600;color:var(--gray6);margin-bottom:10px">📅 היסטוריה יומית</div>
1370            <div id="job-c-history" style="color:var(--gray5);font-size:12px">טוען...</div>
1371          </div>
1372        </div>
1373      </div>
1374
1375      <div id="view-web" style="display:none">
1376        <div class="admin-header">
1377          <div class="admin-actions">
1378            <button class="btn-sm btn-primary" onclick="addSourcePrompt('web')">+ הוסף אתר</button>
1379          </div>
1380          <div class="admin-title">אתרים שנסרקו</div>
1381        </div>
1382        <!-- IL/International filter -->
1383        <div style="display:flex;gap:6px;padding:0 0 14px;flex-wrap:wrap">
1384          <button class="sort-tab on" id="web-geo-all"   onclick="_setWebGeo('all',this)"  >הכל</button>
1385          <button class="sort-tab"   id="web-geo-il"    onclick="_setWebGeo('il',this)"   >ישראלי 🇮🇱</button>
1386          <button class="sort-tab"   id="web-geo-intl"  onclick="_setWebGeo('intl',this)" >בינלאומי 🌍</button>
1387        </div>
1388        <div class="table-wrap">
1389          <div class="table-header">
1390            <div class="table-title">אתרים במעקב</div>
1391            <select id="web-sort" onchange="WEB.page=0;_renderWebTable(_srcData)" style="background:var(--gray3);border:none;border-radius:8px;padding:5px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1392              <option value="new">🆕 חדש קודם</option>
1393              <option value="count">↓ הכי פעיל</option>
1394              <option value="recent">↓ עדכני</option>
1395              <option value="name">🔤 שם</option>
1396            </select>
1397          </div>
1398          <table><thead><tr><th style="width:36px;color:var(--gray5)">#</th><th>אתר</th><th>קופונים שנמצאו</th><th>סריקה אחרונה</th><th>תאריך הוספה</th><th>נבדק</th><th>פעולות</th></tr></thead>
1399          <tbody id="web-sources-tbody">
1400            <tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:20px">טוען...</td></tr>
1401          </tbody></table>
1402          <div id="web-pagination"></div>
1403        </div>
1404      </div>
1405
1406      <div id="view-instagram" style="display:none">
1407        <div class="admin-header">
1408          <div class="admin-actions">
1409            <button class="btn-sm btn-ghost" onclick="igTestLogin(this)">🔌 בדוק חיבור</button>
1410            <button class="btn-sm btn-primary" id="ig-scan-all-btn" onclick="igScanAll(this)">📸 סרוק הכל</button>
1411          </div>
1412          <div class="admin-title">אינפלואנסרים</div>
1413        </div>
1414        <div class="table-wrap">
1415          <div class="table-header">
1416            <div class="table-title" id="ig-table-title">טוען פרופילים...</div>
1417            <select id="ig-sort" onchange="_renderInstagramTable()" style="background:var(--gray3);border:none;border-radius:8px;padding:5px 10px;font-size:12px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
1418              <option value="count">↓ הכי פעיל</option>
1419              <option value="name">🔤 שם</option>
1420              <option value="recent">↓ עדכני</option>
1421            </select>
1422          </div>
1423          <table><thead><tr>
1424            <th style="width:36px;color:var(--gray5)">#</th>
1425            <th>שם / הנדל</th>
1426            <th>קופונים שנמצאו</th>
1427            <th>סריקה אחרונה</th>
1428            <th>סטטוס</th>
1429            <th>פעולות</th>
1430          </tr></thead>
1431          <tbody id="ig-sources-tbody">
1432            <tr><td colspan="6" style="color:var(--gray5);text-align:center;padding:24px">טוען...</td></tr>
1433          </tbody></table>
1434        </div>
1435      </div>
1436
1437      <div id="view-no-url" style="display:none">
1438        <div class="admin-header">
1439          <div class="admin-actions">
1440            <button class="btn-sm btn-ghost" onclick="loadNoUrlCoupons()">↺ רענן</button>
1441          </div>
1442          <div class="admin-title">⚠️ אין אתר יעד</div>
1443        </div>
1444        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
1445          קופונים שנשמרו ממקורות שאין לנו דומיין עבורם — הכפתור "הצג מבצע באתר" מוסתר.
1446          הוסף דומיין לכל חנות כדי לתקן, או מחק אם הנתון שגוי.
1447        </div>
1448        <div style="display:flex;gap:6px;padding:0 0 14px;flex-wrap:wrap">
1449          <button class="sort-tab nourl-geo-btn on"   onclick="_noUrlSetGeo('all',this)">הכל</button>
1450          <button class="sort-tab nourl-geo-btn"      onclick="_noUrlSetGeo('il',this)">ישראלי 🇮🇱</button>
1451          <button class="sort-tab nourl-geo-btn"      onclick="_noUrlSetGeo('intl',this)">בינלאומי 🌍</button>
1452        </div>
1453        <div class="table-wrap">
1454          <table id="nourl-table">
1455            <thead><tr>
1456              <th style="width:36px;color:var(--gray5)">#</th>
1457              <th>חנות + קופונים</th>
1458              <th style="min-width:185px">דומיין (לתיקון)</th>
1459              <th colspan="4">פעולות</th>
1460            </tr></thead>
1461            <tbody id="nourl-tbody">
1462              <tr><td colspan="7" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
1463            </tbody>
1464          </table>
1465          <div id="nourl-pagination"></div>
1466        </div>
1467      </div>
1468
1469      <div id="view-newsletters" style="display:none">
1470        <div class="admin-header">
1471          <div class="admin-actions">
1472            <button class="btn-sm btn-ghost" onclick="scanNewsletters(this,7)">📬 סרוק 7 ימים</button>
1473            <button class="btn-sm btn-ghost" onclick="scanNewsletters(this,30)">📬 סרוק 30 ימים</button>
1474            <button class="btn-sm btn-ghost" onclick="loadNewsletterStores(true)">↺ רענן רשימה</button>
1475          </div>
1476          <div class="admin-title">✉️ ניוזלטרים</div>
1477        </div>
1478        <div style="padding:20px 24px 8px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px">
1479          <div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
1480            <div style="font-size:13px;font-weight:600;color:var(--white)">📧 <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aecdc1dbdec1c0cfc79adbeec9c3cfc7c280cdc1c3">[email&#160;protected]</a></div>
1481            <div style="display:inline-flex;align-items:center;gap:5px;background:rgba(0,232,122,0.12);border:1px solid rgba(0,232,122,0.3);border-radius:20px;padding:3px 10px;font-size:11px;color:var(--accent)">
1482              <span style="width:6px;height:6px;border-radius:50%;background:var(--accent);display:inline-block;animation:pulse 2s infinite"></span>
1483              סורק אוטומטי · כל
1484              <select id="nl-interval-select" onchange="setNlInterval(this)"
1485                style="background:transparent;border:none;color:var(--accent);font-size:11px;cursor:pointer;outline:none;padding:0 2px">
1486                <option value="900">15 דקות</option>
1487                <option value="1800">30 דקות</option>
1488                <option value="3600" selected>שעה</option>
1489                <option value="7200">2 שעות</option>
1490                <option value="21600">6 שעות</option>
1491                <option value="43200">12 שעות</option>
1492                <option value="86400">24 שעות</option>
1493              </select>
1494            </div>
1495          </div>
1496          <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">
1497            <button id="nl-filter-all"  class="sort-tab on" onclick="_nlFilter('all',this)">הכל</button>
1498            <button id="nl-filter-il"   class="sort-tab" onclick="_nlFilter('il',this)">🇮🇱 ישראלי</button>
1499            <button id="nl-filter-intl" class="sort-tab" onclick="_nlFilter('intl',this)">🌍 בינלאומי</button>
1500            <span style="width:1px;height:20px;background:var(--gray3);margin:0 2px"></span>
1501            <button id="nl-filter-reg"  class="sort-tab" onclick="_nlFilter('reg',this)">✅ רשומים</button>
1502            <button id="nl-filter-unreg" class="sort-tab" onclick="_nlFilter('unreg',this)">❌ לא רשומים</button>
1503            <span style="width:1px;height:20px;background:var(--gray3);margin:0 2px"></span>
1504            <select id="nl-sort-sel" onchange="_nlSetSortKey(this.value)" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 8px;font-size:12px;cursor:pointer;outline:none;direction:rtl">
1505              <option value="status">מיין: סטטוס</option>
1506              <option value="name">מיין: שם</option>
1507              <option value="emails">מיין: מיילים</option>
1508              <option value="coupons">מיין: קופונים</option>
1509              <option value="date">מיין: תאריך</option>
1510            </select>
1511            <button id="nl-sort-dir-btn" onclick="_nlToggleDir()" title="הפך כיוון מיון" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 9px;font-size:13px;cursor:pointer;line-height:1">↑</button>
1512          </div>
1513        </div>
1514        <div id="nl-stats-bar" style="padding:0 24px 12px;font-size:12px;color:var(--gray5)">טוען...</div>
1515        <div id="nl-result" style="display:none;margin:0 24px 12px;background:var(--gray2);border:1px solid var(--gray3);border-radius:10px;padding:14px 18px;direction:rtl">
1516          <div id="nl-result-text" style="font-size:13px;color:var(--white)"></div>
1517        </div>
1518        <div class="table-wrap" style="padding:0 24px 16px">
1519          <table id="nl-stores-table" style="width:100%;border-collapse:collapse">
1520            <thead>
1521              <tr style="border-bottom:1px solid var(--gray3)">
1522                <th style="text-align:center;padding:10px 8px;font-size:12px;color:var(--gray5);font-weight:500;width:36px">#</th>
1523                <th onclick="_nlSort('name')"    style="text-align:right;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500;cursor:pointer;user-select:none">חנות ↕</th>
1524                <th onclick="_nlSort('status')"  style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500;cursor:pointer;user-select:none">סטטוס ↕</th>
1525                <th onclick="_nlSort('emails')"  style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500;cursor:pointer;user-select:none">מיילים ↕</th>
1526                <th onclick="_nlSort('coupons')" style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500;cursor:pointer;user-select:none">קופונים ↕</th>
1527                <th onclick="_nlSort('date')"    style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500;cursor:pointer;user-select:none">מייל אחרון ↕</th>
1528                <th style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">שותפות</th>
1529                <th style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">פעולות</th>
1530              </tr>
1531            </thead>
1532            <tbody id="nl-stores-tbody">
1533              <tr><td colspan="8" style="text-align:center;padding:40px;color:var(--gray5)">טוען...</td></tr>
1534            </tbody>
1535          </table>
1536        </div>
1537        <div id="nl-pagination" style="display:flex;align-items:center;justify-content:center;gap:6px;margin:0 24px 32px;flex-wrap:wrap"></div>
1538      </div>
1539
1540      <div id="view-affiliate" style="display:none">
1541        <div class="admin-header">
1542          <div class="admin-actions">
1543            <button class="btn-sm btn-ghost" onclick="_affRender()">↺ רענן</button>
1544          </div>
1545          <div class="admin-title">🤝 תוכניות שותפים</div>
1546        </div>
1547        <div id="aff-stats-bar" style="padding:12px 24px 0;font-size:12px;color:var(--gray5)"></div>
1548        <div style="padding:10px 24px 6px;display:flex;gap:6px;flex-wrap:wrap;align-items:center">
1549          <button id="aff-f-all"  class="sort-tab on"  onclick="_affSetFilter('all',this)">הכל</button>
1550          <button id="aff-f-net"  class="sort-tab"     onclick="_affSetFilter('network',this)">🌐 רשתות</button>
1551          <button id="aff-f-dir"  class="sort-tab"     onclick="_affSetFilter('direct',this)">🎯 ישירות</button>
1552          <button id="aff-f-il"   class="sort-tab"     onclick="_affSetFilter('il',this)">🇮🇱 ישראלי</button>
1553          <span style="width:1px;height:20px;background:var(--gray3);margin:0 2px"></span>
1554          <button id="aff-f-reg"  class="sort-tab"     onclick="_affSetFilter('registered',this)">✅ רשומים</button>
1555          <button id="aff-f-pend" class="sort-tab"     onclick="_affSetFilter('pending',this)">⏳ בהמתנה</button>
1556          <button id="aff-f-none" class="sort-tab"     onclick="_affSetFilter('none',this)">❌ לא התחלתי</button>
1557          <span style="width:1px;height:20px;background:var(--gray3);margin:0 2px"></span>
1558          <select id="aff-sort-sel" onchange="_affSetSortKey(this.value)" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 8px;font-size:12px;cursor:pointer;outline:none;direction:rtl">
1559            <option value="status">מיין: סטטוס</option>
1560            <option value="name">מיין: שם</option>
1561            <option value="commission">מיין: עמלה</option>
1562          </select>
1563          <button id="aff-sort-dir-btn" onclick="_affToggleDir()" title="הפך כיוון מיון" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 9px;font-size:13px;cursor:pointer;line-height:1">↑</button>
1564        </div>
1565        <div class="table-wrap" style="padding:12px 24px 32px">
1566          <table style="width:100%;border-collapse:collapse">
1567            <thead>
1568              <tr style="border-bottom:1px solid var(--gray3)">
1569                <th style="text-align:right;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">תוכנית</th>
1570                <th style="text-align:right;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">חנויות מכוסות</th>
1571                <th style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">עמלה</th>
1572                <th style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">סטטוס</th>
1573                <th style="text-align:center;padding:10px 12px;font-size:12px;color:var(--gray5);font-weight:500">פעולה</th>
1574              </tr>
1575            </thead>
1576            <tbody id="aff-tbody"></tbody>
1577          </table>
1578        </div>
1579      </div>
1580
1581      <div id="view-pending" style="display:none">
1582        <div class="admin-header">
1583          <div class="admin-actions">
1584            <button class="btn-sm btn-ghost" onclick="loadPendingView()">↺ רענן</button>
1585            <button class="btn-sm btn-ghost" id="backfill-btn" onclick="runBackfill(this)" title="סרוק קופונים קיימים עם סטטוס unknown ממקורות לא מוכרים והעבר אותם לבדיקה">🔍 זהה מקורות חדשים</button>
1586            <span style="display:flex;gap:4px;margin-right:8px;border-right:1px solid var(--gray3);padding-right:10px">
1587              <button id="pending-geo-all" class="btn-sm" style="background:var(--accent);color:#000" onclick="_setPendingGeo('all',this)">הכל</button>
1588              <button id="pending-geo-il"  class="btn-sm btn-ghost" onclick="_setPendingGeo('il',this)">🇮🇱 ישראל</button>
1589              <button id="pending-geo-int" class="btn-sm btn-ghost" onclick="_setPendingGeo('int',this)">🌍 בינלאומי</button>
1590            </span>
1591          </div>
1592          <div class="admin-title">⏳ קופונים לבדיקה</div>
1593        </div>
1594        <div class="table-wrap">
1595          <div class="table-header">
1596            <div class="table-title" id="pending-table-title">טוען...</div>
1597          </div>
1598          <table><thead><tr>
1599            <th style="width:36px;color:var(--gray5)">#</th>
1600            <th>דומיין מקור</th>
1601            <th style="text-align:center">סוג מקור</th>
1602            <th style="text-align:center">מספר קופונים</th>
1603            <th>חנויות לדוגמה</th>
1604            <th>דוגמאות קודים</th>
1605            <th>פעולות</th>
1606          </tr></thead>
1607          <tbody id="pending-tbody">
1608            <tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:24px">טוען...</td></tr>
1609          </tbody></table>
1610        </div>
1611      </div>
1612
1613        <!-- ── Users view ─────────────────────────────────────── -->
1614        <div id="view-users" class="admin-view" style="display:none">
1615          <div class="admin-header">
1616            <div class="admin-actions">
1617              <button class="btn-sm btn-primary" onclick="_openAddUserModal()">+ הוסף משתמש</button>
1618            </div>
1619            <div class="admin-title">משתמשים</div>
1620          </div>
1621          <div style="background:var(--gray2);border:1px solid var(--gray3);border-radius:12px;overflow:hidden;margin-bottom:24px">
1622            <table style="width:100%;border-collapse:collapse">
1623              <thead><tr style="border-bottom:1px solid var(--gray3)">
1624                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">#</th>
1625                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">שם משתמש</th>
1626                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">תפקיד</th>
1627                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">נוצר</th>
1628                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">כניסה אחרונה</th>
1629                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">IP</th>
1630                <th style="padding:12px 16px;text-align:right;font-size:12px;color:var(--gray5);font-weight:500">פעולות</th>
1631              </tr></thead>
1632              <tbody id="users-tbody"></tbody>
1633            </table>
1634          </div>
1635        </div>
1636
1637      <div id="view-settings" style="display:none">
1638        <div class="admin-header">
1639          <div class="admin-title">הגדרות</div>
1640        </div>
1641        <div class="table-wrap" style="padding:24px 20px">
1642          <p style="color:var(--gray5);font-size:14px">הגדרות המערכת יהיו זמינות בקרוב.</p>
1643        </div>
1644      </div>
1645
1646      <div id="view-logs" style="display:none">
1647        <div class="admin-header">
1648          <div class="admin-actions">
1649            <button class="btn-sm btn-ghost" onclick="loadLogs()" title="רענן">↺ רענן</button>
1650          </div>
1651          <div class="admin-title">לוגים — כל המשתמשים</div>
1652        </div>
1653        <div style="padding:0 20px 24px">
1654          <!-- Device filter bar -->
1655          <div id="log-device-filter" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px"></div>
1656          <!-- Summary chips -->
1657          <div id="log-summary" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px"></div>
1658          <!-- Feed -->
1659          <div id="log-lines" style="overflow-x:auto;border:1px solid var(--gray3);border-radius:12px">טוען לוגים...</div>
1660          <!-- Pagination -->
1661          <div id="log-pagination" style="display:flex;align-items:center;justify-content:center;gap:6px;margin-top:16px;flex-wrap:wrap"></div>
1662        </div>
1663      </div>
1664
1665      <!-- ── HIDE MECHANISM VIEW ─────────────────────────────── -->
1666      <div id="view-hide-mechanism" style="display:none;padding:28px 24px">
1667        <div class="admin-header" style="margin-bottom:24px">
1668          <div class="admin-title">🎨 מנגנון הסתרת קוד קופון</div>
1669        </div>
1670
1671        <!-- Code reveal options -->
1672        <div style="margin-bottom:16px">
1673          <div class="section-label" style="padding:0;margin-bottom:6px">🔐 בחר סגנון: איך מסתירים את הקוד?</div>
1674          <div style="font-size:13px;color:var(--gray5);margin-bottom:24px">לחץ על כל קוד כדי לחשוף — הקוד מועתק אוטומטית</div>
1675        </div>
1676
1677        <style>
1678          .hm-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:20px; }
1679          .hm-col { display:flex; flex-direction:column; gap:10px; }
1680          .hm-label { text-align:center; font-size:11px; font-weight:700; color:var(--accent); letter-spacing:.5px; text-transform:uppercase; padding-bottom:4px; border-bottom:1px solid var(--gray3); }
1681          .hm-name  { text-align:center; font-size:14px; color:#fff; font-weight:600; }
1682          .hm-desc  { text-align:center; font-size:11px; color:var(--gray5); line-height:1.5; min-height:36px; }
1683          .hm-card  { background:var(--gray1); border-radius:16px; border:1px solid var(--gray3); padding:20px 16px 16px; display:flex; flex-direction:column; gap:10px; position:relative; }
1684          .hm-store-row { display:flex; align-items:center; gap:8px; justify-content:flex-end; }
1685          .hm-logo  { width:28px; height:28px; border-radius:6px; background:var(--gray3); display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:#fff; flex-shrink:0; }
1686          .hm-storename { font-size:13px; color:var(--gray5); }
1687          .hm-badge { position:absolute; top:12px; left:12px; background:var(--gray3); border-radius:6px; padding:2px 7px; font-size:10px; color:var(--gray5); }
1688          .hm-discount { font-size:26px; font-weight:800; color:#fff; text-align:center; line-height:1.1; }
1689          .hm-discount small { font-size:14px; font-weight:600; color:var(--gray5); }
1690          .hm-votes { display:flex; gap:8px; }
1691          .hm-vote  { display:flex; align-items:center; gap:4px; background:var(--gray2); border:1px solid var(--gray3); border-radius:8px; padding:4px 10px; font-size:12px; color:var(--gray5); cursor:pointer; }
1692          .hm-vote.up { border-color:rgba(0,232,122,.3); color:var(--accent); }
1693          .hm-vote.dn { border-color:rgba(255,77,77,.3); color:#ff4d4d; }
1694          .hm-rec { background:rgba(0,232,122,.12); border:1px solid rgba(0,232,122,.3); color:var(--accent); font-size:10px; font-weight:700; padding:2px 8px; border-radius:20px; text-align:center; }
1695          /* Shared code box */
1696          .hm-code-box { background:var(--gray2); border:1px solid var(--gray3); border-radius:10px; padding:10px 12px; font-family:monospace; font-size:15px;
1696 color:var(--accent); letter-spacing:2px; text-align:center; font-weight:700; display:flex; align-items:center; gap:8px; cursor:pointer; transition:border-color .2s; position:relative; overflow:hidden; }
1697          .hm-code-box:hover { border-color:rgba(0,232,122,.4); }
1698          /* 1 — Slide Banner */
1699          @keyframes hm-shimmer2 { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
1700          .hm-strip-wrap { position:relative; border-radius:10px; overflow:hidden; cursor:pointer; }
1701          .hm-strip { position:absolute; inset:0; background:linear-gradient(135deg,#182818,#1e381e); border:1px solid rgba(0,232,122,.4); border-radius:10px; display:flex; align-items:center; justify-content:center; gap:8px; font-size:12px; font-weight:700; color:var(--accent); overflow:hidden; transition:transform .38s cubic-bezier(.4,0,.2,1), opacity .28s; }
1702          .hm-strip::before { content:''; position:absolute; inset:0; background:linear-gradient(90deg,transparent,rgba(0,232,122,.07),transparent); background-size:200% 100%; animation:hm-shimmer2 2.2s infinite; }
1703          .hm-strip-arr { font-size:14px; opacity:.55; }
1704          /* 2 — Blur Focus */
1705          .hm-blur-wrap2 { cursor:zoom-in !important; }
1706          .hm-blur-wrap2 .hm-blur-code { filter:blur(8px); transition:filter .4s ease; user-select:none; pointer-events:none; }
1707          .hm-blur-wrap2:hover .hm-blur-code { filter:blur(4px); }
1708          /* 3 — Split Doors */
1709          .hm-doors-wrap { position:relative; border-radius:10px; overflow:hidden; cursor:pointer; }
1710          .hm-door { position:absolute; top:0; bottom:0; background:var(--gray3); border:1px solid var(--gray4); display:flex; align-items:center; transition:transform .4s cubic-bezier(.4,0,.2,1); }
1711          .hm-door-l { left:0; right:50%; justify-content:center; border-radius:10px 0 0 10px; border-right:none; }
1712          .hm-door-r { left:50%; right:0; justify-content:center; border-radius:0 10px 10px 0; border-left:none; }
1713          /* 4 — Night Vision */
1714          .hm-scan-wrap { position:relative; border-radius:10px; overflow:hidden; cursor:pointer; }
1715          .hm-scan-overlay { position:absolute; inset:0; background:repeating-linear-gradient(0deg,rgba(0,0,0,.52) 0px,rgba(0,0,0,.52) 1px,transparent 1px,transparent 3px),rgba(0,14,4,.8); border-radius:10px; display:flex; align-items:center; justify-content:center; font-size:10px; color:rgba(0,232,122,.5); letter-spacing:2.5px; font-family:monospace; border:1px solid rgba(0,232,122,.18); transition:opacity .35s; pointer-events:none; }
1716          /* 5 — Stamp Lift */
1717          @keyframes hm-stamp-out { 0%{opacity:1;transform:rotate(-10deg) scale(1) translateY(0)} 100%{opacity:0;transform:rotate(-28deg) scale(1.5) translateY(-60px)} }
1718          .hm-stamp-wrap { position:relative; border-radius:10px; cursor:pointer; }
1719          .hm-stamp { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; z-index:2; }
1720          .hm-stamp-inner { border:2.5px solid rgba(200,40,40,.75); border-radius:5px; padding:4px 12px; transform:rotate(-10deg); color:rgba(210,50,50,.85); font-size:11px; font-weight:800; letter-spacing:3px; font-family:monospace; text-transform:uppercase; background:rgba(0,0,0,.7); white-space:nowrap; }
1721          /* 6 — Eye Toggle (password-style) */
1722          .hm-eye-btn { color:var(--gray5); cursor:pointer; flex-shrink:0; display:flex; align-items:center; transition:color .2s; opacity:.7; }
1723          .hm-eye-btn:hover, .hm-code-box:hover .hm-eye-btn { color:var(--accent); opacity:1; }
1724          .hm-eye-hidden { flex:1; display:flex; align-items:center; font-size:14px; transition:opacity .2s; }
1725          .hm-eye-dots { color:var(--gray5); letter-spacing:4px; }
1726          .hm-eye-peek { color:var(--accent); font-weight:700; letter-spacing:2px; }
1727          .hm-eye-revealed { display:none; color:var(--accent); font-weight:700; letter-spacing:2px; flex:1; text-align:center; }
1728        </style>
1729
1730        <div class="hm-grid">
1731
1732          <!-- 1 Slide Banner -->
1733          <div class="hm-col">
1734            <div class="hm-label">אפשרות 1</div>
1735            <div class="hm-name">Slide Banner</div>
1736            <div class="hm-desc">רצועה ירוקה עם שימר מכסה את הקוד — לוחצים והיא נחלקת למעלה</div>
1737            <div class="hm-card">
1738              <span class="hm-badge">היום</span>
1739              <div class="hm-store-row"><span class="hm-storename">adika</span><div class="hm-logo" style="background:#b06020">A</div></div>
1740              <div class="hm-discount">10% <small>הנחה</small></div>
1741              <div class="hm-strip-wrap" onclick="hmRevealStrip(this,'ADIKA10')">
1742                <div class="hm-code-box" style="direction:ltr">
1743                  <span style="color:var(--accent)">⎘</span>
1744                  <span style="color:var(--accent);font-weight:700;letter-spacing:2px;flex:1;text-align:center">ADIKA10</span>
1745                </div>
1746                <div class="hm-strip">
1747                  <span class="hm-strip-arr">◀</span>
1748                  <span>הקש לחשיפת הקוד</span>
1749                  <span class="hm-strip-arr">▶</span>
1750                </div>
1751              </div>
1752              <div class="hm-votes"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1753            </div>
1754          </div>
1755
1756          <!-- 2 Blur Focus -->
1757          <div class="hm-col">
1758            <div class="hm-label">אפשרות 2</div>
1759            <div class="hm-name">Blur Focus</div>
1760            <div class="hm-desc">הקוד מטושטש לחלוטין — hover מחדד מעט, לחיצה חושפת</div>
1761            <div class="hm-card">
1762              <span class="hm-badge">לפני 3 ימים</span>
1763              <div class="hm-store-row"><span class="hm-storename">bug</span><div class="hm-logo" style="background:#0055cc">B</div></div>
1764              <div class="hm-discount">15% <small>הנחה</small></div>
1765              <div class="hm-code-box hm-blur-wrap2" style="direction:ltr" onclick="hmRevealBlurFocus(this,'BUG15')">
1766                <span style="color:var(--accent);flex-shrink:0">⎘</span>
1767                <span class="hm-blur-code" style="color:var(--accent);font-weight:700;letter-spacing:2px;flex:1;text-align:center">BUG15</span>
1768              </div>
1769              <div class="hm-votes"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1770            </div>
1771          </div>
1772
1773          <!-- 3 Split Doors -->
1774          <div class="hm-col">
1775            <div class="hm-label">אפשרות 3</div>
1776            <div class="hm-name">Split Doors</div>
1777            <div class="hm-desc">שני תריסים נפתחים לצדדים כמו דלתות מעלית</div>
1778            <div class="hm-card">
1779              <span class="hm-badge">לפני יומיים</span>
1780              <div class="hm-store-row"><span class="hm-storename">fox</span><div class="hm-logo" style="background:#222">F</div></div>
1781              <div class="hm-discount">20% <small>הנחה</small></div>
1782              <div class="hm-doors-wrap" onclick="hmRevealDoors(this,'FOX20')">
1783                <div class="hm-code-box" style="direction:ltr">
1784                  <span style="color:var(--accent)">⎘</span>
1785                  <span style="color:var(--accent);font-weight:700;letter-spacing:2px;flex:1;text-align:center">FOX20</span>
1786                </div>
1787                <div class="hm-door hm-door-l">
1788                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="15 18 9 12 15 6"/></svg>
1789                </div>
1790                <div class="hm-door hm-door-r">
1791                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="9 18 15 12 9 6"/></svg>
1792                </div>
1793              </div>
1794              <div class="hm-votes"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1795            </div>
1796          </div>
1797
1798          <!-- 4 Night Vision -->
1799          <div class="hm-col">
1800            <div class="hm-label">אפשרות 4</div>
1801            <div class="hm-name">Night Vision</div>
1802            <div class="hm-desc">שכבת סריקה צבאית מעל הקוד — לחיצה מסירה את הפילטר</div>
1803            <div class="hm-card">
1804              <span class="hm-badge">לפני 5 ימים</span>
1805              <div class="hm-store-row"><span class="hm-storename">ivory</span><div class="hm-logo" style="background:#3a3a3a">I</div></div>
1806              <div class="hm-discount">15% <small>הנחה</small></div>
1807              <div class="hm-scan-wrap" onclick="hmRevealScan(this,'IVORY15')">
1808                <div class="hm-code-box" style="direction:ltr">
1809                  <span style="color:var(--accent)">⎘</span>
1810                  <span style="color:var(--accent);font-weight:700;letter-spacing:2px;flex:1;text-align:center">IVORY15</span>
1811                </div>
1812                <div class="hm-scan-overlay">[ ENCRYPTED ]</div>
1813              </div>
1814              <div class="hm-votes"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1815            </div>
1816          </div>
1817
1818          <!-- 5 Stamp Lift -->
1819          <div class="hm-col">
1820            <div class="hm-label">אפשרות 5</div>
1821            <div class="hm-name">Stamp Lift</div>
1822            <div class="hm-desc">חותמת "סודי" אדומה באלכסון — לוחצים והיא עפה מהקלף</div>
1823            <div class="hm-card">
1824              <span class="hm-badge">היום</span>
1825              <div class="hm-store-row"><span class="hm-storename">factory54</span><div class="hm-logo" style="background:#1a1a2e">54</div></div>
1826              <div class="hm-discount">50₪ <small>הנחה</small></div>
1827              <div class="hm-stamp-wrap" onclick="hmRevealStamp(this,'FAC50')">
1828                <div class="hm-code-box" style="direction:ltr">
1829                  <span style="color:var(--accent)">⎘</span>
1830                  <span style="color:var(--accent);font-weight:700;letter-spacing:2px;flex:1;text-align:center">FAC50</span>
1831                </div>
1832                <div class="hm-stamp">
1833                  <div class="hm-stamp-inner">סודי</div>
1834                </div>
1835              </div>
1836              <div class="hm-votes"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1837            </div>
1838          </div>
1839
1840          <!-- 6 Eye Toggle -->
1841          <div class="hm-col">
1842            <div class="hm-label">אפשרות 6</div>
1843            <div class="hm-name">Eye Toggle</div>
1844            <div class="hm-desc">כמו שדה סיסמה — לוחצים על עין ורואים את הקוד</div>
1845            <div class="hm-rec">✦ הכי אינטואיטיבי</div>
1846            <div class="hm-card">
1847              <span class="hm-badge">היום</span>
1848              <div class="hm-store-row"><span class="hm-storename">terminalx</span><div class="hm-logo" style="background:#111">TX</div></div>
1849              <div class="hm-discount">5% <small>הנחה</small></div>
1850              <div class="hm-code-box" style="direction:ltr;cursor:pointer" onclick="hmRevealEye(this,'TERMX5')">
1851                <span style="color:var(--accent);flex-shrink:0">⎘</span>
1852                <span class="hm-eye-hidden"><span class="hm-eye-dots">••••</span><span class="hm-eye-peek">X5</span></span>
1853                <span class="hm-eye-revealed">TERMX5</span>
1854                <span class="hm-eye-btn" title="הצג קוד">
1855                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1856                </span>
1857              </div>
1858              <div class="hm-votes"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">2 👍</div></div>
1859            </div>
1860          </div>
1861
1862        </div>
1863
1864        <!-- ── Button styles ── -->
1865        <div style="margin-top:40px">
1866          <div class="section-label" style="padding:0;margin-bottom:6px">🖱️ סגנון כפתור — 6 אפשרויות</div>
1867          <div style="font-size:13px;color:var(--gray5);margin-bottom:20px">איזה סגנון גורם לך מיידית להבין שזה כפתור ולחיצה תחשוף את הקוד?</div>
1868          <style>
1869            /* shared btn-demo internals */
1870            .hm-btn-box { font-family:monospace; font-size:15px; padding:11px 14px; border-radius:8px; display:flex; align-items:center; gap:8px; cursor:pointer; direction:ltr; transition:filter .15s, transform .1s; user-select:none; position:relative; overflow:hidden; }
1871            .hm-btn-box:active { transform:scale(0.97); }
1872            .hm-btn-masked { display:flex; align-items:center; flex:1; gap:0; }
1873            .hm-btn-dots { color:rgba(255,255,255,0.5); font-size:16px; font-weight:700; letter-spacing:3px; }
1874            .hm-btn-end  { color:#00e87a; font-weight:800; font-size:16px; letter-spacing:3px; }
1875            .hm-btn-eye  { color:#888; display:flex; align-items:center; flex-shrink:0; transition:color .2s; }
1876            .hm-btn-box:hover .hm-btn-eye { color:#00e87a; }
1877
1878            /* 1 — Solid Fill */
1879            .hm-btn-solid { background:#00e87a; border:1px solid #00e87a; }
1880            .hm-btn-solid .hm-btn-dots { color:rgba(0,0,0,0.45); }
1881            .hm-btn-solid .hm-btn-end  { color:#003d1f; }
1882            .hm-btn-solid .hm-btn-eye  { color:rgba(0,0,0,0.5); }
1883            .hm-btn-solid:hover { filter:brightness(1.08); }
1884
1885            /* 2 — Pill CTA */
1886            .hm-btn-pill { background:rgba(0,232,122,0.1); border:2px solid rgba(0,232,122,0.75); border-radius:50px; padding:10px 18px; }
1887            .hm-btn-pill:hover { background:rgba(0,232,122,0.18); border-color:#00e87a; }
1888
1889            /* 3 — Scan Line (animated sweep) */
1890            @keyframes hm-scan { 0%{left:-40%} 100%{left:130%} }
1891            .hm-btn-scan { background:rgba(0,232,122,0.07); border:1px solid rgba(0,232,122,0.35); }
1892            .hm-btn-scan::before { content:''; position:absolute; top:0; bottom:0; width:35%; background:linear-gradient(90deg,transparent,rgba(0,232,122,0.25),transparent); animation:hm-scan 1.6s ease-in-out infinite; pointer-events:none; }
1893            .hm-btn-scan:hover { background:rgba(0,232,122,0.13); }
1894
1895            /* 4 — Raised / 3-D */
1896            .hm-btn-raised { background:rgba(0,232,122,0.1); border:1px solid rgba(0,232,122,0.5); box-shadow:0 4px 0 rgba(0,80,40,0.7), 0 6px 14px rgba(0,0,0,0.35); transform:translateY(-2px); }
1897            .hm-btn-raised:hover { background:rgba(0,232,122,0.16); box-shadow:0 5px 0 rgba(0,80,40,0.7), 0 8px 18px rgba(0,0,0,0.35); }
1898            .hm-btn-raised:active { transform:translateY(2px); box-shadow:0 1px 0 rgba(0,80,40,0.7); }
1899
1900            /* 5 — Frosted Glass */
1901            .hm-btn-frost { background:rgba(0,232,122,0.06); border:1px solid rgba(0,232,122,0.22); border-radius:12px; box-shadow:inset 0 1px 0 rgba(255,255,255,0.07), 0 0 24px rgba(0,232,122,0.1); backdrop-filter:blur(6px); }
1902            .hm-btn-frost:hover { background:rgba(0,232,122,0.11); box-shadow:inset 0 1px 0 rgba(255,255,255,0.1), 0 0 32px rgba(0,232,122,0.18); border-color:rgba(0,232,122,0.4); }
1903
1904            /* 6 — Pulse Ring */
1905            @keyframes hm-ring-pulse { 0%{box-shadow:0 0 0 0 rgba(0,232,122,0.55),0 0 0 0 rgba(0,232,122,0.25)} 70%{box-shadow:0 0 0 7px rgba(0,232,122,0),0 0 0 14px rgba(0,232,122,0)} 100%{box-shadow:0 0 0 0 rgba(0,232,122,0)} }
1906            .hm-btn-pulse { background:rgba(0,232,122,0.07); border:1.5px solid rgba(0,232,122,0.5); animation:hm-ring-pulse 1.9s ease-out infinite; }
1907            .hm-btn-pulse:hover { background:rgba(0,232,122,0.14); border-color:#00e87a; }
1908          </style>
1909
1910          <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">
1911
1912            <!-- btn 1 — Solid -->
1913            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
1914              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">1 — Solid Fill</div>
1915              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">רקע ירוק מלא — הכי ברור שזה כפתור</div>
1916              <div class="hm-btn-box hm-btn-solid" onclick="hmRevealBtn(this,'CODE15')">
1917                <span class="hm-btn-masked"><span class="hm-btn-dots">••••</span><span class="hm-btn-end">15</span></span>
1918                <span class="hm-btn-eye"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
1919              </div>
1920              <div class="hm-votes" style="margin-top:8px"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1921            </div>
1922
1923            <!-- btn 2 — Pill -->
1924            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
1925              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">2 — Pill CTA</div>
1926              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">פיל מעוגל עם מסגרת ירוקה עבה</div>
1927              <div class="hm-btn-box hm-btn-pill" onclick="hmRevealBtn(this,'SUMMER20')">
1928                <span class="hm-btn-masked"><span class="hm-btn-dots">••••••</span><span class="hm-btn-end">20</span></span>
1929                <span class="hm-btn-eye"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
1930              </div>
1931              <div class="hm-votes" style="margin-top:8px"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1932            </div>
1933
1934            <!-- btn 3 — Scan -->
1935            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
1936              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">3 — Scan Line</div>
1937              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">קו אור עובר — מרגיש כמו סריקה</div>
1938              <div class="hm-btn-box hm-btn-scan" onclick="hmRevealBtn(this,'ZARA10')">
1939                <span class="hm-btn-masked"><span class="hm-btn-dots">••••</span><span class="hm-btn-end">10</span></span>
1940                <span class="hm-btn-eye"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
1941              </div>
1942              <div class="hm-votes" style="margin-top:8px"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1943            </div>
1944
1945            <!-- btn 4 — Raised -->
1946            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
1947              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">4 — Raised / 3D</div>
1948              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">צל תחתון — כפתור פיזי עם עומק</div>
1949              <div class="hm-btn-box hm-btn-raised" onclick="hmRevealBtn(this,'BUG15')">
1950                <span class="hm-btn-masked"><span class="hm-btn-dots">••••</span><span class="hm-btn-end">15</span></span>
1951                <span class="hm-btn-eye"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
1952              </div>
1953              <div class="hm-votes" style="margin-top:8px"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1954            </div>
1955
1956            <!-- btn 5 — Frosted -->
1957            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
1958              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">5 — Frosted Glass</div>
1959              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">זכוכית מטושטשת עם זוהר ירוק</div>
1960              <div class="hm-btn-box hm-btn-frost" onclick="hmRevealBtn(this,'FOX5')">
1961                <span class="hm-btn-masked"><span class="hm-btn-dots">••••</span><span class="hm-btn-end">X5</span></span>
1962                <span class="hm-btn-eye"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
1963              </div>
1964              <div class="hm-votes" style="margin-top:8px"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1965            </div>
1966
1967            <!-- btn 6 — Pulse Ring -->
1968            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
1969              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">6 — Pulse Ring</div>
1970              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">טבעת ירוקה פועמת — מושכת את העין</div>
1971              <div class="hm-btn-box hm-btn-pulse" onclick="hmRevealBtn(this,'KSP30')">
1972                <span class="hm-btn-masked"><span class="hm-btn-dots">••••</span><span class="hm-btn-end">30</span></span>
1973                <span class="hm-btn-eye"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
1974              </div>
1975              <div class="hm-votes" style="margin-top:8px"><div class="hm-vote dn">0 👎</div><div class="hm-vote up">0 👍</div></div>
1976            </div>
1977
1978          </div>
1979        </div>
1980
1981        <!-- ── Shine effects ── -->
1982        <div style="margin-top:36px">
1983          <div class="section-label" style="padding:0;margin-bottom:6px">✨ אפקט זוהר לאחר חשיפה — 12 אפשרויות</div>
1984          <div style="font-size:13px;color:var(--gray5);margin-bottom:20px">לחץ על כל קוד כדי לראות את האפקט מחדש</div>
1985          <style>
1986            /* 1 Silver sweep */
1987            @keyframes hm-shine-silver { 0%{background-position:-200% center} 100%{background-position:200% center} }
1988            .hm-shine-1 { background:linear-gradient(90deg,var(--accent) 0%,#fff 40%,#aef7d3 60%,var(--accent) 100%); background-size:200% auto; -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; animation:hm-shine-silver .7s ease forwards; }
1989            /* 2 Glow pulse */
1990            @keyframes hm-shine-glow { 0%,100%{text-shadow:0 0 0 transparent} 40%{text-shadow:0 0 14px rgba(0,232,122,.9),0 0 30px rgba(0,232,122,.5)} }
1991            .hm-shine-2 { animation:hm-shine-glow .8s ease forwards; color:var(--accent)!important; }
1992            /* 3 Flash background */
1993            @keyframes hm-shine-flash { 0%{background:rgba(0,232,122,.0)} 20%{background:rgba(0,232,122,.28)} 100%{background:rgba(0,232,122,.0)} }
1994            .hm-shine-3-wrap { animation:hm-shine-flash .6s ease forwards; }
1995            /* 4 Gold sweep */
1996            @keyframes hm-shine-gold { 0%{background-position:-200% center} 100%{background-position:200% center} }
1997            .hm-shine-4 { background:linear-gradient(90deg,#f59e0b 0%,#fff8c5 35%,#fbbf24 60%,#f59e0b 100%); background-size:200% auto; -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; animation:hm-shine-gold .7s ease forwards; }
1998            /* 5 Neon flicker */
1999            @keyframes hm-shine-neon { 0%{opacity:1} 10%{opacity:.4} 20%{opacity:1} 30%{opacity:.6} 45%{opacity:1} 60%{opacity:.3} 75%{opacity:1} 100%{opacity:1} }
2000            .hm-shine-5 { animation:hm-shine-neon .7s ease forwards; color:#00e87a!important; text-shadow:0 0 10px rgba(0,232,122,.8),0 0 24px rgba(0,232,122,.4); }
2001            /* 6 Scale bounce */
2002            @keyframes hm-shine-scale { 0%{transform:scale(1)} 30%{transform:scale(1.12)} 60%{transform:scale(.97)} 80%{transform:scale(1.04)} 100%{transform:scale(1)} }
2003            .hm-shine-6 { animation:hm-shine-scale .55s cubic-bezier(.4,0,.2,1) forwards; display:inline-block; }
2004            /* 7 Warp In — text stretches from center outward */
2005            @keyframes hm-shine-warp { 0%{transform:scaleX(0.1);opacity:0} 65%{transform:scaleX(1.07)} 82%{transform:scaleX(0.97)} 100%{transform:scaleX(1);opacity:1} }
2006            .hm-shine-7 { animation:hm-shine-warp .45s cubic-bezier(.4,0,.2,1) forwards; display:inline-block; color:var(--accent); }
2007            /* 8 Rainbow Cycle — hue spins through spectrum */
2008            @keyframes hm-shine-rainbow { 0%{filter:hue-rotate(0deg) brightness(1.6)} 40%{filter:hue-rotate(140deg) brightness(2)} 80%{filter:hue-rotate(280deg) brightness(1.4)} 100%{filter:hue-rotate(360deg) brightness(1)} }
2009            .hm-shine-8 { animation:hm-shine-rainbow .75s ease forwards; color:var(--accent); }
2010            /* 9 Ink Reveal — blurry → sharp, letters compress */
2011            @keyframes hm-shine-ink { 0%{filter:blur(9px);opacity:0;letter-spacing:8px} 55%{letter-spacing:2.5px} 100%{filter:blur(0);opacity:1;letter-spacing:1.5px} }
2012            .hm-shine-9 { animation:hm-shine-ink .5s ease-out forwards; display:inline-block; color:var(--accent); }
2013            /* 10 Type Highlight — green strip sweeps behind text */
2014            @keyframes hm-shine-hl { 0%{background-size:0% 100%} 100%{background-size:100% 100%} }
2015            .hm-shine-10-wrap { background:linear-gradient(90deg,rgba(0,232,122,.22),rgba(0,232,122,.1)); background-repeat:no-repeat; background-size:0% 100%; animation:hm-shine-hl .45s ease forwards; border-radius:4px; }
2016            /* 11 Stamp Pop — scales in from tiny with slight rotation */
2017            @keyframes hm-shine-stamp { 0%{transform:scale(0.35) rotate(-4deg);opacity:0}
2017 55%{transform:scale(1.1) rotate(1deg);opacity:1} 75%{transform:scale(0.96) rotate(-0.5deg)} 100%{transform:scale(1) rotate(0deg);opacity:1} }
2018            .hm-shine-11 { animation:hm-shine-stamp .5s cubic-bezier(.4,0,.2,1) forwards; display:inline-block; color:var(--accent); }
2019            /* 12 Slide Up — rises from below, white flash → green */
2020            @keyframes hm-shine-slideup { 0%{transform:translateY(9px);opacity:0;color:#fff} 35%{color:#fff} 100%{transform:translateY(0);opacity:1;color:var(--accent)} }
2021            .hm-shine-12 { animation:hm-shine-slideup .42s ease-out forwards; display:inline-block; }
2022          </style>
2023          <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">
2024            <!-- shine 1 -->
2025            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2026              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">1 — Silver Sweep</div>
2027              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">ברק כספי עובר על הקוד</div>
2028              <div class="cc-code" id="hs1" onclick="hmShinePlay(1)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs1-t">SUMMER15</span></div>
2029            </div>
2030            <!-- shine 2 -->
2031            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2032              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">2 — Glow Pulse</div>
2033              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">זוהר ירוק מסביב לאותיות</div>
2034              <div class="cc-code" id="hs2" onclick="hmShinePlay(2)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs2-t">SUMMER15</span></div>
2035            </div>
2036            <!-- shine 3 -->
2037            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2038              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">3 — Flash</div>
2039              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הרקע מהבהב ירוק</div>
2040              <div class="cc-code" id="hs3" onclick="hmShinePlay(3)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs3-t">SUMMER15</span></div>
2041            </div>
2042            <!-- shine 4 -->
2043            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2044              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">4 — Gold Sweep</div>
2045              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">ברק זהוב עובר על הקוד</div>
2046              <div class="cc-code" id="hs4" onclick="hmShinePlay(4)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs4-t">SUMMER15</span></div>
2047            </div>
2048            <!-- shine 5 -->
2049            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2050              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">5 — Neon Flicker</div>
2051              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הקוד מהבהב כמו ניאון</div>
2052              <div class="cc-code" id="hs5" onclick="hmShinePlay(5)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs5-t">SUMMER15</span></div>
2053            </div>
2054            <!-- shine 6 -->
2055            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2056              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">6 — Bounce</div>
2057              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הקוד קופץ ומתנדנד</div>
2058              <div class="cc-code" id="hs6" onclick="hmShinePlay(6)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs6-t">SUMMER15</span></div>
2059            </div>
2060            <!-- shine 7 -->
2061            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2062              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">7 — Warp In</div>
2063              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הקוד מתרחב מהמרכז החוצה</div>
2064              <div class="cc-code" id="hs7" onclick="hmShinePlay(7)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs7-t">SUMMER15</span></div>
2065            </div>
2066            <!-- shine 8 -->
2067            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2068              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">8 — Rainbow Cycle</div>
2069              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הקוד עובר את הספקטרום</div>
2070              <div class="cc-code" id="hs8" onclick="hmShinePlay(8)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs8-t">SUMMER15</span></div>
2071            </div>
2072            <!-- shine 9 -->
2073            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2074              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">9 — Ink Reveal</div>
2075              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">מטושטש ↔ חד — כמו התמקדות</div>
2076              <div class="cc-code" id="hs9" onclick="hmShinePlay(9)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs9-t">SUMMER15</span></div>
2077            </div>
2078            <!-- shine 10 -->
2079            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2080              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">10 — Type Highlight</div>
2081              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">רצועה ירוקה נוסעת מאחורי הקוד</div>
2082              <div class="cc-code" id="hs10" onclick="hmShinePlay(10)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs10-t">SUMMER15</span></div>
2083            </div>
2084            <!-- shine 11 -->
2085            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2086              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">11 — Stamp Pop</div>
2087              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הקוד "מוחתם" — קטן עד גדול עם סיבוב</div>
2088              <div class="cc-code" id="hs11" onclick="hmShinePlay(11)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs11-t">SUMMER15</span></div>
2089            </div>
2090            <!-- shine 12 -->
2091            <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px">
2092              <div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:4px">12 — Slide Up</div>
2093              <div style="font-size:11px;color:var(--gray5);margin-bottom:12px">הקוד עולה מלמטה עם הבזק לבן</div>
2094              <div class="cc-code" id="hs12" onclick="hmShinePlay(12)" style="cursor:pointer;direction:ltr;letter-spacing:1.5px"><span id="hs12-t">SUMMER15</span></div>
2095            </div>
2096          </div>
2097        </div>
2098      </div>
2099
2100      <!-- ── SEARCH ANIMATION VIEW ──────────────────────────── -->
2101      <div id="view-search-anim" style="display:none;padding:28px 24px">
2102        <div class="admin-header" style="margin-bottom:24px">
2103          <div class="admin-title">⏳ אנימציית חיפוש — אפשרויות עיצוב</div>
2104        </div>
2105        <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:20px;max-width:1100px">
2106
2107          <!-- Option 1: Live Log -->
2108          <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;padding:20px">
2109            <div style="font-size:13px;font-weight:700;color:var(--accent);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--gray3)">אפשרות 1 — יומן חיפוש חי 🖥️</div>
2110            <div style="background:#0d0d0d;border-radius:10px;padding:14px 16px;font-family:monospace;font-size:12px;min-height:180px" id="mg-log1">
2111              <div class="mg-log-line" style="display:flex;align-items:flex-start;gap:8px;margin-bottom:9px;opacity:0;animation:mg-fadeSlide .4s .1s forwards"><span style="flex-shrink:0;width:18px">🔍</span><span style="color:#aaa">מרחיב שאילתה עם <strong style="color:#fff">Claude</strong>...</span></div>
2112              <div class="mg-log-line" style="display:flex;align-items:flex-start;gap:8px;margin-bottom:9px;opacity:0;animation:mg-fadeSlide .4s .9s forwards"><span style="flex-shrink:0;width:18px">🌐</span><span style="color:#aaa">שולח <strong style="color:#fff">12</strong> שאילתות ל-DuckDuckGo...</span></div>
2113              <div class="mg-log-line" style="display:flex;align-items:flex-start;gap:8px;margin-bottom:9px;opacity:0;animation:mg-fadeSlide .4s 1.8s forwards"><span style="flex-shrink:0;width:18px">📄</span><span style="color:#aaa">מנתח <strong style="color:#fff">8 דפים</strong> במקביל...</span></div>
2114              <div class="mg-log-line" style="display:flex;align-items:flex-start;gap:8px;margin-bottom:9px;opacity:0;animation:mg-fadeSlide .4s 2.7s forwards"><span style="flex-shrink:0;width:18px">🤖</span><span style="color:#aaa">מחלץ קופונים עם AI... <span style="color:var(--accent)">✓ 4</span> נמצאו</span></div>
2115              <div class="mg-log-line" style="display:flex;align-items:flex-start;gap:8px;margin-bottom:9px;opacity:0;animation:mg-fadeSlide .4s 3.5s forwards"><span style="flex-shrink:0;width:18px">✅</span><span style="color:#aaa"><span style="color:var(--accent)">נמצאו 9 קופונים</span> תוך 14.2 שניות</span></div>
2116              <div style="margin-top:12px;font-size:11px;color:#444;font-family:monospace"><span style="display:inline-block;width:7px;height:13px;background:var(--accent);margin-left:4px;animation:mg-blink 1s infinite;vertical-align:middle"></span> <span id="mg-log-timer">0.0s</span></div>
2117            </div>
2118            <button onclick="mgReplayLog()" style="display:block;margin:10px auto 0;font-size:11px;background:var(--gray2);border:1px solid var(--gray3);color:var(--gray5);border-radius:6px;padding:4px 12px;cursor:pointer">↺ הפעל מחדש</button>
2119          </div>
2120
2121          <!-- Option 2: Skeleton -->
2122          <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;padding:20px">
2123            <div style="font-size:13px;font-weight:700;color:var(--accent);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--gray3)">אפשרות 2 — כרטיסי שלד (Skeleton) ✨</div>
2124            <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
2125              <div style="background:#1e1e1e;border-radius:12px;padding:14px"><div style="display:flex;gap:10px;align-items:center;margin-bottom:10px"><div style="width:36px;height:36px;border-radius:10px;flex-shrink:0" class="mg-shimmer"></div><div style="flex:1"><div class="mg-shimmer" style="height:10px;border-radius:5px;width:80%;margin-bottom:6px"></div><div class="mg-shimmer" style="height:10px;border-radius:5px;width:55%"></div></div></div><div class="mg-shimmer" style="height:22px;border-radius:8px;width:70%;margin-bottom:10px"></div><div class="mg-shimmer" style="height:36px;border-radius:8px;margin-bottom:10px"></div><div style="display:flex;gap:6px"><div class="mg-shimmer" style="height:8px;border-radius:5px;width:40%"></div><div class="mg-shimmer" style="height:8px;border-radius:5px;width:30%"></div></div></div>
2126              <div style="background:#1e1e1e;border-radius:12px;padding:14px"><div style="display:flex;gap:10px;align-items:center;margin-bottom:10px"><div style="width:36px;height:36px;border-radius:10px;flex-shrink:0" class="mg-shimmer"></div><div style="flex:1"><div class="mg-shimmer" style="height:10px;border-radius:5px;width:65%;margin-bottom:6px"></div><div class="mg-shimmer" style="height:10px;border-radius:5px;width:40%"></div></div></div><div class="mg-shimmer" style="height:22px;border-radius:8px;width:50%;margin-bottom:10px"></div><div class="mg-shimmer" style="height:36px;border-radius:8px;margin-bottom:10px"></div><div style="display:flex;gap:6px"><div class="mg-shimmer" style="height:8px;border-radius:5px;width:35%"></div><div class="mg-shimmer" style="height:8px;border-radius:5px;width:25%"></div></div></div>
2127              <div style="background:#1e1e1e;border-radius:12px;padding:14px"><div style="display:flex;gap:10px;align-items:center;margin-bottom:10px"><div style="width:36px;height:36px;border-radius:10px;flex-shrink:0" class="mg-shimmer"></div><div style="flex:1"><div class="mg-shimmer" style="height:10px;border-radius:5px;width:75%;margin-bottom:6px"></div><div class="mg-shimmer" style="height:10px;border-radius:5px;width:50%"></div></div></div><div class="mg-shimmer" style="height:22px;border-radius:8px;width:60%;margin-bottom:10px"></div><div class="mg-shimmer" style="height:36px;border-radius:8px;margin-bottom:10px"></div><div style="display:flex;gap:6px"><div class="mg-shimmer" style="height:8px;border-radius:5px;width:45%"></div><div class="mg-shimmer" style="height:8px;border-radius:5px;width:30%"></div></div></div>
2128              <div style="background:#1e1e1e;border-radius:12px;padding:14px"><div style="display:flex;gap:10px;align-items:center;margin-bottom:10px"><div style="width:36px;height:36px;border-radius:10px;flex-shrink:0" class="mg-shimmer"></div><div style="flex:1"><div class="mg-shimmer" style="height:10px;border-radius:5px;width:70%;margin-bottom:6px"></div><div class="mg-shimmer" style="height:10px;border-radius:5px;width:45%"></div></div></div><div class="mg-shimmer" style="height:22px;border-radius:8px;width:55%;margin-bottom:10px"></div><div class="mg-shimmer" style="height:36px;border-radius:8px;margin-bottom:10px"></div><div style="display:flex;gap:6px"><div class="mg-shimmer" style="height:8px;border-radius:5px;width:38%"></div><div class="mg-shimmer" style="height:8px;border-radius:5px;width:28%"></div></div></div>
2129            </div>
2130            <div style="text-align:center;margin-top:14px;font-size:12px;color:#555;display:flex;align-items:center;justify-content:center;gap:8px">
2131              <div><span class="mg-dot-bounce" style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);margin:0 2px;animation:mg-dot 1.2s infinite"></span><span class="mg-dot-bounce" style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);margin:0 2px;animation:mg-dot 1.2s .2s infinite"></span><span class="mg-dot-bounce" style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);margin:0 2px;animation:mg-dot 1.2s .4s infinite"></span></div>
2132              מחפש קופונים...
2133            </div>
2134          </div>
2135
2136          <!-- Option 3: Tips -->
2137          <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;padding:20px">
2138            <div style="font-size:13px;font-weight:700;color:var(--accent);margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--gray3)">אפשרות 3 — טיפים לחיסכון 💡</div>
2139            <div style="position:relative;overflow:hidden;min-height:220px;display:flex;flex-direction:column;align-items:center;justify-content:center">
2140              <div style="text-align:center;padding:10px">
2141                <div style="font-size:40px;margin-bottom:14px" id="mg-tip-icon">💳</div>
2142                <div style="font-size:14px;color:var(--gray6);line-height:1.7;max-width:240px;margin:0 auto" id="mg-tip-text">כרטיסי אשראי מסוימים נותנים <strong style="color:#fff">קאשבק נוסף</strong> על קנייה ע
2142ם קוד קופון — תמיד שווה לבדוק!</div>
2143              </div>
2144              <div style="display:flex;gap:6px;justify-content:center;margin-top:18px" id="mg-tip-dots">
2145                <div style="width:7px;height:7px;border-radius:50%;background:var(--accent)" id="mg-td0"></div>
2146                <div style="width:7px;height:7px;border-radius:50%;background:var(--gray3)" id="mg-td1"></div>
2147                <div style="width:7px;height:7px;border-radius:50%;background:var(--gray3)" id="mg-td2"></div>
2148                <div style="width:7px;height:7px;border-radius:50%;background:var(--gray3)" id="mg-td3"></div>
2149                <div style="width:7px;height:7px;border-radius:50%;background:var(--gray3)" id="mg-td4"></div>
2150              </div>
2151              <div style="width:100%;height:2px;background:var(--gray3);border-radius:2px;margin-top:16px;overflow:hidden"><div style="height:2px;background:var(--accent);border-radius:2px;animation:mg-progress 4s linear infinite" id="mg-tip-bar"></div></div>
2152              <div style="font-size:11px;color:var(--gray5);margin-top:10px;display:flex;align-items:center;gap:6px;justify-content:center"><span class="mg-dot-bounce" style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);margin:0 2px;animation:mg-dot 1.2s infinite"></span><span class="mg-dot-bounce" style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);margin:0 2px;animation:mg-dot 1.2s .2s infinite"></span><span class="mg-dot-bounce" style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);margin:0 2px;animation:mg-dot 1.2s .4s infinite"></span> AI מחפש עבורך...</div>
2153            </div>
2154          </div>
2155        </div>
2156        <style>
2157          .mg-shimmer { background:linear-gradient(90deg,#2a2a2a 25%,#333 50%,#2a2a2a 75%);background-size:200% 100%;animation:mg-shimmer 1.6s infinite; }
2158          @keyframes mg-shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
2159          @keyframes mg-fadeSlide { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:none} }
2160          @keyframes mg-blink { 0%,100%{opacity:1} 50%{opacity:0} }
2161          @keyframes mg-dot { 0%,80%,100%{transform:scale(.6);opacity:.4} 40%{transform:scale(1);opacity:1} }
2162          @keyframes mg-progress { from{width:0%} to{width:100%} }
2163        </style>
2164      </div>
2165
2166      <!-- ── EXPIRED VIEW ───────────────────────────────────── -->
2167      <div id="view-expired" style="display:none;padding:28px 24px">
2168        <div class="admin-header" style="margin-bottom:24px">
2169          <div class="admin-title">⏰ פג תוקף — בחר סגנון תצוגה</div>
2170        </div>
2171        <div style="font-size:13px;color:var(--gray5);margin-bottom:24px">אחרי 10 דאונווטים מצטברים, הקופון יסומן כ"פג תוקף" — בחר את הסגנון שאתה מעדיף</div>
2172        <div class="coupon-grid" id="grid-expired-demo-top" style="padding:0"></div>
2173      </div>
2174
2175      <!-- ── MOLE GAME VIEW ────────────────────────────────────── -->
2176      <div id="view-mole" style="display:none;padding:28px 24px">
2177        <div class="admin-header" style="margin-bottom:24px">
2178          <div class="admin-title">🎯 משחקים — תפוס קופונים!</div>
2179        </div>
2180        <div id="wam-view-inner" style="text-align:center;max-width:400px;margin:0 auto">
2181          <div style="font-size:13px;color:var(--gray5);margin-bottom:16px">תפוס את הקופונים שקופצים — כמה נקודות תצבור?</div>
2182          <div style="font-size:22px;font-weight:700;color:var(--accent);margin-bottom:16px" id="wam-view-score">0 נקודות</div>
2183          <div id="wam-view-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;max-width:320px;margin:0 auto 20px"></div>
2184          <div style="display:flex;gap:10px;justify-content:center">
2185            <button class="btn-sm btn-ghost" onclick="wamStart()">▶ התחל</button>
2186            <button class="btn-sm btn-ghost" onclick="wamStop()">■ עצור</button>
2187          </div>
2188        </div>
2189      </div>
2190
2191      <!-- ── LOAD ANIMATIONS VIEW (real in-search game) ──────── -->
2192      <div id="view-minigames" style="display:none;padding:28px 24px">
2193        <div class="admin-header" style="margin-bottom:24px">
2194          <div class="admin-title">🎬 אנימציות טעינה — מה שרץ בזמן חיפוש</div>
2195        </div>
2196        <div style="max-width:700px">
2197          <!-- Skeleton grid -->
2198          <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px;margin-bottom:20px" id="demo-skel-grid"></div>
2199          <!-- Tips panel -->
2200          <div style="background:var(--gray3);border-radius:14px;padding:16px 24px">
2201            <div style="display:flex;align-items:center;justify-content:center;gap:14px">
2202              <div id="demo-tip-icon" style="font-size:26px;flex-shrink:0">💡</div>
2203              <div id="demo-tip-text" style="font-size:15px;color:var(--gray6);line-height:1.5"></div>
2204            </div>
2205            <div style="margin-top:12px;display:flex;justify-content:center">
2206              <div style="width:80px;height:2px;background:var(--gray4);border-radius:2px;overflow:hidden">
2207                <div id="demo-tip-bar" style="height:2px;background:var(--accent);border-radius:2px;width:0%"></div>
2208              </div>
2209            </div>
2210          </div>
2211          <div style="margin-top:16px;display:flex;gap:10px">
2212            <button class="btn-sm btn-ghost" onclick="demoGameStart()">▶ הפעל</button>
2213            <button class="btn-sm btn-ghost" onclick="demoGameStop()">■ עצור</button>
2214          </div>
2215        </div>
2216      </div>
2217
2218      <!-- ── DEBUGGING VIEW ─────────────────────────────────── -->
2219      <!-- ── Store Pages Demo ─────────────────────────────────────────────── -->
2220      <div id="view-store-pages-demo" style="display:none;padding:28px 24px">
2221        <div class="admin-header" style="margin-bottom:20px">
2222          <div class="admin-title">🏪 מוקאפ — דפי חנויות לגוגל</div>
2223        </div>
2224        <div style="background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:12px;padding:16px 20px;margin-bottom:20px;font-size:13px;color:var(--gray6);line-height:1.7">
2225          שלושה עיצובים שונים לדפי חנות. בחר חנות + עיצוב כדי לראות מוקאפ מלא. <strong style="color:var(--accent)">דמו בלבד</strong> — אין שינויים בקוד או בנתונים.
2226        </div>
2227
2228        <!-- Layout selector -->
2229        <div style="margin-bottom:16px">
2230          <div style="font-size:11px;color:var(--gray5);margin-bottom:8px;font-weight:600;letter-spacing:0.5px">עיצוב</div>
2231          <div style="display:flex;gap:8px;flex-wrap:wrap">
2232            <button id="sd-layout-a" onclick="_sdSetLayout('a')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;
2232cursor:pointer;font-family:'DM Sans',sans-serif">A — Hero + גריד</button>
2233            <button id="sd-layout-b" onclick="_sdSetLayout('b')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">B — סיידבר + רשימה</button>
2234            <button id="sd-layout-c" onclick="_sdSetLayout('c')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">C — מינימל SEO</button>
2235            <button id="sd-layout-d" onclick="_sdSetLayout('d')" style="padding:8px 18px;background:var(--accent);color:#000;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:600">D — סיידבר משופר ★</button>
2236          </div>
2237        </div>
2238
2239        <!-- Store selector -->
2240        <div style="margin-bottom:24px">
2241          <div style="font-size:11px;color:var(--gray5);margin-bottom:8px;font-weight:600;letter-spacing:0.5px">חנות</div>
2242          <div style="display:flex;gap:8px;flex-wrap:wrap" id="sd-store-btns">
2243            <button onclick="_sdSetStore('ksp')" style="padding:8px 18px;background:var(--accent);color:#000;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:600">KSP</button>
2244            <button onclick="_sdSetStore('superpharm')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">סופר פארם</button>
2245            <button onclick="_sdSetStore('aliexpress')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">AliExpress</button>
2246            <button onclick="_sdSetStore('asos')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">ASOS</button>
2247            <button onclick="_sdSetStore('castro')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">קסטרו</button>
2248          </div>
2249        </div>
2250
2251        <div id="store-demo-content" style="border:1px solid var(--gray3);border-radius:14px;overflow:hidden;min-height:400px"></div>
2252      </div>
2253
2254      <!-- ── Home Page Demo ─────────────────────────────────────────────── -->
2255      <div id="view-home-demo" style="display:none;padding:28px 24px">
2256        <div class="admin-header" style="margin-bottom:20px">
2257          <div class="admin-title">🏠 מוקאפ — עמוד הבית</div>
2258        </div>
2259        <div style="background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:12px;padding:16px 20px;margin-bottom:20px;font-size:13px;color:var(--gray6);line-height:1.7">
2260          5 עיצובים שונים לעמוד הבית עם חנויות במקום קופונים בודדים. לחיצה על חנות מפנה ל-/store/. <strong style="color:var(--accent)">דמו בלבד</strong>
2261        </div>
2262        <div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:24px">
2263          <button id="hp-btn-a" onclick="_hpSetLayout('a')" style="padding:8px 18px;background:var(--accent);color:#000;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:600">A — גריד חנויות</button>
2264          <button id="hp-btn-b" onclick="_hpSetLayout('b')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">B — שורות קטגוריה</button>
2265          <button id="hp-btn-c" onclick="_hpSetLayout('c')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;
2265cursor:pointer;font-family:'DM Sans',sans-serif">C — עריכה + גריד</button>
2266          <button id="hp-btn-d" onclick="_hpSetLayout('d')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">D — לוגואים מינימל</button>
2267          <button id="hp-btn-e" onclick="_hpSetLayout('e')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">E — קטגוריות ראשית</button>
2268          <button id="hp-btn-f" onclick="_hpSetLayout('f')" style="padding:8px 18px;background:var(--gray3);color:var(--white);border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">F — קומבו ★</button>
2269        </div>
2270        <div id="hp-demo-content" style="border:1px solid var(--gray3);border-radius:14px;overflow:hidden;min-height:500px"></div>
2271      </div>
2272
2273      <!-- ── Contact Messages ─────────────────────────────────────────────── -->
2274      <div id="view-contact-messages" style="display:none;padding:28px 24px">
2275        <div class="admin-header" style="margin-bottom:20px">
2276          <div class="admin-title">✉️ הודעות צור קשר</div>
2277        </div>
2278        <div id="contact-messages-list" style="display:flex;flex-direction:column;gap:12px">
2279          <div style="color:var(--gray5);text-align:center;padding:40px">טוען...</div>
2280        </div>
2281      </div>
2282
2283      <div id="view-debug" style="display:none;padding:28px 24px">
2284        <div class="admin-header" style="margin-bottom:20px">
2285          <div class="admin-actions">
2286            <button class="btn-sm btn-ghost" id="debug-clear-btn" onclick="debugClear()">✕ נקה</button>
2287          </div>
2288          <div class="admin-title">🐛 Pipeline Debugger — לוגים חיים</div>
2289        </div>
2290
2291        <!-- Run bar -->
2292        <div style="display:flex;gap:10px;align-items:center;margin-bottom:16px;flex-direction:row-reverse">
2293          <input id="debug-query-input" type="text" placeholder="שם חנות, למשל: fox" dir="rtl"
2294            style="flex:1;max-width:320px;background:var(--gray2);border:1px solid var(--gray3);border-radius:8px;padding:8px 14px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif"
2295            onkeydown="if(event.key==='Enter')debugRun()" />
2296          <button id="debug-run-btn" onclick="debugRun()"
2297            style="padding:8px 20px;background:var(--accent);color:#000;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif">
2298            ▶ הרץ
2299          </button>
2300          <span id="debug-status" style="font-size:12px;color:var(--gray5)"></span>
2301        </div>
2302
2303        <!-- Terminal -->
2304        <div id="debug-terminal"
2305          style="background:#0d0d0d;border:1px solid #222;border-radius:12px;padding:14px 16px;font-family:'Courier New',monospace;font-size:12px;line-height:1.7;min-height:420px;max-height:65vh;overflow-y:auto;white-space:pre-wrap;word-break:break-all;color:#ccc">
2306          <span style="color:#555">// לחץ הרץ כדי לראות את ה-pipeline בפעולה</span>
2307        </div>
2308
2309        <!-- Result report -->
2310        <div id="debug-result" style="margin-top:20px;display:none">
2311          <!-- Section title -->
2312          <div style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;color:var(--white);margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--gray3)">
2313            📊 דוח תוצאות
2314          </div>
2315          <!-- Stats chips -->
2316          <div id="debug-stats" style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:18px"></div>
2317          <!-- Source breakdown -->
2318          <div id="debug-sources" style="margin-bottom:18px"></div>
2319          <!-- Main table -->
2320          <div id="debug-table-wrap" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:12px;overflow:hidden">
2321            <div style="padding:12px 16px;border-bottom:1px solid var(--gray3);font-size:12px;font-weight:600;color:var(--gray5)">
2322              קופונים שנמצאו
2323            </div>
2324            <div style="overflow-x:auto">
2325              <table id="debug-table" style="width:100%;border-collapse:collapse;font-size:12px">
2326                <thead>
2327                  <tr style="background:var(--gray1)">
2328                    <th style="padding:9px 14px;text-align:right;color:var(--gray5);font-weight:500;white-space:nowrap">חנות</th>
2329                    <th style="padding:9px 14px;text-align:right;color:var(--gray5);font-weight:500;white-space:nowrap">קוד</th>
2330                    <th style="padding:9px 14px;text-align:right;color:var(--gray5);font-weight:500;white-space:nowrap">הנחה</th>
2331                    <th style="padding:9px 14px;text-align:right;color:var(--gray5);font-weight:500;white-space:nowrap">קטגוריה</th>
2332                    <th style="padding:9px 14px;text-align:right;color:var(--gray5);font-weight:500">הערות</th>
2333                    <th style="padding:9px 14px;text-align:right;color:var(--gray5);font-weight:500;white-space:nowrap">מקור</th>
2334                  </tr>
2335                </thead>
2336                <tbody id="debug-table-body"></tbody>
2337              </table>
2338            </div>
2339          </div>
2340        </div>
2341      </div>
2342
2343      <div id="view-null-domain" style="display:none">
2344        <div class="admin-header">
2345          <div class="admin-actions">
2346            <button class="btn-sm btn-ghost" onclick="loadNullDomainStores()">↺ רענן</button>
2347            <button id="nd-delete-orphans-btn" class="btn-sm btn-ghost" onclick="_ndDeleteOrphans()"
2348              style="color:#f97316;border-color:#7c2d12">
2349              🧹 מחק קופונים יתומים
2350            </button>
2351            <button id="nd-delete-btn" class="btn-sm" onclick="_ndDeleteSelected()"
2352              style="display:none;background:#7f1d1d;color:#fca5a5;border:1px solid #991b1b">
2353              🗑 מחק נבחרים
2354            </button>
2355          </div>
2356          <div class="admin-title">
2356🌐 אין דומיין</div>
2357        </div>
2358        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
2359          חנויות שחסר להן דומיין — הקופונים שלהן מוסתרים מהמשתמשים עד שיתוקן.
2360        </div>
2361        <div class="table-wrap">
2362          <table id="nulldomain-table">
2363            <thead><tr>
2364              <th style="width:36px;text-align:center">
2365                <input type="checkbox" id="nd-select-all" onchange="_ndToggleAll(this.checked)"
2366                  style="cursor:pointer;width:15px;height:15px;accent-color:var(--accent)">
2367              </th>
2368              <th style="width:36px;color:var(--gray5)">#</th>
2369              <th style="min-width:130px">שם אנגלי <span style="color:#fc8181">*</span></th>
2370              <th style="min-width:120px">שם עברי <span style="color:#fc8181">*</span></th>
2371              <th style="width:60px;text-align:center">קופונים</th>
2372              <th>שמות נוספים (aliases)</th>
2373              <th style="min-width:160px">מקור</th>
2374              <th style="min-width:160px">דומיין <span style="color:#fc8181">*</span></th>
2375              <th style="width:150px"></th>
2376            </tr></thead>
2377            <tbody id="nulldomain-tbody">
2378              <tr><td colspan="9" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2379            </tbody>
2380          </table>
2381        </div>
2382      </div>
2383
2384      <div id="view-trusted-international" style="display:none">
2385        <div class="admin-header">
2386          <div class="admin-actions">
2387            <button class="btn-sm btn-primary" onclick="_tiAddPrompt()">+ הוסף דומיין</button>
2388            <button class="btn-sm btn-ghost" onclick="loadTrustedInternational()">↺ רענן</button>
2389          </div>
2390          <div class="admin-title">🌍 אתרים בינלאומיים מורשים</div>
2391        </div>
2392        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
2393          רשימת דומיינים בינלאומיים שמותר לאסוף מהם קופונים דרך promocodis.co.il. אתרים שאינם ברשימה, אינם ישראלים ואין להם תוכן בעברית — יידלגו.
2394        </div>
2395        <div class="table-wrap">
2396          <table id="trusted-intl-table">
2397            <thead><tr>
2398              <th style="width:36px;color:var(--gray5)">#</th>
2399              <th>דומיין</th>
2400              <th>נוסף</th>
2401              <th style="width:100px;text-align:center">פעולות</th>
2402            </tr></thead>
2403            <tbody id="trusted-intl-tbody">
2404              <tr><td colspan="4" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2405            </tbody>
2406          </table>
2407        </div>
2408      </div>
2409
2410      <div id="view-blocked-sites" style="display:none">
2411        <div class="admin-header">
2412          <div class="admin-actions">
2413            <button class="btn-sm btn-ghost" onclick="loadBlockedSites()">↺ רענן</button>
2414          </div>
2415          <div class="admin-title">🚫 אתרים חסומים</div>
2416        </div>
2417        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
2418          אתרים שנחסמו — קופונים מהם לא יאספו. ניתן למחוק קופונים קיימים שהגיעו מאתרים אלה.
2419        </div>
2420        <div class="table-wrap">
2421          <table id="blocked-sites-table">
2422            <thead><tr>
2423              <th style="width:36px;color:var(--gray5)">#</th>
2424              <th>אתר</th>
2425              <th>תיאור האתר</th>
2426              <th>למה נחסם</th>
2427              <th style="width:130px;text-align:center">פעולות</th>
2428            </tr></thead>
2429            <tbody id="blocked-sites-tbody">
2430              <tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2431            </tbody>
2432          </table>
2433        </div>
2434      </div>
2435
2436      <div id="view-stores" style="display:none">
2437        <div class="admin-header">
2438          <div class="admin-actions">
2439            <button class="btn-sm btn-ghost" onclick="loadStoresView()">↺ רענן</button>
2440            <input id="stores-search" type="text" placeholder="חיפוש חנות..." oninput="_storesFilter()"
2441              style="background:var(--gray2);border:1px solid var(--gray3);border-radius:8px;padding:6px 12px;font-size:12px;color:var(--white);width:180px;direction:rtl">
2442            <select id="stores-sort-sel" onchange="_storesSetSortKey(this.value)" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 8px;font-size:12px;cursor:pointer;outline:none;direction:rtl">
2443              <option value="name">מיין: שם</option>
2444              <option value="coupons">מיין: קופונים</option>
2445              <option value="domain">
2445מיין: דומיין</option>
2446            </select>
2447            <button id="stores-sort-dir-btn" onclick="_storesToggleDir()" title="הפך כיוון מיון" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 9px;font-size:13px;cursor:pointer;line-height:1">↑</button>
2448            <div style="display:flex;gap:4px;margin-right:4px">
2449              <button id="stores-origin-all" class="btn-sm" onclick="_storesOriginFilter('all')" style="font-size:11px;background:var(--accent);color:#000">הכל</button>
2450              <button id="stores-origin-il"  class="btn-sm btn-ghost" onclick="_storesOriginFilter('il')"  style="font-size:11px">🇮🇱 IL</button>
2451              <button id="stores-origin-int" class="btn-sm btn-ghost" onclick="_storesOriginFilter('int')" style="font-size:11px">🌍 INT</button>
2452              <button id="stores-origin-null" class="btn-sm btn-ghost" onclick="_storesOriginFilter('null')" style="font-size:11px">❓ אין</button>
2453            </div>
2454          </div>
2455          <div class="admin-title">🏪 חנויות</div>
2456        </div>
2457        <div class="table-wrap">
2458          <table id="stores-table">
2459            <thead><tr>
2460              <th style="width:36px;color:var(--gray5)">#</th>
2461              <th style="min-width:130px">שם חנות</th>
2462              <th style="min-width:120px">שם חנות עברית</th>
2463              <th style="width:55px;text-align:center">קופונים</th>
2464              <th style="min-width:170px">דומיין</th>
2465              <th style="min-width:200px">Aliases</th>
2466              <th style="width:44px;text-align:center">לוגו</th>
2467              <th style="width:70px;text-align:center">מדינה</th>
2468              <th style="width:120px"></th>
2469            </tr></thead>
2470            <tbody id="stores-tbody">
2471              <tr><td colspan="6" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2472            </tbody>
2473          </table>
2474        </div>
2475        <div id="stores-pagination"></div>
2476      </div>
2477
2478      <div id="view-orphan-coupons" style="display:none">
2479        <div class="admin-header">
2480          <div class="admin-actions">
2481            <button class="btn-sm btn-ghost" onclick="loadOrphanCoupons()">↺ רענן</button>
2482            <button id="orphan-search-btn" class="btn-sm" onclick="_orphanSearchStores()"
2483              style="display:none;background:var(--accent);color:#000;border:none;font-weight:600">
2484              🔍 חפש חנויות
2485            </button>
2486            <button id="orphan-delete-selected-btn" class="btn-sm" onclick="_deleteSelectedOrphans()"
2487              style="display:none;background:#7f1d1d;color:#fca5a5;border:1px solid #991b1b">
2488              🗑 מחק נבחרים (<span id="orphan-selected-count">0</span>)
2489            </button>
2490            <button id="orphan-delete-btn" class="btn-sm" onclick="_deleteOrphanCoupons()"
2491              style="display:none;background:#7f1d1d;color:#fca5a5;border:1px solid #991b1b;opacity:0.6">
2492              🗑 מחק הכל
2493            </button>
2494          </div>
2495          <div class="admin-title">🔗 קופונים ללא דומיין</div>
2496        </div>
2497        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
2498          קופונים שהחנות שלהם אינה קיימת בטבלת החנויות — ממקור כלשהו (טלגרם, סריקה, ניוזלטר). אלו מוסתרים ממשתמשים ויש למחוק אותם.
2499        </div>
2500        <div class="table-wrap">
2501          <table id="orphan-table">
2502            <thead><tr>
2503              <th style="width:32px;padding:8px 4px 8px 10px">
2504                <input type="checkbox" id="orphan-check-all" onchange="_orphanToggleAll(this.checked)"
2505                  style="cursor:pointer;accent-color:var(--accent)">
2506              </th>
2507              <th>חנות (לא קיימת)</th>
2508              <th>קוד</th>
2509              <th>מקור</th>
2510              <th>תאריך סריקה</th>
2511            </tr></thead>
2512            <tbody id="orphan-tbody">
2513              <tr><td colspan="6" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2514            </tbody>
2515          </table>
2516        </div>
2517      </div>
2518
2519      <!-- orphan store search modal -->
2520      <div id="orphan-search-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:9000;align-items:center;justify-content:center">
2521        <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;width:520px;max-width:95vw;max-height:80vh;display:flex;flex-direction:column">
2522          <div style="padding:18px 20px 14px;border-bottom:1px solid var(--gray3);display:flex;align-items:center;justify-content:space-between">
2523            <div>
2524              <div style="font-size:15px;font-weight:600;color:var(--white)">תוצאות חיפוש חנויות</div>
2525              <div id="orphan-modal-sub" style="font-size:12px;color:var(--gray5);margin-top:2px">מחפש...</div>
2526            </div>
2527            <button onclick="_orphanModalClose()" style="background:none;border:none;color:var(--gray5);font-size:20px;cursor:pointer;line-height:1">×</button>
2528          </div>
2529          <div id="orphan-modal-list" style="overflow-y:auto;flex:1;padding:14px 20px;display:flex;flex-direction:column;gap:8px">
2530            <div style="color:var(--gray5);text-align:center;padding:24px">מחפש התאמות...</div>
2531          </div>
2532          <div style="padding:14px 20px;border-top:1px solid var(--gray3);display:flex;align-items:center;justify-content:space-between">
2533            <div id="orphan-modal-summary" style="font-size:12px;color:var(--gray5)"></div>
2534            <div style="display:flex;gap:8px">
2535              <button onclick="_orphanModalClose()" style="padding:7px 14px;background:var(--gray2);color:var(--gray5);border:1px solid var(--gray3);border-radius:8px;font-size:13px;
2535cursor:pointer">ביטול</button>
2536              <button id="orphan-modal-approve" onclick="_orphanModalApprove()" disabled
2537                style="padding:7px 16px;background:#166534;color:#bbf7d0;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;opacity:0.4">
2538                אשר מיזוג
2539              </button>
2540            </div>
2541          </div>
2542        </div>
2543      </div>
2544
2545      <!-- ── אין מדינה — stores with origin IS NULL ───────────────────────── -->
2546      <div id="view-no-origin" style="display:none">
2547        <div class="admin-header">
2548          <div class="admin-actions">
2549            <button class="btn-sm btn-ghost" onclick="loadNoOriginStores()">↺ רענן</button>
2550          </div>
2551          <div class="admin-title">🌍 אין מדינה</div>
2552        </div>
2553        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
2554          חנויות שעדיין לא זוהה להן מדינה (origin=NULL). יש לבדוק ידנית ולהגדיר IL / INT.
2555        </div>
2556        <div class="table-wrap">
2557          <table id="no-origin-table">
2558            <thead><tr>
2559              <th style="width:36px;color:var(--gray5)">#</th>
2560              <th>שם חנות</th>
2561              <th>דומיין</th>
2562              <th style="width:55px;text-align:center">קופונים</th>
2563              <th style="width:160px;text-align:center">מדינה</th>
2564            </tr></thead>
2565            <tbody id="no-origin-tbody">
2566              <tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2567            </tbody>
2568          </table>
2569        </div>
2570        <div id="no-origin-pagination"></div>
2571      </div>
2572
2573      <!-- ── תצוגת קופונים — admin coupon preview ───────────────────────────── -->
2574      <div id="view-coupon-preview" style="display:none">
2575        <div class="admin-header">
2576          <div class="admin-actions">
2577            <button class="btn-sm btn-ghost" onclick="loadCouponPreview()">↺ רענן</button>
2578            <div style="display:flex;gap:6px">
2579              <button id="cprev-filter-all" class="btn-sm" onclick="_cprevSetFilter('all')"
2580                style="background:var(--accent);color:#000;font-size:11px">הכל</button>
2581              <button id="cprev-filter-il" class="btn-sm btn-ghost" onclick="_cprevSetFilter('il')"
2582                style="font-size:11px">🇮🇱 ישראל</button>
2583              <button id="cprev-filter-int" class="btn-sm btn-ghost" onclick="_cprevSetFilter('int')"
2584                style="font-size:11px">🌍 בינלאומי</button>
2585            </div>
2586          </div>
2587          <div class="admin-title">🎫 תצוגת קופונים</div>
2588        </div>
2589        <div id="coupon-preview-grid" style="padding:20px;display:grid;grid-template-columns:1fr;gap:8px">
2590          <div style="color:var(--gray5);text-align:center;grid-column:1/-1;padding:40px">טוען...</div>
2591        </div>
2592        <div id="coupon-preview-pagination" style="padding:0 20px 20px"></div>
2593      </div>
2594
2595      <!-- ── Dedicated analytics views (panels moved here by JS) ───────────── -->
2596      <div id="view-analytics-usage" style="display:none;padding:24px"></div>
2597      <div id="view-analytics-speed" style="display:none;padding:24px"></div>
2598      <div id="view-analytics-popup" style="display:none;padding:24px"></div>
2599      <div id="view-analytics-extension" style="display:none;padding:24px"></div>
2600      <div id="view-analytics-buttons" style="display:none;padding:24px"></div>
2601      <div id="view-user-profiles" style="display:none;padding:24px"></div>
2602      <div id="view-search-intel" style="display:none;padding:24px"></div>
2603      <div id="view-demand-gaps" style="display:none;padding:24px"></div>
2604      <div id="view-traffic-sources" style="display:none;padding:24px"></div>
2605      <div id="view-geo-map" style="display:none;padding:24px"></div>
2606      <div id="view-device-stats" style="display:none;padding:24px"></div>
2607      <div id="view-js-errors" style="display:none;padding:24px"></div>
2608
2609      <!-- ── קטגוריות חנויות ─────────────────────────────────────────────── -->
2610      <div id="view-store-categories" style="display:none">
2611        <div class="admin-header">
2612          <div class="admin-actions">
2613            <button class="btn-sm btn-ghost" onclick="loadStoreCategoriesView()">↺ רענן</button>
2614            <input id="cat-search" type="text" placeholder="חיפוש חנות..." oninput="_catFilter()"
2615              style="background:var(--gray2);border:1px solid var(--gray3);border-radius:8px;padding:6px 12px;font-size:12px;color:var(--white);width:180px;direction:rtl">
2616            <select id="cat-filter-sel" onchange="_catFilter()" style="background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:8px;padding:3px 8px;font-size:12px;cursor:pointer;outline:none;direction:rtl">
2617              <option value="all">הכל</option>
2618              <option value="empty">ללא קטגוריה</option>
2619              <option value="has">עם קטגוריה</option>
2620            </select>
2621          </div>
2622          <div class="admin-title">🏷 קטגוריות חנויות</div>
2623        </div>
2624        <div style="padding:0 0 14px;font-size:13px;color:var(--gray6);line-height:1.6">
2625          לחץ על תגי הקטגוריה כדי להוסיף/להסיר. השינוי נשמר מיד לאחר לחיצה.
2626        </div>
2627        <div class="table-wrap">
2628          <table id="cat-table">
2629            <thead><tr>
2630              <th style="width:36px;color:var(--gray5)">#</th>
2631              <th style="min-width:140px">שם חנות</th>
2632              <th style="min-width:120px">שם עברי</th>
2633              <th style="min-width:160px">דומיין</th>
2634              <th>קטגוריות</th>
2635              <th style="width:60px;text-align:center">סטטוס</th>
2636            </tr></thead>
2637            <tbody id="cat-tbody">
2638              <tr><td colspan="6" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>
2639            </tbody>
2640          </table>
2641        </div>
2642        <div id="cat-pagination"></div>
2643      </div>
2644
2645    </div>
2646    <div id="mob-backdrop" onclick="closeMobMenu()"></div>
2647    <div class="sidebar" id="admin-sidebar">
2648      <button id="mob-close-btn" onclick="closeMobMenu()">✕</button>
2649      <div class="sidebar-section">
2650        <span class="sidebar-label">כללי</span>
2651        <button class="sidebar-item on" onclick="setAdminView(this,'dashboard')"><div class="sidebar-icon" style="background:#1a1a1a">◈</div> דשבורד</button>
2652        <button class="sidebar-item" onclick="setAdminView(this,'coupons')"><div class="sidebar-icon" style="background:#1a1a1a">⊞</div> כל הקופונים<span class="sidebar-count" id="adm-sidebar-count">—</span></button>
2653        <button class="sidebar-item" onclick="setAdminView(this,'stores')"><div class="sidebar-icon" style="background:#0a1a2a">🏪</div> כל החנויות<span class="sidebar-count" id="src-stores-count">—</span></button>
2654        <button class="sidebar-item" onclick="setAdminView(this,'store-categories');loadStoreCategoriesView()"><div class="sidebar-icon" style="background:#0a2a1a">🏷</div> קטגוריות חנויות<span class="sidebar-count" id="src-cat-count">—</span></button>
2655      </div>
2656      <div class="sidebar-section">
2657        <span class="sidebar-label">אנליטיקה</span>
2658        <button class="sidebar-item" onclick="setAdminView(this,'analytics-usage')"><div class="sidebar-icon" style="background:#0d1a2a">📊</div> אנליטיקס שימוש</button>
2659        <button class="sidebar-item" onclick="setAdminView(this,'analytics-speed')"><div class="sidebar-icon" style="background:#0a1a0a">⚡</div> זמני חיפוש</button>
2660        <button class="sidebar-item" onclick="setAdminView(this,'analytics-popup')"><div class="sidebar-icon" style="background:#1a0d0a">🎫</div> פופאפ קופונים</button>
2661        <button class="sidebar-item" onclick="setAdminView(this,'analytics-extension')"><div class="sidebar-icon" style="background:#0a1a10">🧩</div> תוסף Chrome</button>
2662        <button class="sidebar-item" onclick="setAdminView(this,'analytics-buttons')"><div class="sidebar-icon" style="background:#1a1a0a">🖱️</div> תפריט וכפתורים</button>
2663        <span class="sidebar-label">משתמשים</span>
2664        <button class="sidebar-item" onclick="setAdminView(this,'user-profiles')"><div class="sidebar-icon" style="background:#0d1a2a">👤</div> פרופילי משתמשים</button>
2665        <button class="sidebar-item" onclick="setAdminView(this,'search-intel')"><div class="sidebar-icon" style="background:#0a1a0a">🔍</div> חיפושים</button>
2666        <button class="sidebar-item" onclick="setAdminView(this,'demand-gaps')"><div class="sidebar-icon" style="background:#1a0a0a">🕳️</div> ביקוש ללא קופונים</button>
2667        <button class="sidebar-item" onclick="setAdminView(this,'traffic-sources')"><div class="sidebar-icon" style="background:#0a0a1a">🚦</div> מקורות תנועה</button>
2668        <button class="sidebar-item" onclick="setAdminView(this,'geo-map')"><div class="sidebar-icon" style="background:#0a1a10">🗺️</div> גיאוגרפיה</button>
2669        <button class="sidebar-item" onclick="setAdminView(this,'device-stats')"><div class="sidebar-icon" style="background:#1a1a0a">💻</div> מכשירים ודפדפנים</button>
2670        <button class="sidebar-item" onclick="setAdminView(this,'js-errors')"><div class="sidebar-icon" style="background:#1a0a0a">🐛</div> שגיאות JS</button>
2671      </div>
2672      <div class="sidebar-section">
2673        <span class="sidebar-label">מקורות</span>
2674        <button class="sidebar-item" onclick="setAdminView(this,'sources')"><div class="sidebar-icon" style="background:#1a2a1a">▲</div> טלגרם<span class="sidebar-count" id="src-tg-count">—</span></button>
2675        <button class="sidebar-item" onclick="setAdminView(this,'coupon-sites')"><div class="sidebar-icon" style="background:#1a2a1a">🏷</div> אתרי קופונים בלבד<span class="sidebar-count" id="src-coupon-sites-count">—</span></button>
2676        <button class="sidebar-item" onclick="setAdminView(this,'web')"><div class="sidebar-icon" style="background:#2a2a1a">◆</div> אתרים שנסרקו<span class="sidebar-count" id="src-web-count">—</span></button>
2677        <button class="sidebar-item" onclick="setAdminView(this,'instagram')"><div class="sidebar-icon" style="background:#2a1a2a">📸</div> אינפלואנסרים<span class="sidebar-count" id="src-ig-count">—</span></button>
2678        <button class="sidebar-item" onclick="setAdminView(this,'affiliate')"><div class="sidebar-icon" style="background:#1a2a1a">🤝</div> שותפויות<span class="sidebar-count" id="src-aff-count">—</span></button>
2679        <button class="sidebar-item" onclick="setAdminView(this,'blocked-sites')"><div class="sidebar-icon" style="background:#2a0a0a">🚫</div> אתרים חסומים<span class="sidebar-count" id="src-blocked-count">—</span></button>
2680        <button class="sidebar-item" onclick="setAdminView(this,'trusted-international')"><div class="sidebar-icon" style="background:#0a2a0a">🌍</div> בינלאומי מורשה<span class="sidebar-count" id="src-trusted-intl-count">—</span></button>
2681      </div>
2682      <div class="sidebar-section">
2683        <span class="sidebar-label">אוטומציה</span>
2684        <button class="sidebar-item" onclick="setAdminView(this,'automation-scans')"><div class="sidebar-icon" style="background:#0a1a2a">🤖</div> סריקות</button>
2685        <button class="sidebar-item" onclick="setAdminView(this,'newsletters')"><div class="sidebar-icon" style="background:#1a1a2a">✉</div> ניוזלטרים<span class="sidebar-count" id="src-nl-count">0</span></button>
2686      </div>
2687      <div class="sidebar-section">
2688        <span class="sidebar-label">בדיקות</span>
2689        <button class="sidebar-item" onclick="setAdminView(this,'null-domain')"><div class="sidebar-icon" style="background:#2a1a00">🌐</div> אין דומיין<span class="sidebar-count" id="src-nulldomain-count">—</span></button>
2690        <button class="sidebar-item" onclick="setAdminView(this,'orphan-coupons')"><div class="sidebar-icon" style="background:#2a0a0a">🔗</div> קופונים ללא דומיין<span class="sidebar-count" id="src-orphan-count">—</span></button>
2691        <button class="sidebar-item" onclick="setAdminView(this,'no-origin')"><div class="sidebar-icon" style="background:#1a1a2a">🌍</div> אין מדינה<span class="sidebar-count" id="src-noorigin-count">—</span></button>
2692        <button class="sidebar-item" onclick="setAdminView(this,'coupon-preview')"><div class="sidebar-icon" style="background:#0a1a0a">🎫</div> תצוגת קופונים</button>
2693        <button class="sidebar-item" onclick="setAdminView(this,'debug')"><div class="sidebar-icon" style="background:#0d1a0d">🐛</div> Pipeline Debugger</button>
2694      </div>
2695      <div class="sidebar-section">
2696        <span class="sidebar-label">מערכת</span>
2697        <button class="sidebar-item" onclick="setAdminView(this,'users')"><div class="sidebar-icon" style="background:#1a1a2a">👥</div> משתמשים</button>
2698        <button class="sidebar-item" onclick="setAdminView(this,'contact-messages');_loadContactMessages()"><div class="sidebar-icon" style="background:#1a1a2a">✉️</div> הודעות קשר<span class="sidebar-count" id="contact-badge">—</span></button>
2699        <button class="sidebar-item" onclick="setAdminView(this,'settings')"><div class="sidebar-icon" style="background:#1a1a1a">⚙</div> הגדרות</button>
2700        <button class="sidebar-item" onclick="setAdminView(this,'logs')"><div class="sidebar-icon" style="background:#1a1a1a">◎</div> לוגים</button>
2701      </div>
2702      <div class="sidebar-section">
2703        <span class="sidebar-label">נסיונות</span>
2704        <button class="sidebar-item" onclick="setAdminView(this,'store-pages-demo');_loadStoreDemo()"><div class="sidebar-icon" style="background:#1a2a1a">🏪</div> דפי חנויות<span class="sidebar-count" id="store-demo-badge"></span></button>
2705        <button class="sidebar-item" onclick="setAdminView(this,'home-demo');_hpRender()"><div class="sidebar-icon" style="background:#1a2a2a">🏠</div> עמוד בית</button>
2706        <button class="sidebar-item" onclick="setAdminView(this,'hide-mechanism')"><div class="sidebar-icon" style="background:#1a1a2a">🎨</div> מנגנון הסתרה</button>
2707        <button class="sidebar-item" onclick="setAdminView(this,'expired')"><div class="sidebar-icon" style="background:#2a1a1a">⏰</div> פג תוקף</button>
2708        <button class="sidebar-item" onclick="setAdminView(this,'search-anim')"><div class="sidebar-icon" style="background:#1a1a2a">⏳</div> אנימציית חיפוש</button>
2709        <button class="sidebar-item" onclick="setAdminView(this,'mole')"><div class="sidebar-icon" style="background:#2a1a2a">🎯</div> משחקים</button>
2710        <button class="sidebar-item" onclick="setAdminView(this,'minigames')"><div class="sidebar-icon" style="background:#1a2a1a">🎬</div> אנימציות טעינה</button>
2711      </div>
2712    </div>
2713  </div>
2714</div>
2715
2716<div class="toast" id="toast"></div>
2717
2718<!-- ── Edit Source Modal ──────────────────────────────────────────── -->
2719<!-- ── Edit User Modal ────────────────────────────────────────────────── -->
2720<div id="edit-user-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.8);z-index:3000;align-items:center;justify-content:center;backdrop-filter:blur(8px)" onclick="if(event.target===this)_closeEditUserModal()">
2721  <div style="background:var(--gray2);border:1px solid var(--gray3);border-radius:20px;padding:32px;width:360px;max-width:92vw">
2722    <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:20px;text-align:right">ערוך משתמש</div>
2723    <input type="hidden" id="eu-id"/>
2724    <div style="margin-bottom:12px;text-align:right">
2725      <label style="font-size:12px;color:var(--gray5);display:block;margin-bottom:6px">שם משתמש</label>
2726      <input id="eu-username" type="text" dir="ltr" autocomplete="off"
2727        style="width:100%;padding:12px 14px;background:var(--gray3);border:1px solid var(--gray4);border-radius:10px;color:var(--white);font-size:14px;outline:none;font-family:'DM Sans',sans-serif"
2728        onfocus="this.style.borderColor='var(--accent)'" onblur="this.style.borderColor='var(--gray4)'"/>
2729    </div>
2730    <div style="margin-bottom:12px;text-align:right">
2731      <label style="font-size:12px;color:var(--gray5);display:block;margin-bottom:6px">תפקיד</label>
2732      <select id="eu-role" style="width:100%;padding:12px 14px;background:var(--gray3);border:1px solid var(--gray4);border-radius:10px;color:var(--white);font-size:14px;outline:none;font-family:'DM Sans',sans-serif;cursor:pointer"
2733        onfocus="this.style.borderColor='var(--accent)'" onblur="this.style.borderColor='var(--gray4)'">
2734        <option value="admin">👑 אדמין</option>
2735        <option value="operator">🔧 אופרטור</option>
2736        <option value="user">👤 משתמש</option>
2737      </select>
2738    </div>
2739    <div style="margin-bottom:16px;text-align:right">
2740      <label style="font-size:12px;color:var(--gray5);display:block;margin-bottom:6px">סיסמה חדשה <span style="color:var(--gray5);font-weight:400">(השאר ריק כדי לשמור את הנוכחית)</span></label>
2741      <input id="eu-password" type="password" dir="ltr" autocomplete="new-password" placeholder="••••••••"
2742        style="width:100%;padding:12px 14px;background:var(--gray3);border:1px solid var(--gray4);border-radius:10px;color:var(--white);font-size:14px;outline:none;font-family:'DM Sans',sans-serif"
2743        onfocus="this.style.borderColor='var(--accent)'" onblur="this.style.borderColor='var(--gray4)'"
2744        onkeydown="if(event.key==='Enter')_submitEditUser()"/>
2745    </div>
2746    <div id="eu-error" style="display:none;color:#ff6b6b;font-size:13px;margin-bottom:12px;text-align:right"></div>
2747    <div style="display:flex;gap:8px;justify-content:flex-end">
2748      <button onclick="_closeEditUserModal()" style="padding:10px 18px;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;color:var(--gray6);font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif">ביטול</button>
2749      <button onclick="_submitEditUser()" style="padding:10px 18px;background:var(--accent);border:none;border-radius:8px;color:var(--black);font-size:13px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif">שמור</button>
2750    </div>
2751  </div>
2752</div>
2753
2754<!-- ── Login Modal ─────────────────────────────────────────────────────── -->
2755<div id="login-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.88);z-index:3000;align-items:center;justify-content:center;backdrop-filter:blur(12px)">
2756  <div style="background:var(--gray2);border:1px solid var(--gray3);border-radius:24px;padding:40px 36px;width:380px;max-width:92vw;text-align:center;box-shadow:0 32px 80px rgba(0,0,0,0.5)">
2757    <div style="width:52px;height:52px;background:linear-gradient(135deg,rgba(0,232,122,0.15),rgba(0,232,122,0.05));border:1px solid rgba(0,232,122,0.2);border-radius:14px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px;font-size:22px">🔐</div>
2758    <div style="font-family:'Syne',sans-serif;font-size:22px;font-weight:800;margin-bottom:4px">קופון<span style="color:var(--accent)">AI</span></div>
2759    <div style="font-size:13px;color:var(--gray5);margin-bottom:28px">כניסה לאזור הניהול</div>
2760    <form id="login-form" onsubmit="submitLogin(event)" autocomplete="on" style="text-align:right">
2761      <div style="margin-bottom:12px">
2762        <label style="font-size:12px;color:var(--gray5);display:block;margin-bottom:6px;text-align:right">שם משתמש</label>
2763        <input id="login-username" type="text" name="username" placeholder="admin" dir="ltr" autocomplete="username"
2764          style="width:100%;padding:13px 16px;background:var(--gray3);border:1px solid var(--gray4);border-radius:10px;color:var(--white);font-size:15px;outline:none;font-family:'DM Sans',sans-serif;text-align:left;transition:border-color 0.2s"
2765          onfocus="this.style.borderColor='var(--accent)'" onblur="this.style.borderColor='var(--gray4)'"/>
2766      </div>
2767      <div style="margin-bottom:16px">
2768        <label style="font-size:12px;color:var(--gray5);display:block;margin-bottom:6px;text-align:right">סיסמה</label>
2769        <input id="login-password" type="password" name="password" placeholder="••••••••" dir="ltr" autocomplete="current-password"
2770          style="width:100%;padding:13px 16px;background:var(--gray3);border:1px solid var(--gray4);border-radius:10px;color:var(--white);font-size:15px;outline:none;font-family:'DM Sans',sans-serif;text-align:left;transition:border-color 0.2s"
2771          onfocus="this.style.borderColor='var(--accent)'" onblur="this.style.borderColor='var(--gray4)'"/>
2772      </div>
2773      <div id="login-error" style="display:none;background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);border-radius:8px;padding:10px;color:#ff6b6b;font-size:13px;margin-bottom:14px;text-align:center">שם משתמש או סיסמה שגויים</div>
2774      <button type="submit" style="width:100%;padding:14px;background:var(--accent);color:var(--black);border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif;transition:opacity 0.15s" onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">כניסה</button>
2775    </form>
2776    <button onclick="_hideLoginModal()" style="width:100%;padding:10px;background:transparent;color:var(--gray5);border:none;font-size:13px;
2776cursor:pointer;margin-top:10px;font-family:'DM Sans',sans-serif">ביטול</button>
2777  </div>
2778</div>
2779
2780<div id="edit-source-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:2000;align-items:center;justify-content:center">
2781  <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:20px;padding:28px 32px;width:min(420px,92vw);direction:rtl;position:relative">
2782    <button onclick="closeEditSourceModal()" style="position:absolute;top:14px;left:16px;background:var(--gray3);border:none;border-radius:6px;color:var(--gray5);width:28px;height:28px;cursor:pointer;font-size:14px">✕</button>
2783    <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:20px">✏️ עריכת מקור</div>
2784    <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:6px">שם תצוגה</label>
2785    <input id="es-name-input" placeholder="שם הערוץ"
2786      style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:10px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:rtl;margin-bottom:14px"/>
2787    <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:6px">קישור</label>
2788    <input id="es-url-input" placeholder="https://t.me/channelname"
2789      oninput="_esUrlChanged(this.value)"
2790      onpaste="setTimeout(()=>_esUrlChanged(this.value),0)"
2791      style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:10px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:ltr"/>
2792    <div id="es-error" style="color:#fc8181;font-size:12px;margin-top:8px;display:none"></div>
2793    <div style="margin-top:20px;display:flex;gap:10px">
2794      <button id="es-submit-btn" onclick="submitEditSource()" style="background:var(--accent);color:var(--black);border:none;border-radius:10px;padding:10px 24px;font-size:14px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif">שמור</button>
2795      <button onclick="closeEditSourceModal()" style="background:var(--gray3);color:var(--gray6);border:none;border-radius:10px;padding:10px 18px;font-size:14px;cursor:pointer;font-family:'DM Sans',sans-serif">ביטול</button>
2796    </div>
2797  </div>
2798</div>
2799
2800<!-- ── Add Source Modal ───────────────────────────────────────────── -->
2801<div id="add-source-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:2000;align-items:center;justify-content:center">
2802  <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:20px;padding:28px 32px;width:min(420px,92vw);direction:rtl;position:relative">
2803    <button onclick="closeAddSourceModal()" style="position:absolute;top:14px;left:16px;background:var(--gray3);border:none;border-radius:6px;color:var(--gray5);width:28px;height:28px;cursor:pointer;font-size:14px">✕</button>
2804    <div id="as-modal-title" style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:20px">+ הוסף ערוץ</div>
2805    <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:6px">קישור</label>
2806    <input id="as-url-input" placeholder="t.me/channelname"
2807      onkeydown="if(event.key==='Enter')submitAddSource()"
2808      style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:10px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:ltr"/>
2809    <div id="as-url-hint" style="font-size:11px;color:var(--gray5);margin-top:6px">לדוגמא: t.me/channelname</div>
2810    <div id="as-error" style="color:#fc8181;font-size:12px;margin-top:8px;display:none"></div>
2811    <div style="margin-top:20px;display:flex;gap:10px">
2812      <button id="as-submit-btn" onclick="submitAddSource()" style="background:var(--accent);color:var(--black);border:none;border-radius:10px;padding:10px 24px;font-size:14px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif">רשום מקור</button>
2813      <button onclick="closeAddSourceModal()" style="background:var(--gray3);color:var(--gray6);border:none;border-radius:10px;padding:10px 18px;font-size:14px;cursor:pointer;font-family:'DM Sans',sans-serif">ביטול</button>
2814    </div>
2815  </div>
2816</div>
2817
2818<!-- ── Add Coupon Modal ────────────────────────────────────────────── -->
2819<div id="add-coupon-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:2000;align-items:center;justify-content:center">
2820  <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:20px;padding:28px 32px;width:min(480px,92vw);direction:rtl;position:relative">
2821    <button onclick="closeAddCouponModal()" style="position:absolute;top:14px;left:16px;background:var(--gray3);border:none;border-radius:6px;color:var(--gray5);width:28px;height:28px;cursor:pointer;font-size:14px">✕</button>
2822    <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:20px">+ הוסף קופון ידנית</div>
2823    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
2824      <div>
2825        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">חנות *</label>
2826        <input id="ac-store" placeholder="לדוגמא: Castro" style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:rtl"/>
2827      </div>
2828      <div>
2829        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">
2829קוד (או השאר ריק אם אין)</label>
2830        <input id="ac-code" placeholder="SAVE20 / NO_CODE" style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:ltr;text-transform:uppercase"/>
2831      </div>
2832      <div>
2833        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">הנחה *</label>
2834        <input id="ac-discount" placeholder="20% הנחה / משלוח חינם" style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:rtl"/>
2835      </div>
2836      <div>
2837        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">קטגוריה</label>
2838        <select id="ac-cat" style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;cursor:pointer">
2839          <option value="Fashion">אופנה</option><option value="Tech">טכנולוגיה</option>
2840          <option value="Food">אוכל</option><option value="Travel">נסיעות</option>
2841          <option value="Vacation">חופשה</option><option value="Home">בית</option>
2842          <option value="Beauty">יופי</option><option value="Health">בריאות</option>
2843          <option value="Kids">ילדים</option><option value="Sports">ספורט</option>
2844          <option value="Other">אחר</option>
2845        </select>
2846      </div>
2847      <div>
2848        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">תוקף (אופציונלי)</label>
2849        <input id="ac-expiry" type="date" style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif"/>
2850      </div>
2851      <div>
2852        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">מינימום הזמנה</label>
2853        <input id="ac-min" placeholder="₪200 מינימום" style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:rtl"/>
2854      </div>
2855      <div style="grid-column:1/-1">
2856        <label style="font-size:11px;color:var(--gray5);display:block;margin-bottom:4px">הערות</label>
2857        <input id="ac-notes" placeholder="תנאים, הגבלות..." style="width:100%;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;font-family:'DM Sans',sans-serif;direction:rtl"/>
2858      </div>
2859    </div>
2860    <div style="margin-top:20px;display:flex;gap:10px;justify-content:flex-start">
2861      <button onclick="submitAddCoupon(this)" style="background:var(--accent);color:var(--black);border:none;border-radius:10px;padding:10px 24px;font-size:14px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif">שמור קופון</button>
2862      <button onclick="closeAddCouponModal()" style="background:var(--gray3);color:var(--gray6);border:none;border-radius:10px;padding:10px 18px;font-size:14px;cursor:pointer;font-family:'DM Sans',sans-serif">ביטול</button>
2863    </div>
2864    <div id="ac-error" style="color:#fc8181;font-size:12px;margin-top:10px;display:none"></div>
2865  </div>
2866</div>
2867
2868<footer style="background:var(--gray1);border-top:1px solid var(--gray3);padding:40px 32px;text-align:center;direction:rtl">
2869  <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:800;margin-bottom:8px">קופון<span style="color:var(--accent)">AI</span></div>
2870  <div style="font-size:13px;color:var(--gray5);margin-bottom:24px">מופעל על ידי AI — סורק קופונים מכל הרשת 24/7</div>
2871  <div style="display:flex;justify-content:center;gap:20px;flex-wrap:wrap;font-size:12px;margin-bottom:16px">
2872    <a href="/" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">ראשי</a>
2873    <span style="color:var(--gray4)">·</span>
2874    <a href="/about" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">אודות</a>
2875    <span style="color:var(--gray4)">·</span>
2876    <a href="/extension" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">תוסף Chrome</a>
2877    <span style="color:var(--gray4)">·</span>
2878    <a href="/contact" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">צור קשר</a>
2879    <span style="color:var(--gray4)">·</span>
2880    <a href="/terms" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">תנאי שימוש</a>
2881    <span style="color:var(--gray4)">·</span>
2882    <a href="/accessibility" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">הצהרת נגישות</a>
2883    <span style="color:var(--gray4)">·</span>
2884    <a href="/sitemap.xml" style="color:var(--gray6);text-decoration:none" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--gray6)'">מפת האתר</a>
2885  </div>
2886  <div style="font-size:11px;color:var(--gray5)">© 2025 קופוןAI · עוצב עם ❤️ בישראל</div>
2887</footer>
2888<!-- Honeypot: invisible to humans, bots follow it and get logged/banned -->
2889<a href="/api/honeypot" style="display:none;visibility:hidden;opacity:0;position:absolute;
2889left:-9999px" aria-hidden="true" tabindex="-1">sitemap-data</a>
vendor: 110 bytes, lines 2889-2891
2889
2890
2891<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
2891<script>
2892const _ADMIN_KEY = 'couponai_admin_auth'; // must be defined before any top-level code calls _isAdminAuthed()
2893const COUPONS = [];
2894let ALL_COUPONS = []; // full set for discount sorting
2895let pubCat = '', pubSearch = '', inSearch = false;
2896
2897// Store name -> URL slug. Must match the backend's stores.slug generation
2898// (lowercase, strip space/&/./'/_/- with no separator) so links always hit
2899// the real slug-based /store/{slug} route instead of 404ing.
2900function _storeSlug(name) { return (name || '').toLowerCase().replace(/[ &.'_-]/g, ''); }
2901
2902const COPY_ICON = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>`;
2903const THUMB_UP   = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9V5a3 3 0 00-3-3l-4 9v11h11.28a2 2 0 002-1.7l1.38-9a2 2 0 00-2-2.3H14z"/><path d="M7 22H4a2 2 0 01-2-2v-7a2 2 0 012-2h3"/></svg>`;
2904const THUMB_DOWN = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 15v4a3 3 0 003 3l4-9V2H5.72a2 2 0 00-2 1.7l-1.38 9a2 2 0 002 2.3H10z"/><path d="M17 2h2.67A2.31 2.31 0 0122 4v7a2.31 2.31 0 01-2.33 2H17"/></svg>`;
2905
2906// Store name → favicon domain mapping (Hebrew + English)
2907// STORE_DOMAINS is populated at runtime from /api/store-urls (DB-driven, admin-managed).
2908// Do NOT add hardcoded entries here — all store→domain mappings must come from the stores table.
2909const STORE_DOMAINS = {};
2910// Store name → hosted logo URL (Supabase Storage). Populated from /api/store-logos.
2911const STORE_LOGOS = {};
2912// Store canonical name → Hebrew display name. Populated from /api/store-he.
2913const STORE_HE = {};
2914// Store canonical name → active coupon count. Populated from /api/store-counts.
2915const STORE_COUNTS = {};
2916
2917// Title-case a store name: "keds israel" → "Keds Israel"
2918function _titleCase(s) {
2919  return (s || '').replace(/\b\w/g, c => c.toUpperCase());
2920}
2921
2922// Card display: Hebrew for Israeli domains (.co.il / .il), English title-case otherwise
2923function _storeDisplayCard(store) {
2924  const key    = (store || '').toLowerCase();
2925  const domain = STORE_DOMAINS[key] || '';
2926  const isIsraeli = domain.endsWith('.co.il') || domain.endsWith('.il');
2927  if (isIsraeli) return STORE_HE[key] || _titleCase(store);
2928  return _titleCase(store);
2929}
2930
2931// Modal title: "עברית | English" if Hebrew name exists, else just English title-case
2932function _storeDisplayModal(store) {
2933  const key = (store || '').toLowerCase();
2934  const he  = STORE_HE[key] || '';
2935  const en  = _titleCase(store);
2936  return he ? `${he} | ${en}` : en;
2937}
2938
2939// Resolves once loadCoupons() has finished populating STORE_DOMAINS + STORE_LOGOS.
2940let _storeDataReadyResolve;
2941const _storeDataReady = new Promise(r => { _storeDataReadyResolve = r; });
2942// Legacy placeholder kept so the fetch below can reference it — not used for lookup.
2943const _STORE_DOMAINS_LEGACY_PLACEHOLDER = {
2944  // ── International ──────────────────────────────────────────────────────────
2945  'iherb':'iherb.com','איהרב':'iherb.com','אי הרב':'iherb.com',
2946  'shein':'shein.com','שיין':'shein.com',
2947  'zara':'zara.com','זארה':'zara.com',
2948  'h&m':'hm.com','hm':'hm.com',
2949  'nike':'nike.com','נייק':'nike.com','נייקי':'nike.com',
2950  'adidas':'adidas.com','אדידס':'adidas.com',
2951  'amazon':'amazon.com','אמזון':'amazon.com',
2952  'aliexpress':'aliexpress.com','ali express':'aliexpress.com','עלי אקספרס':'aliexpress.com',
2953  'pandabuy':'pandabuy.com',
2954  'ebay':'ebay.com','איביי':'ebay.com',
2955  'asos':'asos.com',
2956  'mango':'mango.com','מנגו':'mango.com',
2957  'bershka':'bershka.com','ברשקה':'bershka.com',
2958  'pull&bear':'pullandbear.com',
2959  'stradivarius':'stradivarius.com',
2960  'dell':'dell.com','hp':'hp.com','lenovo':'lenovo.com',
2961  'apple':'apple.com','אפל':'apple.com',
2962  'samsung':'samsung.com','סמסונג':'samsung.com',
2963  'booking':'booking.com','בוקינג':'booking.com',
2964  'airbnb':'airbnb.com','אירבנב':'airbnb.com',
2965  'skyscanner':'skyscanner.com','agoda':'agoda.com',
2966  'nespresso':'nespresso.com',
2967  'wix':'wix.com',
2968  // ── Israeli fashion ────────────────────────────────────────────────────────
2969  'castro':'castro.com','קסטרו':'castro.com','castro fashion':'castro.com',
2970  'isracard':'isracard.co.il','ישראכרט':'isracard.co.il','israelicard':'isracard.co.il','israelicard (ישראכרט)':'isracard.co.il','isracart':'isracard.co.il',
2971  'terminalx':'terminalx.com','terminal x':'terminalx.com','טרמינל x':'terminalx.com','טרמינל איקס':'terminalx.com','טרמינל':'terminalx.com',
2972  'golf':'golf-il.co.il','גולף':'golf-il.co.il','גולף אנד קו':'golf-il.co.il','golf & co':'golf-il.co.il',
2973  'renuar':'renuar.co.il','רנואר':'renuar.co.il',
2974  'adika':'adikastyle.com','אדיקה':'adikastyle.com','adikastyle':'adikastyle.com','אדיקה סטייל':'adikastyle.com',
2975  'fox racing':'foxracing.com','fox racing israel':'foxracing.com',
2976  'fox':'fox.co.il','פוקס':'fox.co.il','פוקס הום':'fox.co.il','fox home':'foxhome.co.il',
2977  'delta':'delta.co.il','דלתא':'delta.co.il',
2978  'kitan':'kitan.co.il','כיתן':'kitan.co.il',
2979  'hoodies':'hoodies.co.il',
2980  'factory54':'factory54.co.il','factory 54':'factory54.co.il','פקטורי 54':'factory54.co.il',
2981  'like':'like.co.il','לייק':'like.co.il',
2982  'terminal x':'terminalx.com',
2983  // ── Israeli electronics ────────────────────────────────────────────────────
2984  'ksp':'ksp.co.il','קספ':'ksp.co.il','קי אס פי':'ksp.co.il',
2985  'bug':'bug.co.il','באג':'bug.co.il',
2986  'ivory':'ivory.co.il','אייבורי':'ivory.co.il',
2987  'zap':'zap.co.il','זאפ':'zap.co.il',
2988  'machsaneyhasmal':'payngo.co.il','מחסני חשמל':'payngo.co.il','מחסני':'payngo.co.il','payngo':'payngo.co.il','פייגו':'payngo.co.il',
2989  // ── Israeli food & pharma ──────────────────────────────────────────────────
2990  'shufersal':'shufersal.co.il','שופרסל':'shufersal.co.il',
2991  'rami levy':'rami-levy.co.il','רמי לוי':'rami-levy.co.il',
2992  'wolt':'wolt.com','וולט':'wolt.com',
2993  '10bis':'10bis.co.il','תן ביס':'10bis.co.il','ten bis':'10bis.co.il',
2994  'super-pharm':'super-pharm.co.il','super pharm':'super-pharm.co.il','סופר פארם':'super-pharm.co.il','superpharm':'super-pharm.co.il',
2995  'newpharm':'newpharm.co.il','ניו פארם':'newpharm.co.il',
2996  // ── Israeli home & retail ──────────────────────────────────────────────────
2997  'ace':'ace.co.il','אייס':'ace.co.il',
2998  'ikea':'ikea.com','איקאה':'ikea.com',
2999  'homecenter':'homecenter.co.il','הום סנטר':'homecenter.co.il','home center':'homecenter.co.il',
3000  'hamashbir':'hamashbir.co.il','המשביר':'hamashbir.co.il','המשביר לצרכן':'hamashbir.co.il',
3001  'buy2':'buy2.co.il',
3002  'next':'next.co.il','נקסט':'next.co.il',
3003  // ── Israeli travel & hotels ────────────────────────────────────────────────
3004  'fattal':'fattal.co.il','פתאל':'fattal.co.il','מלונות פתאל':'fattal.co.il',
3005  'dan hotels':'danhotels.com','מלון דן':'danhotels.com',
3006  'isrotel':'isrotel.co.il','ישרוטל':'isrotel.co.il',
3007  'eldan':'eldan.co.il','אלדן':'eldan.co.il',
3008  'hertz':'hertz.com',
3009  // ── Israeli books & culture ────────────────────────────────────────────────
3010  'steimatzky':'steimatzky.co.il','סטימצקי':'steimatzky.co.il','סטימצקי ספרים':'steimatzky.co.il',
3011  'tzomet sfarim':'tzomet-sfarim.co.il','צומת ספרים':'tzomet-sfarim.co.il',
3012  // ── Books & entertainment ──────────────────────────────────────────────────
3013  'olamhakolnoa':'olamhakolnoa.co.il','עולם הקולנוע':'yes.co.il','yes':'yes.co.il',
3014  // ── International deals ────────────────────────────────────────────────────
3015  "brad's deals":'bradsdeals.com','bradsdeals':'bradsdeals.com','ברדס דילס':'bradsdeals.com',
3016  // ── Fashion & lifestyle ────────────────────────────────────────────────────
3017  'nautica':'nautica.com','נאוטיקה':'nautica.com',
3018  'modibodi':'modibodi.com','מודיבודי':'modibodi.com',
3019  'soho':'soho-fashion.co.il','סוהו':'soho-fashion.co.il',
3020  'naaman':'naaman.co.il','נעמן':'naaman.co.il',
3021  'soltam':'soltam.co.il','סולתם':'soltam.co.il',
3022  'ivrit':'ivrit.co.il','עברית':'ivrit.co.il','עברית סטייל':'ivrit.co.il',
3023  'timberland':'timberland.com','טימברלנד':'timberland.com',
3024  'walgreens':'walgreens.com','וולגרינס':'walgreens.com',
3025  'uni k wax':'unikwax.com','unikwax':'unikwax.com','יוני קיי וקס':'unikwax.com',
3026  'tolmens':'tolmens.co.il','טולמנס':'tolmens.co.il','טולמנס דוט':'tolmens.co.il',
3027  'tyrolar':'tyrolar.co.il','טירולר':'tyrolar.co.il',
3028  'mania jeans':'maniajeans.co.il','מאניה ג\'ינס':'maniajeans.co.il','מאניה':'maniajeans.co.il','mania':'maniajeans.co.il',
3029  'keter':'keter.com','כתר':'keter.com','כתר ספרים':'keter.com',
3030  'yaakovi':'yaakovi.co.il','יעקבי':'yaakovi.co.il',
3031  // ── Israeli textiles & other ───────────────────────────────────────────────
3032  'arad textile':'aradtextile.com','ערד טקסטיל':'aradtextile.com','ערד':'aradtextile.com',
3033  'arad textile / ערד טקסטיל':'aradtextile.com','arad textile | ערד טקסטיל':'aradtextile.com',
3034  'arad textile ערד טקסטיל':'aradtextile.com',
3035  'shilav':'shilav.co.il','שילב':'shilav.co.il','שילב (shilav)':'shilav.co.il',
3036  'vardinon':'vardinon.co.il','ורדינון':'vardinon.co.il',
3037  'terminal':'terminalx.com','terminal x':'terminalx.com','terminal x | טרמינל איקס':'terminalx.com',
3038  // ── Israeli more ──────────────────────────────────────────────────────────
3039  'zer4u':'zer4u.co.il','זר פור יו':'zer4u.co.il','זר פור יו (zer4u)':'zer4u.co.il','zaga':'zaga.co.il','זגה':'zaga.co.il',
3040  'zaga זגה':'zaga.co.il','zer4u זר פור יו':'zer4u.co.il',
3041  'optikana':'optikana.co.il','אופטיקנה':'optikana.co.il',
3042  'urban':'urban.co.il','אורבן':'urban.co.il',
3043  'intima':'intima.co.il','אינטימה':'intima.co.il',
3044  'issta':'issta.co.il','איסתא':'issta.co.il',
3045  'alufirst':'alufirst.co.il','אלופירסט':'alufirst.co.il',
3046  'elm':'elm.co.il','אלם':'elm.co.il','אלמ':'elm.co.il',
3047  'eshet':'eshet-tours.co.il','אשת טורס':'eshet-tours.co.il',
3048  'beitely':'beitely.co.il','ביתילי':'beitely.co.il',
3049  'discount bank':'discount.co.il','בנק דיסקונט':'discount.co.il',
3050  'hakuk':'hakuk.co.il','בקבוק לתינוק':'hakuk.co.il',
3051  'heni':'heni.co.il','הני':'heni.co.il',
3052  'hashmalneto':'hashmalneto.co.il','חשמל נטו':'hashmalneto.co.il',
3053  '247':'twentyfourseven.co.il','טוונטי פור סבן':'twentyfourseven.co.il','twenty4seven':'twentyfourseven.co.il','twentyfourseven':'twentyfourseven.co.il',
3054  'pandazzz':'pandazzz.co.il','פנדה (pandazzz)':'pandazzz.co.il','pandazzz (פנדה)':'pandazzz.co.il',
3055  'shamhi':'shamhi.co.il','שמחי ובניו':'shamhi.co.il',
3056  'tami4':'tami4.co.il','תמי4':'tami4.co.il','תמי4 | tami4':'tami4.co.il',
3057  'lastprice':'lastprice.co.il','lastprice | לאסטפרייס':'lastprice.co.il',
3058  'house-design':'house-design.co.il','האוס דיזיין':'house-design.co.il',
3059  'house-design | האוס דיזיין':'house-design.co.il',
3060  'lovi':'lovi.co.il','lovi לאבי':'lovi.co.il',
3061  'steve madden israel':'stevemadden.co.il',
3062  'bmanuka':'bmanuka.co.il','bmanuka / בימנוקה':'bmanuka.co.il',
3063  'art depot':'artdepot.co.il','art depot / ארט דיפו':'artdepot.co.il',
3064  'traveling in israel':'traveling-in-israel.com',
3065  'tel aviv museum of art':'tamuseum.org.il',
3066  // ── International fashion ─────────────────────────────────────────────────
3067  'shein':'shein.com','שיין':'shein.com',
3068  'nike':'nike.com','נייק':'nike.com',
3069  'adidas':'adidas.com','אדידס':'adidas.com',
3070  'adidas israel online | אדידס ישראל אונליין':'adidas.co.il',
3071  'adidas israel online':'adidas.co.il',
3072  'puma':'puma.com','פומה':'puma.com',
3073  'under armour':'underarmour.com','אנדר ארמור':'underarmour.com',
3074  'american eagle':'ae.com',
3075  'banana republic':'bananarepublic.com',
3076  'old navy':'oldnavy.com',
3077  'gap':'gap.com',
3078  'express':'express.com',
3079  'pacsun':'pacsun.com',
3080  'lane bryant':'lanebryant.com',
3081  'maurices':'maurices.com',
3082  'urban outfitters':'urbanoutfitters.com',
3083  'fashion nova':'fashionnova.com',
3084  'asos':'asos.com',
3085  'browns fashion':'brownsfashion.com',
3086  'slamjam':'slamjam.com',
3087  'ganni':'ganni.com',
3088  'cettire':'cettire.com',
3089  'giglio':'giglio.com',
3090  'gilt':'gilt.com',
3091  'dolce gabbana':'dolcegabbana.com',
3092  'armani':'armani.com',
3093  'michael kors':'michaelkors.com',
3094  'jimmy choo':'jimmychoo.com','jimmychoo':'jimmychoo.com',
3095  'ugg':'ugg.com','ugg®':'ugg.com',
3096  'crocs':'crocs.com',
3097  'clarks':'clarks.com',
3098  'steve madden':'stevemadden.com',
3099  'charles tyrwhitt':'charlestyrwhitt.com',
3100  'tarte':'tartecosmetics.com',
3101  'quince':'onequince.com',
3102  'yellow the label':'yellowthelabel.com',
3103  'bylt basics':'byltbasics.com',
3104  'drop dead clothing':'dropdead.co.uk',
3105  'general pants co.':'generalpants.com.au',
3106  'rack room shoes':'rackroomshoes.com',
3107  'journeys':'journeys.com',
3108  'mam originals':'mam.com','mam':'mam.com','mam®':'mam.com',
3109  'zaful':'zaful.com','zaful - זאפול':'zaful.com',
3110  // ── International tech ─────────────────────────────────────────────────────
3111  'samsung':'samsung.com',
3112  'hp':'hp.com',
3113  'huion':'huion.com',
3114  'easeus software':'easeus.com',
3115  'hubbleconnected':'hubbleconnected.com',
3116  'red magic':'redmagic.gg','red magic eu store':'redmagic.gg','red magic france':'fr.redmagic.gg',
3117  'red magic germany':'de.redmagic.gg','red magic italy':'it.redmagic.gg','red magic spain':'es.redmagic.gg',
3118  'red magic global':'redmagic.gg',
3119  'trc electronics':'trcelectronics.com',
3120  'airthings':'airthings.com',
3121  'explore scientific':'explorescientific.com',
3122  'spelab auto parts':'spelab.com',
3123  'xpel':'xpel.com',
3124  'geekbuying':'geekbuying.com','geekbuying | גיקביינג':'geekbuying.com',
3125  'tomtop':'tomtop.com','tomtop | טום טופ':'tomtop.com',
3126  'banggood':'banggood.com','banggood | בנגוד':'banggood.com',
3127  'dhgate':'dhgate.com',
3128  'ridge':'ridge.com',
3129  'vitruvian':'vitruvianform.com',
3130  'sharkninja':'sharkninja.com',
3131  'thermoworks':'thermoworks.com',
3132  'topmediaforyou':'topmediaforyou.com','topmediaii':'topmediaii.com','topmediaii (topmediaii)':'topmediaii.com',
3133  'pngtree':'pngtree.com',
3134  // ── Travel & hotels ────────────────────────────────────────────────────────
3135  'airbnb':'airbnb.com',
3136  'booking.com':'booking.com',
3137  'hotels.com':'hotels.com',
3138  'expedia':'expedia.com','expedia us':'expedia.com',
3139  'agoda':'agoda.com',
3140  'priceline':'priceline.com',
3141  'orbitz':'orbitz.com',
3142  'onetravel':'onetravel.com',
3143  'lastminute':'lastminute.com',
3144  'viator':'viator.com',
3145  'ihg hotels & resorts':'ihg.com','ihg':'ihg.com',
3146  'marriott':'marriott.com',
3147  'millennium hotels':'millenniumhotels.com',
3148  'motel 6':'motel6.com',
3149  'great value vacations':'greatvaluevacations.com',
3150  'cheapcaribbean.com':'cheapcaribbean.com',
3151  'atlantis the palm':'atlantisthepalm.com',
3152  'air india':'airindia.com',
3153  'cleartrip':'cleartrip.com',
3154  'rentickle':'rentickle.com',
3155  // ── Beauty & health ────────────────────────────────────────────────────────
3156  'sephora':'sephora.com',
3157  'ulta':'ulta.com',
3158  'laseraway':'laseraway.com',
3159  'plexaderm':'plexaderm.com',
3160  'forest essentials':'forestessentialsindia.com','forestessentials':'forestessentialsindia.com',
3161  'nykaa fashion':'nykaafashion.com',
3162  'noom':'noom.com',
3163  'betterhelp':'betterhelp.com',
3164  'feelgood contacts':'feelgoodcontacts.com',
3165  'lauricidin':'lauricidin.com',
3166  'lumimeds':'lumimeds.com',
3167  'peptide tech':'peptidetech.com',
3168  'solar buddies':'solarbuddies.com',
3169  'memorypur':'memorypur.com',
3170  'palmonas':'palmonas.com',
3171  'crushed tonic':'crushedtonic.com',
3172  'leaf and flower':'leafandflower.com',
3173  'priv':'priv.com',
3174  'times prime':'timesprime.com',
3175  'patpat':'patpat.com',
3176  'rebound fitness':'reboundfitness.com',
3177  'youfit':'youfit.com',
3178  // ── Home & garden ─────────────────────────────────────────────────────────
3179  'pottery barn':'potterybarn.com',
3180  'wayfair':'wayfair.com',
3181  'the home depot':'homedepot.com',
3182  'lowes':'lowes.com',"lowe's":'lowes.com',
3183  'overstock':'overstock.com',
3184  'hobby lobby':'hobbylobby.com',
3185  'staples':'staples.com',
3186  'lights4fun':'lights4fun.co.uk',
3187  'myphotobook':'myphotobook.co.uk',
3188  'sproos':'sproos.com',
3189  'gardyn':'mygardyn.com',
3190  'donut media':'donut.media',
3191  // ── Food & grocery ─────────────────────────────────────────────────────────
3192  'doordash':'doordash.com',
3193  'instacart':'instacart.com',
3194  'sunbasket':'sunbasket.com',
3195  'vitacost':'vitacost.com',
3196  'iherb':'iherb.com',
3197  'petco':'petco.com',
3198  'costco':'costco.com',
3199  // ── Services & subscriptions ───────────────────────────────────────────────
3200  'amazon':'amazon.com',
3201  'ebay':'ebay.com',
3202  'walmart':'walmart.com',
3203  'target':'target.com','target.com':'target.com',
3204  'tjmaxx':'tjmaxx.com','t.j.maxx':'tjmaxx.com',
3205  'macys':'macys.com',"macy's":'macys.com',
3206  "bloomingdale's":'bloomingdales.com',
3207  'nordstrom rack':'nordstromrack.com',
3208  'kohls':'kohls.com',"kohl's":'kohls.com',
3209  'jcpenney':'jcpenney.com',
3210  'belk':'belk.com',
3211  'rei':'rei.com',
3212  "cabela's":'cabelas.com',
3213  "carter's":'carters.com',
3214  'temu':'temu.com','temu (temu)':'temu.com',
3215  'aliexpress':'aliexpress.com',
3216  'etsy':'etsy.com',
3217  'verizon':'verizon.com',
3218  'at&t':'att.com',
3219  'boost mobile':'boostmobile.com',
3220  'siriusxm':'siriusxm.com',
3221  'mint':'mint.com',
3222  'wall street journal':'wsj.com',
3223  'western union':'westernunion.com',
3224  'godaddy':'godaddy.com',
3225  'namecheap':'namecheap.com',
3226  'bluehost':'bluehost.com',
3227  'hostinger':'hostinger.com',
3228  'bigrock':'bigrock.in',
3229  'expressvpn':'expressvpn.com',
3230  'nordvpn':'nordvpn.com',
3231  'keeper security':'keepersecurity.com',
3232  'duolingo':'duolingo.com',
3233  'busuu':'busuu.com',
3234  'widilo (duolingo cashback)':'widilo.fr','widilo':'widilo.fr',
3235  'shutterfly':'shutterfly.com',
3236  'vistaprint':'vistaprint.com',
3237  'art.com':'art.com',
3238  'ftd':'ftd.com',
3239  'proflowers':'proflowers.com',
3240  '1-800-flowers':'1800flowers.com',
3241  '1-800 contacts':'1800contacts.com',
3242  'cvs':'cvs.com',
3243  'walgreens':'walgreens.com',
3244  'rent the runway':'renttherunway.com',
3245  'vivid seats':'vividseats.com',
3246  'pngtree':'pngtree.com',
3247  'gq box':'gqbox.com',
3248  'military.com':'military.com',
3249  'sabina store':'sabinastore.com','sabina':'sabinastore.com',
3250  'randonautica':'randonautica.com',
3251  'go ape':'goape.co.uk',
3252  'fancy homebody':'fancyhomebody.com',
3253  'ajio':'ajio.com',
3254  'advance auto parts':'advanceautoparts.com',
3255  'tactical gear junkie':'tacticalgearjunkie.com',
3256  'overtime':'overtime.tv',
3257  'djgate':'djgate.com','jd':'jd.com',
3258  'academy':'academy.com',
3259  'tom&co':'tomandco.be',
3260  'sihoo france':'sihoooffice.com',
3261  'gardyn':'mygardyn.com',
3262  'sproos':'sproos.com',
3263  'pandazzz':'pandazzz.co.il',
3264  'mam originals':'mam.com',
3265  'centre pompidou-metz':'centrepompidou-metz.fr',
3266  'forum des images':'forumdesimages.fr',
3267  'the metropolitan museum of art':'metmuseum.org',
3268  'retailmenot':'retailmenot.com',
3269  'kay jewelers':'kay.com',
3270  "d'aniello boutique":'danielloboutique.com',
3271  'luximara':'luximara.com',
3272  'bambini fashion':'bambinifashion.com',
3273  'bhphotovideo':'bhphotovideo.com',
3274  'sbla beauty':'sblabeauty.com',
3275  '7g':'7g.co.il',"7g 7 ג'י":'7g.co.il',
3276  'ksp | קיי אס פי':'ksp.co.il','ksp performance':'ksp.co.il','ksp performance | ksp':'ksp.co.il',
3277  'ksp performance ksp':'ksp.co.il',
3278  'adika / עדיקה':'adikastyle.com','adika | עדיקה':'adikastyle.com','adika עדיקה':'adikastyle.com',
3279  'ivory - אייבורי':'ivory.co.il','ivory.co.il - אייבורי':'ivory.co.il',
3280  'ivoryil':'ivory.co.il','ivory (אייבורי)':'ivory.co.il',
3281  'soltam | סולתם':'soltam.co.il','soltam סולתם':'soltam.co.il',
3282  'סולתם (soltam)':'soltam.co.il',
3283  'tollmans dot':"tollmans.co.il","tollman's dot":'tollmans.co.il',
3284  "tollman's dot טולמנ'ס דוט":'tollmans.co.il',
3285  'ace':'ace.co.il','אייס acr':'ace.co.il','אייס ace':'ace.co.il',
3286  'lostprice':'lastprice.co.il',
3287  // ── Remaining fixes ───────────────────────────────────────────────────────
3288  'jd':'jd.com',
3289  'אייס':'ace.co.il','ace':'ace.co.il',
3290  'אלמ':'elm.co.il',
3291  'בנק דיסקונט':'discount.co.il','discount bank':'discount.co.il',
3292  'בקבוק לתינוק':'hakuk.co.il',
3293  'הני':'heni.co.il',
3294  'טוונטי פור סבן':'twentyfourseven.co.il','twenty four seven':'twentyfourseven.co.il',
3295  'טולמנס דוט':'tollmans.co.il',"tollman's dot":'tollmans.co.il',"tollman's dot טולמנ'ס דוט":'tollmans.co.il',
3296  'ישרוטל':'isrotel.co.il','isrotel':'isrotel.co.il',
3297  'סוהו':'soho-fashion.co.il','soho fashion':'soho-fashion.co.il',
3298  'עברית':'ivrit.co.il','ivrit':'ivrit.co.il',
3299  'עולם הקולנוע':'olamhakolnoa.co.il',
3300  'כרמלה':'carmella.co.il','carmela':'carmella.co.il','carmella':'carmella.co.il',
3301  'צומת ספרים':'booknet.co.il','tzomet sfarim':'booknet.co.il','booknet':'booknet.co.il',
3302  'atterley':'atterleyroad.com','atterley road':'atterleyroad.com',
3303  'mandm direct':'mandmdirect.com','mandm':'mandmdirect.com','mand m direct':'mandmdirect.com',
3304}; // _STORE_DOMAINS_LEGACY_PLACEHOLDER end — NOT used at runtime
3305// ── Auto-merge server-side domain map (non-blocking, additive) ────────────────
3306// Fetches /api/store-domains and adds any stores not already in STORE_DOMAINS.
3307// Any store added to the DB is automatically recognized for favicons without
3308// changing this file.
3309fetch('/api/store-domains').then(r => r.json()).then(serverMap => {
3310  for (const [domain, name] of Object.entries(serverMap)) {
3311    const key = name.toLowerCase();
3312    if (!STORE_DOMAINS[key])  STORE_DOMAINS[key]  = domain;
3313    if (!STORE_DOMAINS[name]) STORE_DOMAINS[name] = domain;
3314  }
3315}).catch(() => {/* non-critical — STORE_DOMAINS already has all known stores */});
3316
3317// ── Globe fingerprint — detect Google's "no favicon" placeholder ──────────────
3318let _globeFingerprint = null;
3319function _loadGlobeFingerprint() {
3320  const img = new Image();
3321  img.crossOrigin = 'anonymous';
3322  img.onload = () => {
3323    try {
3324      const c = document.createElement('canvas');
3325      c.width = img.width; c.height = img.height;
3326      c.getContext('2d').drawImage(img, 0, 0);
3327      _globeFingerprint = c.getContext('2d').getImageData(0, 0, img.width, img.height).data;
3328    } catch(e) {}
3329  };
3330  img.src = 'https://www.google.com/s2/favicons?domain=thisdoesnotexist99999xyz.com&sz=64';
3331}
3332function _isGlobe(img) {
3333  if (!_globeFingerprint) return false;
3334  try {
3335    const c = document.createElement('canvas');
3336    c.width = img.naturalWidth; c.height = img.naturalHeight;
3337    c.getContext('2d').drawImage(img, 0, 0);
3338    const data = c.getContext('2d').getImageData(0, 0, img.naturalWidth, img.naturalHeight).data;
3339    if (data.length !== _globeFingerprint.length) return false;
3340    return data.every((v, i) => v === _globeFingerprint[i]);
3341  } catch(e) { return false; }
3342}
3343
3344function _storeInitial(store, size) {
3345  const words = (store || '').trim().split(/\s+/).filter(Boolean);
3346  let letters;
3347  if (words.length >= 2) {
3348    letters = words.slice(0, 2)
3349      .map(w => (w.replace(/[^a-zA-Zא-ת]/g, '')[0] || w[0] || '').toUpperCase())
3350      .join('');
3351  } else {
3352    const clean = ((words[0] || '').replace(/[^a-zA-Zא-ת]/g, '') || (store || '??'));
3353    letters = clean.slice(0, 2).toUpperCase();
3354  }
3355  const colors = ['#00e87a','#63b3ed','#ffe45e','#b794f4','#fc8181','#f6ad55','#68d391','#76e4f7'];
3356  let hash = 0;
3357  for (let i = 0; i < store.length; i++) hash = store.charCodeAt(i) + ((hash << 5) - hash);
3358  const color = colors[Math.abs(hash) % colors.length];
3359  // size param → fixed px square (modal); no size → 100% fill (cards)
3360  const dim = size ? `width:${size}px;height:${size}px;` : `width:100%;height:100%;`;
3361  return `<div style="${dim}display:flex;align-items:center;justify-content:center;background:${color}22;font-size:13px;font-weight:800;color:${color};font-family:'Syne',system-ui,sans-serif;line-height:1">${letters}</div>`;
3362}
3363
3364function _faviconErr(img, urls, store) {
3365  // urls is a pipe-separated string of remaining fallback URLs
3366  const list = (typeof urls === 'string' ? urls : '').split('|').filter(
3366Boolean);
3367  if (list.length === 0) {
3368    // Favicon chain exhausted — try stored logo before initials
3369    const storedLogo = STORE_LOGOS[(store || '').toLowerCase()];
3370    if (storedLogo) {
3371      img.onerror = () => { img.style.display = 'none'; img.insertAdjacentHTML('afterend', _storeInitial(store)); img.remove(); };
3372      img.src = storedLogo;
3373      return;
3374    }
3375    img.style.display = 'none';
3376    img.insertAdjacentHTML('afterend', _storeInitial(store));
3377    img.remove();
3378    return;
3379  }
3380  const next = list[0];
3381  const rest = list.slice(1).join('|');
3382  img.onload = null;
3383  img.onerror = function() { _faviconErr(img, rest, store); };
3384  img.src = next;
3385}
3386
3387function _faviconUrls(domain) {
3388  return [
3389    `/api/favicon?domain=${encodeURIComponent(domain)}`,
3390  ];
3391}
3392
3393function storeToFavicon(store, sourceUrl) {
3394  const key = (store || '').toLowerCase().trim();
3395  let domain = null;
3396
3397  // 1. Exact match in map
3398  if (STORE_DOMAINS[key]) domain = STORE_DOMAINS[key];
3399
3400  // 2. No partial match — exact only (partial causes "golf" to hit "golf kids")
3401
3402  // 3. Extract domain from coupon source URL (skip coupon aggregator domains)
3403  if (!domain && sourceUrl) {
3404    try {
3405      const raw = sourceUrl.replace(/^\[.*?\]\|/, '').replace(/^Web:\s*/, '').trim();
3406      const h = new URL(raw).hostname.replace(/^www\./, '');
3407      const skip = ['secretcoupon','israelicoupons','promocode','couponet','couponfollow',
3408                    'israelcoupon','couponwcode','bradsdeals','slickdeals','retailmenot'];
3409      if (h && !skip.some(s => h.includes(s))) domain = h;
3410    } catch(e) {}
3411  }
3412
3413  // 4. No domain known — return null (do not guess)
3414  return domain ? _faviconUrls(domain) : null;
3415}
3416
3417// Returns the target store homepage URL from the store name only.
3418// Source URL is intentionally ignored — it's where we found the deal, not where to buy.
3419function _storeHomeUrl(store) {
3420  const key = (store || '').toLowerCase().trim();
3421
3422  // 1. Exact match in STORE_DOMAINS
3423  if (STORE_DOMAINS[key]) {
3424    const d = STORE_DOMAINS[key];
3425    return `https://${d.startsWith('www.') ? '' : 'www.'}${d}/`;
3426  }
3427
3428  // No partial match — "golf" must not resolve via "golf kids" or any other store
3429  return ''; // no verified URL in DB for this store — do not guess
3430}
3431
3432function formatDiscount(raw) {
3433  if (!raw) return 'הנחה';
3434  const d = raw.trim();
3435  // Already has a Hebrew/English discount descriptor — show as-is
3436  if (/הנחה|חינם|shipping|discount|off\b/i.test(d)) return d;
3437  // Percentage or shekel amount → append הנחה
3438  if (d.includes('%') || d.includes('₪')) return d + ' הנחה';
3439  return d;
3440}
3441
3442function parseDiscount(discount) {
3443  if (!discount) return 0;
3444  const s = discount.toString();
3445  const pct = s.match(/(\d+(?:\.\d+)?)\s*%/);
3446  if (pct) return parseFloat(pct[1]);
3447  const shek = s.match(/₪\s*(\d+)|(\d+)\s*₪/);
3448  if (shek) return parseFloat(shek[1] || shek[2]) * 0.15;
3449  const heShek = s.match(/(\d+)\s*(?:שקל|שח|ש"ח)/);
3450  if (heShek) return parseFloat(heShek[1]) * 0.15;
3451  return 0;
3452}
3453
3454function getVoteState(id) { return localStorage.getItem(`vote_${id}`) || ''; }
3455
3456// ── Device ID — one random ID per browser, persists forever ─────────────────
3457function _getDeviceId() {
3458  let id = localStorage.getItem('_couponai_device_id');
3459  if (!id) {
3460    id = 'dev_' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
3461    localStorage.setItem('_couponai_device_id', id);
3462  }
3463  return id;
3464}
3465
3466// ── Browser fingerprint collector ────────────────────────────────────────────
3467function _collectEnv() {
3468  const nav = navigator;
3469  const conn = nav.connection || nav.mozConnection || nav.webkitConnection || {};
3470  return {
3471    lang:     nav.language || nav.userLanguage || '',
3472    langs:    (nav.languages || []).join(','),
3473    tz:       Intl.DateTimeFormat().resolvedOptions().timeZone || '',
3474    mem:      nav.deviceMemory || null,
3475    cpu:      nav.hardwareConcurrency || null,
3476    conn:     conn.effectiveType || conn.type || null,
3477    rtt:      conn.rtt || null,
3478    platform: nav.platform || '',
3479    vendor:   nav.vendor || '',
3480    cookies:  nav.cookieEnabled,
3481    online:   nav.onLine,
3482    pwa:      window.matchMedia('(display-mode: standalone)').matches,
3483    dnt:      nav.doNotTrack === '1',
3484  };
3485}
3486
3487function _collectScreen() {
3488  return {
3489    w:     screen.width,
3490    h:     screen.height,
3491    vw:    window.innerWidth,
3492    vh:    window.innerHeight,
3493    dpr:   window.devicePixelRatio || 1,
3494    touch: navigator.maxTouchPoints || 0,
3495    depth: screen.colorDepth || null,
3496    orient: (screen.orientation || {}).type || null,
3497  };
3498}
3499
3500function _collectUTM() {
3501  const p = new URLSearchParams(location.search);
3502  const utm = {};
3503  for (const k of ['source','medium','campaign','content','term']) {
3504    const v = p.get('utm_' + k);
3505    if (v) utm[k] = v;
3506  }
3507  // absorb click IDs
3508  for (const [key, field] of [['fbclid','fb_click'],['gclid','g_click'],['ttclid','tt_click'],['msclkid','ms_click']]) {
3509    const v = p.get(key);
3510    if (v) utm[field] = v;
3511  }
3512  return Object.keys(utm).length ? utm : null;
3513}
3514
3515async function _canvasFingerprint() {
3516  try {
3517    const c = document.createElement('canvas');
3518    c.width = 200; c.height = 50;
3519    const ctx = c.getContext('2d');
3520    ctx.textBaseline = 'top';
3521    ctx.font = '14px Arial';
3522    ctx.fillStyle = '#f60';
3523    ctx.fillRect(125, 1, 62, 20);
3524    ctx.fillStyle = '#069';
3525    ctx.fillText('CouponAI🛒', 2, 15);
3526    ctx.fillStyle = 'rgba(102,204,0,0.7)';
3527    ctx.fillText('CouponAI🛒', 4, 17);
3528    const data = c.toDataURL();
3529    let hash = 0;
3530    for (let i = 0; i < data.length; i++) hash = ((hash << 5) - hash) + data.charCodeAt(i) | 0;
3531    return hash.toString(36);
3532  } catch(e) { return null; }
3533}
3534
3535async function _webglFingerprint() {
3536  try {
3537    const c = document.createElement('canvas');
3538    const gl = c.getContext('webgl') || c.getContext('experimental-webgl');
3539    if (!gl) return null;
3540    const dbg = gl.getExtension('WEBGL_debug_renderer_info');
3541    return {
3542      renderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
3543      vendor:   dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL)   : null,
3544    };
3545  } catch(e) { return null; }
3546}
3547
3548let _fpCache = null;
3549async function _collectFingerprint() {
3550  if (_fpCache) return _fpCache;
3551  const [canvas, webgl] = await Promise.all([_canvasFingerprint(), _webglFingerprint()]);
3552  _fpCache = { canvas, webgl };
3553  return _fpCache;
3554}
3555
3556// Build the full device context payload, cached after first call
3557let _deviceContextCache = null;
3558async function _getDeviceContext() {
3559  if (_deviceContextCache) return _deviceContextCache;
3560  const [fp] = await Promise.all([_collectFingerprint()]);
3561  // Visit counter
3562  const visits = parseInt(localStorage.getItem('_cai_visits') || '0') + 1;
3563  localStorage.setItem('_cai_visits', visits);
3564  const firstVisit = localStorage.getItem('_cai_first_visit') || Date.now().toString();
3565  localStorage.setItem('_cai_first_visit', firstVisit);
3566
3567  _deviceContextCache = {
3568    ua:          navigator.userAgent,
3569    ref:         document.referrer || null,
3570    utm:         _collectUTM(),
3571    url:         location.href,
3572    env:         _collectEnv(),
3573    screen:      _collectScreen(),
3574    fp,
3575    visit_num:   visits,
3576    first_visit: parseInt(firstVisit),
3577  };
3578  return _deviceContextCache;
3579}
3580
3581// ── Client-side analytics (localStorage) ────────────────────────────────────
3582const AK = 'couponai_analytics';
3583function _aGet() { try { return JSON.parse(localStorage.getItem(AK)||'{}'); } catch(e) { return {}; } }
3584function _aSet(d) { try { localStorage.setItem(AK, JSON.stringify(d)); } catch(e) {} }
3585function _currentUsername() {
3586  return sessionStorage.getItem('couponai_admin_user') || '';
3587}
3588function _tagWithUser(data) {
3589  // Attach _u (username) to event data when an admin/operator is logged in
3590  const u = _currentUsername();
3591  if (!u) return data || '';
3592  if (data && typeof data === 'object') return { ...data, _u: u };
3593  return { _v: data || '', _u: u };
3594}
3595function trackEvent(type, data) {
3596  const d = _aGet();
3597  if (!d.events) d.events = [];
3598  d.events.push({ type, data: _tagWithUser(data), ts: Date.now() });
3599  if (d.events.length > 5000) d.events = d.events.slice(-5000);
3600  _aSet(d);
3601}
3602function trackSearch(q) {
3603  const d = _aGet();
3604  if (!d.searches) d.searches = [];
3605  d.searches.push({ q, ts: Date.now(), u: _currentUsername() || undefined });
3606  if (d.searches.length > 2000) d.searches = d.searches.slice(-2000);
3607  _aSet(d);
3608}
3609// ── Server-side analytics persistence ───────────────────────────────────────
3610async function _syncAnalyticsToServer(beacon) {
3611  const d = _aGet();
3612  if (!d.events && !d.searches && !d.sessions) return;
3613  const ctx = await _getDeviceContext().catch(() => ({}));
3614  const payload = { ...d, did: _getDeviceId(), ctx };
3615  try {
3616    if (beacon && navigator.sendBeacon) {
3617      // sendBeacon is fire-and-forget, safe on tab close
3618      navigator.sendBeacon('/api/analytics', JSON.stringify(payload));
3619    } else {
3620      await fetch('/api/analytics', {
3621        method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload)
3622      });
3623      // Server just acks the insert now — it no longer echoes the full dataset back.
3624      // Local state (events/searches/sessions) is untouched; admin analytics views
3625      // pull the merged cross-device dataset on demand via _loadAnalyticsFromServer().
3626    }
3627  } catch(e) {}
3628}
3629
3630async function _loadAnalyticsFromServer() {
3631  try {
3632    const resp = await fetch('/api/analytics');
3633    if (!resp.ok) return;
3634    const server = await resp.json();
3635    if (!server || (!server.events && !server.searches)) return;
3636    // Merge server data into localStorage
3637    const local = _aGet();
3638    const merged = {};
3639    for (const key of ['events','searches','sessions']) {
3640      const s = server[key] || [];
3641      const l = local[key] || [];
3642      const seen = {};
3643      for (const item of [...l, ...s]) {
3644        // Sessions use {start, end, deviceId} — no ts/type/q, so key by start time
3645        const dk = key === 'sessions'
3646          ? `s-${item.start || item.ts || Math.random()}`
3647          : `${item.ts||''}-${item.type||''}-${item.q||''}`;
3648        seen[dk] = item;
3649      }
3650      merged[key] = Object.values(seen).sort((a,b) =>
3650 ((a.ts||a.start||0)-(b.ts||b.start||0)));
3651    }
3652    _aSet(merged);
3653  } catch(e) {}
3654}
3655
3656// ── Coupon event sender ───────────────────────────────────────────────────────
3657async function _sendCouponEvent(type, store, code, couponId) {
3658  try {
3659    const ctx = await _getDeviceContext().catch(() => ({}));
3660    fetch('/api/coupon-event', {
3661      method: 'POST',
3662      headers: {'Content-Type':'application/json'},
3663      body: JSON.stringify({ type, store: store||'', code: code||'', coupon_id: couponId||null,
3664                             did: _getDeviceId(), ua: ctx.ua, ref: ctx.ref }),
3665      keepalive: true,
3666    });
3667  } catch(e) {}
3668}
3669
3670// ── Search event sender ───────────────────────────────────────────────────────
3671async function _sendSearchEvent(q, results, elapsed, mode) {
3672  try {
3673    const ctx = await _getDeviceContext().catch(() => ({}));
3674    fetch('/api/search-event', {
3675      method: 'POST',
3676      headers: {'Content-Type':'application/json'},
3677      body: JSON.stringify({ q, results, elapsed, mode: mode||'fast',
3678                             did: _getDeviceId(), ua: ctx.ua, ref: ctx.ref, utm: ctx.utm }),
3679      keepalive: true,
3680    });
3681  } catch(e) {}
3682}
3683
3684// ── JS error tracker ─────────────────────────────────────────────────────────
3685(function() {
3686  function _sendError(msg, src, line, col, stack) {
3687    try {
3688      fetch('/api/js-error', {
3689        method: 'POST',
3690        headers: {'Content-Type':'application/json'},
3691        body: JSON.stringify({ message: msg, source: src, lineno: line, colno: col,
3692                               stack: stack||null, ua: navigator.userAgent,
3693                               url: location.href, did: _getDeviceId() }),
3694        keepalive: true,
3695      });
3696    } catch(e) {}
3697  }
3698  window.addEventListener('error', e => {
3699    _sendError(e.message, e.filename, e.lineno, e.colno, e.error && e.error.stack);
3700  });
3701  window.addEventListener('unhandledrejection', e => {
3702    const msg = (e.reason && e.reason.message) ? e.reason.message : 
3702String(e.reason);
3703    const stack = (e.reason && e.reason.stack) || null;
3704    _sendError('UnhandledRejection: ' + msg, location.href, null, null, stack);
3705  });
3706})();
3707
3708// ── Record session start ──────────────────────────────────────────────────────
3709(function() {
3710  // Load server analytics then render dashboard panels — admin-only data, so only
3711  // fetch it for authed admin sessions. Public visitors never see these panels, and
3712  // admin views already re-fetch on demand (showPage('admin'), switchAdminView()).
3713  if (_isAdminAuthed()) {
3714    _loadAnalyticsFromServer().then(() => {
3715      try { _renderPopSearchBar(); } catch(e) {}
3716      try { _renderSlowBar(); } catch(e) {}
3717      try { loadAnalytics(); } catch(e) {}
3718      try { loadSpeedAnalytics(); } catch(e) {}
3719      try { loadPopupAnalytics(); } catch(e) {}
3720    });
3721  }
3722
3723  const d = _aGet();
3724  if (!d.sessions) d.sessions = [];
3725  d.sessions.push({ start: Date.now(), end: null, deviceId: _getDeviceId() });
3726  if (d.sessions.length > 2000) d.sessions = d.sessions.slice(-2000);
3727  _aSet(d);
3728
3729  // Save to server on tab close (beacon = no wait for response)
3730  window.addEventListener('beforeunload', () => {
3731    const d2 = _aGet();
3732    if (d2.sessions && d2.sessions.length) { d2.sessions[d2.sessions.length-1].end = Date.now(); _aSet(d2); }
3733    _syncAnalyticsToServer(true);
3734  });
3735
3736  // Auto-save every 5 minutes
3737  setInterval(() => _syncAnalyticsToServer(false), 5 * 60 * 1000);
3738
3739  // Save after every search (called from aiSearch)
3740  window._analyticsSearchDone = () => _syncAnalyticsToServer(false);
3741})();
3742
3743function cardAge(createdAt) {
3744  if (!createdAt) return { label: '', isOld: false };
3745  const days = Math.floor((Date.now() - new Date(createdAt)) / 86400000);
3746  if (days < 1)   return { label: 'היום',           isOld: false };
3747  if (days < 7)   return { label: `לפני ${days} ימים`, isOld: false };
3748  if (days < 30)  return { label: `לפני ${Math.floor(days/7)} שבועות`, isOld: false };
3749  if (days < 60)  return { label: `לפני חודש`,      isOld: false };
3750  return           { label: `לפני ${Math.floor(days/30)} חודשים`, isOld: true };
3751}
3752
3753function couponCard(c) {
3754  const id = c.id || '';
3755  const faviconUrls = storeToFavicon(c.store, c.source) || [];
3756  const favicon = faviconUrls[0] || null;
3757  // Encode as pipe-separated string — avoids double-quote conflicts in HTML attributes
3758  const faviconFallbacksStr = faviconUrls.slice(1).join('|');
3759  const discount = formatDiscount(c.discount);
3760  // Detect newsletter / first-purchase deals
3761  const _notesLower = (c.notes || '').toLowerCase();
3762  const isNewsletter = _notesLower.includes('ניוזלטר') || _notesLower.includes('רכישה ראשונה') || _notesLower.includes('newsletter') || _notesLower.includes('first order') || _notesLower.includes('first purchase');
3763  const newsletterBadge = isNewsletter ? `<div class="cc-newsletter-badge">✉️ לקוחות חדשים בלבד — רישום לניוזלטר</div>` : '';
3764  // Strip the standardized prefix from notes since the badge already shows it
3765  const displayNotes = isNewsletter
3766    ? (c.notes || '').replace(/הנחה לרכישה ראשונה\s*[—–-]\s*דורש רישום לניוזלטר\s*/i, '').trim()
3767    : (c.notes || '');
3768  const notesHtml = displayNotes ? `<div class="cc-notes">${displayNotes}</div>` : '';
3769  const voteState = id ? getVoteState(id) : '';
3770  const upDisabled   = voteState === 'up'   ? 'disabled class="vote-btn vote-up voted"'   : 'class="vote-btn vote-up"';
3771  const downDisabled = voteState === 'down' ? 'disabled class="vote-btn vote-down voted"' : 'class="vote-btn vote-down"';
3772  const safeCode  = (c.code  || '').replace(/'/g, "\\'");
3773  const safeStore = (c.store || '').replace(/'/g, "\\'");
3774  // Strip discovery-type prefix "[primary]|" / "[secondary]|" / "[auto]|" then "Web: "
3775  const safeSrc   = (c.src || '').replace(/^\[(?:primary|secondary|auto)\]\|/,'').replace(/^Web:\s*/i,'').trim().replace(/'/g, "\\'");
3776  // Real store homepage — always points to the store, never the aggregator source
3777  const _knownHome = _storeHomeUrl(c.store).replace(/'/g, "\\'");
3778  const storeHome = _knownHome || '';
3779  // Aggregator source URL — used as fallback link on the שגיאה button
3780  const _aggUrl = (_admCleanSrc(c.src || '').url || '').replace(/'/g, "\\'");
3781  const expired  = isExpired(c);
3782  const age = cardAge(c.createdAt);
3783  // Published date from source site — shown as secondary line below the age badge
3784  const pubLabel = c.publishedAt
3785    ? (() => {
3786        const d = new Date(c.publishedAt);
3787        if (isNaN(d)) return '';
3788        const fmt = d.toLocaleDateString('he-IL', {day:'2-digit', month:'2-digit', year:'2-digit'});
3789        return d > new Date() ? `בתוקף עד ${fmt}` : `פורסם ${fmt}`;
3790      })()
3791    : '';
3792  // On expired cards: show age badge in normal gray (not orange) — expired badge already signals it
3793  const ageBadge = (age.label || pubLabel)
3794    ? `<div class="cc-age">${age.label}${age.label && pubLabel ? '<br>' : ''}${pubLabel ? `<span style="opacity:0.7">${pubLabel}</span>` : ''}</div>`
3795    : '';
3796  const expClass  = expired ? 'exp-strike' : '';
3797  const expPill   = expired ? `<span class="exp-badge-orange" style="margin-right:auto;font-size:10px;padding:2px 8px">⏰ ייתכן שפג תוקף</span>` : '';
3798  return `
3799    <div class="coupon-card ${(c.votes || 0) > 10 ? 'featured' : ''} ${expClass}" data-id="${id}" data-created="${c.createdAt||''}" data-pub="${c.publishedAt||''}" data-pub-label="${pubLabel||''}" data-age-label="${age.label||''}" data-notes="${(displayNotes||'').replace(/"/g,'&quot;')}" data-discount-raw="${(c.discount||'').replace(/"/g,'&quot;')}" data-source="${(c.source||'').replace(/"/g,'&quot;')}" data-votes="${c.votes||0}" data-newsletter="${isNewsletter?'1':''}">
3800      ${ageBadge}
3801      <a class="cc-store-row" href="/store/${_storeSlug(c.store)}" style="text-decoration:none;color:inherit">
3802        <div class="cc-logo">
3803          ${favicon
3804            ? `<img src="${favicon}" alt="${safeStore}" onerror="_faviconErr(this,'${faviconFallbacksStr}','${safeStore}')"/>`
3805            : _storeInitial(c.store)}
3806        </div>
3807        <div class="cc-store">${_storeDisplayCard(c.store)}</div>
3808      </a>
3809      ${newsletterBadge}
3810      ${notesHtml}
3811      <div class="cc-discount">${discount}</div>
3812      ${(c.code || '').toUpperCase() === 'NO_CODE'
3813        ? storeHome
3814          ? `<a class="no-code-btn" href="/go?store=${encodeURIComponent(c.store)}&url=${encodeURIComponent(storeHome)}" target="_blank" rel="noopener noreferrer" onclick="trackEvent('show_site','${c.id||''}')" title="הצג מבצע באתר">🛍️ הצג מבצע באתר</a>`
3815          : ``
3816        : `<div class="cc-code-row">
3817        <button class="cc-copy" id="ccp-${id}" onclick="ccReveal(this.closest('.cc-code-row').querySelector('.cc-code'),'${safeCo
3817de}','${safeStore}','${safeSrc}','${id}','${storeHome}')" title="העתק קוד">${COPY_ICON}</button>
3818        <div class="cc-code" onclick="ccReveal(this,'${safeCode}','${safeStore}','${safeSrc}','${id}','${storeHome}')" style="cursor:pointer">
3819          <div class="cc-code-masked">
3820            <span class="cc-code-peek">
3821              <span class="cc-code-dots">••••••</span>
3822              <span class="cc-code-end">${(c.code||'').slice(-2)}</span>
3823            </span>
3824            <span class="cc-code-eye"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
3825          </div>
3826          <div class="cc-code-revealed" id="ccr-${id}">${c.code}</div>
3827        </div>
3828      </div>`
3829      }
3830      ${expPill ? `<div class="cc-footer">${expPill}</div>` : ''}
3831    </div>`;
3832}
3833
3834function isExpired(c) { return (c.votes || 0) <= -10; }
3835
3836async function vote(id, dir, btn) {
3837  if (!id || btn.disabled) return;
3838  const key = `vote_${id}`;
3839  const existing = getVoteState(id);
3840  // Same direction — ignore
3841  if ((existing === 'up' && dir > 0) || (existing === 'down' && dir < 0)) return;
3842
3843  const endpoint = dir > 0 ? `/api/coupons/${id}/vote` : `/api/coupons/${id}/downvote`;
3844  const res = await fetch(endpoint, { method: 'POST' }).catch(() => null);
3845  if (!res || !res.ok) return;
3846
3847  localStorage.setItem(key, dir > 0 ? 'up' : 'down');
3848
3849  const card   = btn.closest('.coupon-card');
3850  const upBtn   = card.querySelector('.vote-up');
3851  const downBtn = card.querySelector('.vote-down');
3852  const upSpan   = upBtn   ? upBtn.querySelector('span')   : null;
3853  const downSpan = downBtn ? downBtn.querySelector('span') : null;
3854
3855  if (dir > 0) {
3856    // ── Clicked 👍 ──────────────────────────────────
3857    trackEvent('thumb_up', id);
3858    // If switching from a downvote, cancel it on the red counter too
3859    if (existing === 'down' && downSpan)
3860      downSpan.textContent = Math.max(0, parseInt(downSpan.textContent || '0') - 1);
3861    // Add to green counter
3862    if (upSpan) upSpan.textContent = parseInt(upSpan.textContent || '0') + 1;
3863    // Lock green, unlock red
3864    upBtn.disabled = true;    upBtn.classList.add('voted');
3865    downBtn.disabled = false; downBtn.classList.remove('voted');
3866  } else {
3867    // ── Clicked 👎 ──────────────────────────────────
3868    trackEvent('thumb_down', id);
3869    // If switching from an upvote, cancel it on the green counter too
3870    if (existing === 'up' && upSpan)
3871      upSpan.textContent = Math.max(0, parseInt(upSpan.textContent || '0') - 1);
3872    // Add to red counter
3873    if (downSpan) downSpan.textContent = parseInt(downSpan.textContent || '0') + 1;
3874    // Lock red, unlock green
3875    downBtn.disabled = true;  downBtn.classList.add('voted');
3876    upBtn.disabled = false;   upBtn.classList.remove('voted');
3877  }
3878}
3879
3880function showSections(searchMode) {
3881  inSearch = searchMode;
3882  // Sections always stay visible — search results appear above them
3883}
3884
3885function clearSearch() {
3886  document.getElementById('pub-search').value = '';
3887  pubSearch = '';
3888  const banner = document.getElementById('ai-banner');
3889  if (banner) { banner.className = 'ai-banner'; banner.innerHTML = ''; banner.style.borderRadius = ''; }
3890  _stopMiniGame();
3891  const rw = document.getElementById('search-results-wrap');
3892  if (rw) rw.style.display = 'none';
3893  const rg = document.getElementById('search-results-grid');
3894  if (rg) rg.innerHTML = '';
3895  showSections(false); // restore main grids
3896}
3897
3898
3899// ── Userbot toggle ───────────────────────────────────────────────────────────
3900let _userbotEnabled = null;
3901
3902async function loadUserbotStatus() {
3903  try {
3904    const r = await fetch('/api/admin/userbot/status');
3905    if (!r.ok) return;
3906    const d = await r.json();
3907    _userbotEnabled = d.enabled;
3908    _applyUserbotUI(d);
3909  } catch(e) {}
3910}
3911
3912function _applyUserbotUI(d) {
3913  const enabled  = d.enabled;
3914  const status   = d.status || 'unknown';
3915  const stats    = d.stats  || {};
3916  const dot      = document.getElementById('userbot-dot');
3917  const track    = document.getElementById('userbot-track');
3918  const thumb    = document.getElementById('userbot-thumb');
3919  const label    = document.getElementById('userbot-toggle-label');
3920  const subtitle = document.getElementById('userbot-subtitle');
3921  const statsEl  = document.getElementById('userbot-stats');
3922  const chk      = document.getElementById('userbot-toggle');
3923  if (!dot) return;
3924
3925  // Dot color
3926  if (enabled && status === 'running') {
3927    dot.style.background = '#00e87a';
3928    dot.style.boxShadow  = '0 0 6px #00e87a';
3929  } else if (status === 'paused') {
3930    dot.style.background = '#f59e0b';
3931    dot.style.boxShadow  = '0 0 6px #f59e0b';
3932  } else {
3933    dot.style.background = '#ef4444';
3934    dot.style.boxShadow  = 'none';
3935  }
3936
3937  // Toggle switch
3938  chk.checked = enabled;
3939  track.style.background = enabled ? 'var(--accent)' : 'var(--gray4)';
3940  thumb.style.transform  = enabled ? 'translateX(-20px)' : 'translateX(0)';
3941  label.textContent = enabled ? 'פעיל' : 'כבוי';
3942  label.style.color = enabled ? 'var(--accent)' : 'var(--gray5)';
3943
3944  // Subtitle
3945  const statusMap = { running: '🟢 מאזין להודעות', paused: '🟡 מושהה', unknown: enabled ? '🟢 מחובר' : '⚪ לא פעיל' };
3946  subtitle.textContent = statusMap[status] || status;
3947
3948  // Stats
3949  if (stats.coupons_saved !== undefined) {
3950    const lastMsg = stats.last_message_at
3951      ? new Date(stats.last_message_at * 1000).toLocaleTimeString('he-IL')
3952      : '—';
3953    const nowSec = Math.floor(Date.now() / 1000);
3954    const heartbeat = stats.heartbeat_at;
3955    const aliveAgo = heartbeat ? (nowSec - heartbeat) : null;
3956    const aliveStr = aliveAgo === null
3957      ? '<span style="color:var(--gray5)">אין מידע</span>'
3958      : aliveAgo < 90
3959        ? `<span style="color:var(--accent)">
3959חי (לפני ${aliveAgo}ש׳)</span>`
3960        : aliveAgo < 300
3961          ? `<span style="color:#ffe45e">לפני ${Math.round(aliveAgo/60)} דק׳</span>`
3962          : `<span style="color:#ff6b6b">⚠️ לא מדווח ${Math.round(aliveAgo/60)} דק׳</span>`;
3963    statsEl.innerHTML = `${stats.coupons_saved} קופונים נשמרו • הודעה אחרונה: ${lastMsg} • תהליך: ${aliveStr}`;
3964  } else {
3965    statsEl.textContent = '';
3966  }
3967}
3968
3969async function toggleUserbot() {
3970  const newVal = !_userbotEnabled;
3971  _userbotEnabled = newVal;
3972  // Optimistic UI update
3973  _applyUserbotUI({ enabled: newVal, status: newVal ? 'running' : 'paused', stats: {} });
3974  try {
3975    const r = await fetch('/api/admin/userbot/toggle', {
3976      method: 'POST',
3977      headers: { 'Content-Type': 'application/json' },
3978      body: JSON.stringify({ enabled: newVal }),
3979    });
3980    if (!r.ok) throw new Error();
3981    // Reload actual status after 1s
3982    setTimeout(loadUserbotStatus, 1000);
3983  } catch(e) {
3984    // Revert on error
3985    _userbotEnabled = !newVal;
3986    loadUserbotStatus();
3987  }
3988}
3989
3990// null = not loaded yet (switch stays dimmed/disabled) — never guess a value before the server confirms one.
3991let _jobBEnabled = null;
3992let _jobBToggleBusy = false;
3993
3994function _applyJobBToggleUI(enabled) {
3995  _jobBEnabled = enabled;
3996  const wrap  = document.getElementById('job-b-toggle-wrap');
3997  const track = document.getElementById('job-b-toggle-track');
3998  const thumb = document.getElementById('job-b-toggle-thumb');
3999  const label = document.getElementById('job-b-toggle-label');
4000  const chk   = document.getElementById('job-b-toggle');
4001  if (!track) return;
4002  // Known value (true/false) → enable the switch. null → keep it dimmed/unclickable.
4003  wrap.style.opacity = enabled === null ? '0.4' : (_jobBToggleBusy ? '0.6' : '1');
4004  wrap.style.pointerEvents = (enabled === null || _jobBToggleBusy) ? 'none' : 'auto';
4005  if (enabled === null) { label.textContent = 'טוען…'; return; }
4006  chk.checked = enabled;
4007  track.style.background = enabled ? 'var(--accent)' : 'var(--gray4)';
4008  thumb.style.transform  = enabled ? 'translateX(-20px)' : 'translateX(0)';
4009  label.textContent = enabled ? 'פעיל' : 'כבוי';
4010  label.style.color = enabled ? 'var(--accent)' : 'var(--gray5)';
4011}
4012
4013async function toggleJobB() {
4014  if (_jobBToggleBusy || _jobBEnabled === null) return; // ignore clicks until state is known / prior click settled
4015  const newVal = !_jobBEnabled;
4016  _jobBToggleBusy = true;
4017  _applyJobBToggleUI(newVal); // optimistic, but now locked against double-clicks
4018  try {
4019    const r = await fetch('/api/admin/job-b/toggle', {
4020      method: 'POST',
4021      headers: { 'Content-Type': 'application/json' },
4022      body: JSON.stringify({ enabled: newVal }),
4023    });
4024    if (!r.ok) throw new Error();
4025    const confirmed = await r.json(); // trust the server's echoed value, not our guess
4026    _jobBToggleBusy = false;
4027    _applyJobBToggleUI(!!confirmed.enabled);
4028    showToast(confirmed.enabled ? '🟢 Job B הופעל — ירוץ הלילה ב-02:00' : '🔴 Job B הושבת — לא ירוץ הלילה');
4029  } catch(e) {
4030    _jobBToggleBusy = false;
4031    _applyJobBToggleUI(!newVal); // revert to prior known state on error
4032    showToast('❌ שגיאה בשינוי סטטוס Job B — נסה שוב');
4033  }
4034}
4035
4036// ── Admin table state ────────────────────────────────────────────────────────
4037const ADM = { filter:'', sort:'newest', cat:'', disc:'', geo:'all', page:0, perPage:20 };
4038const WEB = { page: 0, perPage: 20 };
4039const LOG = { page: 0, perPage: 50 };
4040const NL  = { page: 0, perPage: 20 };
4041const CAT_HE = {Fashion:'אופנה',Tech:'טכנולוגיה',Food:'אוכל',Travel:'נסיעות',
4042                Vacation:'חופשה',Home:'בית',Beauty:'יופי',Health:'בריאות',
4043                Kids:'ילדים',Sports:'ספורט',Other:'אחר',
4044                Leisure:'פנאי',Finance:'פיננסי'};
4045
4046const DTYPE_LABEL = {
4047  primary:   { text: 'חיפוש משתמש', bg: 'rgba(0,232,122,0.15)',  color: 'var(--accent)',  icon: '🔍' },
4048  secondary: { text: 'חיפוש משני',  bg: 'rgba(255,228,94,0.15)', color: 'var(--accent2)', icon: '🔄' },
4049  auto:      { text: 'אוטומטי',     bg: 'rgba(100,100,100,0.2)', color: 'var(--gray6)',   icon: '🤖' },
4050  scan:      { text: 'חיפוש ממוקד', bg: 'rgba(94,180,255,0.15)', color: '#5eb4ff',        icon: '🔎' },
4051};
4052
4053function _admCleanSrc(src) {
4054  if (!src) return { url: '', type: '' };
4055  const m = src.match(/^\[(primary|secondary|auto)\]\|/);
4056  let type = m ? m[1] : '';
4057  const url = src
4058    .replace(/^\[(?:primary|secondary|auto)\]\|/, '')
4059    .replace(/^Web:\s*/i, '').trim();
4060  // Web-scraped coupons (no [tag] prefix, but has a URL) → 'scan'
4061  if (!type && (src.match(/^\/?\s*[Ww]eb:\s*https?:\/\//) || (!m && url.startsWith('http')))) {
4062    type = 'scan';
4063  }
4064  return { url, type };
4065}
4066
4067// Hebrew ↔ English store name aliases for search matching
4068const STORE_ALIASES = {
4069  'קסטרו':['castro','castro.com','castro fashion'],
4070  'castro':['קסטרו'],'castro fashion':['קסטרו'],
4071  'ישראכרט':['isracard','israelicard'],'isracard':['ישראכרט'],'israelicard':['ישראכרט'],
4072  'זארה':['zara'],'zara':['זארה'],
4073  'אייץ׳ אנד אם':['h&m','hm'],'h&m':['אייץ׳ אנד אם'],'hm':['אייץ׳ אנד אם'],
4074  'נייקי':['nike'],'nike':['נייקי'],
4075  'אדידס':['adidas'],'adidas':['אדידס'],
4076  'שין':['shein'],'שיין':['shein'],'shein':['שין','שיין'],
4077  'טרמינל':['terminalx','terminal x'],'טרמינל איקס':['terminalx','terminal x'],'terminalx':['טרמינל','טרמינל איקס'],
4078  'באג':['bug'],'bug':['באג'],
4079  'קיי אס פי':['ksp'],'ksp':['קיי אס פי'],
4080  'גולף':['golf'],'golf':['גולף'],
4081  'פוקס':['fox'],'fox':['פוקס'],
4082  'רנואר':['renuar'],'renuar':['רנואר'],
4083  'אדיקה':['adika'],'adika':['אדיקה'],
4084  'שופרסל':['shufersal'],'shufersal':['שופרסל'],
4085  'רמי לוי':['rami levy','rami-levy'],'rami levy':['רמי לוי'],
4086  'עליאקספרס':['aliexpress'],'aliexpress':['עליאקספרס'],
4087  'אמאזון':['amazon'],'amazon':['אמאזון'],
4088  'בוקינג':['booking'],'booking':['בוקינג'],
4089  'איביי':['ebay'],'ebay':['איביי'],
4090};
4091
4092function _searchAliases(q) {
4093  const direct = STORE_ALIASES[q] || [];
4094  // also try partial key match
4095  const partial = [];
4096  for (const [k,v] of Object.entries(STORE_ALIASES)) {
4097    if (k !== q && k.includes(q)) partial.push(...v);
4098  }
4099  return [...direct, ...partial];
4100}
4101
4102// Shared pagination helper — build page-number list with ellipsis
4103function _pageNums(cur, tot) {
4104  if (tot <= 1) return [];
4105  const c = cur + 1; // 1-indexed
4106  const out = [];
4107  out.push(1);
4108  const lo = Math.max(2, c - 2);
4109  const hi = Math.min(tot - 1, c + 2);
4110  if (lo > 2)       out.push('…');
4111  for (let i = lo; i <= hi; i++) out.push(i);
4112  if (hi < tot - 1) out.push('…');
4113  if (tot > 1)      out.push(tot);
4114  return out;
4115}
4116
4117async function renderAdminTable() {
4118  // Build query — always fetch from server so new coupons are always visible
4119  const params = new URLSearchParams();
4120  if (ADM.filter)   params.set('search', ADM.filter);
4121  if (ADM.cat)      params.set('category', ADM.cat);
4122  if (ADM.disc)     params.set('source_type', ADM.disc);
4123  if (ADM.sort === 'newest' || ADM.sort === 'oldest') params.set('sort', ADM.sort);
4124  params.set('limit',  ADM.perPage);
4125  params.set('offset', ADM.page * ADM.perPage);
4126
4127  let slice = [], total = 0;
4128  try {
4129    // Fetch page data + exact total count in parallel
4130    const countParams = new URLSearchParams();
4131    if (ADM.filter) countParams.set('search',   ADM.filter);
4132    if (ADM.cat)    countParams.set('category', ADM.cat);
4133    if (ADM.disc)   countParams.set('source_type', ADM.disc);
4134    const [res, countRes] = await Promise.all([
4135      fetch(`/api/coupons?${params}`),
4136      fetch(`/api/coupons/count?${countParams}`),
4137    ]);
4138    const data      = await res.json();
4139    const countData = await countRes.json();
4140    total = countData.count ?? data.length;
4141    const normalize = c => ({
4142      id: c.id, store: c.store, code: c.code,
4143      discount: c.discount || 'הנחה', notes: c.notes || '',
4144      cat: (c.categories && c.categories[0]) || 'Other', src: c.source || '',
4145      status: c.status || 'unknown', votes: c.votes || 0,
4146      createdAt: c.created_at || null, publishedAt: c.published_at || null,
4147    });
4148    slice = data.map(normalize);
4149    if (ADM.geo === 'il')   slice = slice.filter(c => isIsraeli(c.store, c.src));
4150    if (ADM.geo === 'intl') slice = slice.filter(c => !isIsraeli(c.store, c.src));
4151  } catch(e) { slice = []; total = 0; }
4152
4153  const pages = Math.max(1, Math.ceil(total / ADM.perPage));
4154
4155  const html = slice.map((c, idx) => {
4156    const { url: srcUrl, type: srcType } = _admCleanSrc(c.src);
4157    const _tgNameMatch = c.src.match(/^Telegram:\s*(.+)/i);
4158    const srcCell = srcUrl.startsWith('https://t.me/')
4159      ? `<a href="${srcUrl}" target="_blank" style="color:#29b6f6;text-decoration:none;word-break:break-all" title="${srcUrl}"><svg width="10" height="10" viewBox="0 0 24 24" fill="#29b6f6" style="vertical-align:-1px;margin-left:3px"><path d="M11.944 0A12 12 0 1 0 24 12 12 12 0 0 0 11.944 0zm5.992 8.287-2.021 9.525c-.148.658-.55.818-1.113.508l-3-2.21-1.448 1.393c-.16.16-.295.295-.605.295l.213-3.052 5.56-5.023c.242-.214-.054-.333-.373-.12l-6.871 4.326-2.962-.924c-.644-.204-.657-.644.136-.953l11.574-4.462c.537-.194 1.006.131.91.697z"/></svg> ${srcUrl.replace('https://t.me/','@')}</a>`
4160      : srcUrl.startsWith('http')
4161        ? `<a href="${srcUrl}" target="_blank" style="color:var(--accent);text-decoration:none;word-break:break-all" title="${srcUrl}">${new URL(srcUrl).hostname.replace('www.','')}</a>`
4162        : _tgNameMatch
4163          ? `<span style="color:#29b6f6;word-break:break-all"><svg width="10" height="10" viewBox="0 0 24 24" fill="#29b6f6" style="vertical-align:-1px;margin-left:3px"><path d="M11.944 0A12 12 0 1 0 24 12 12 12 0 0 0 11.944 0zm5.992 8.287-2.021 9.525c-.148.658-.55.818-1.113.508l-3-2.21-1.448 1.393c-.16.16-.295.295-.605.295l.213-3.052 5.56-5.023c.242-.214-.054-.333-.373-.12l-6.871 4.326-2.962-.924c-.644-.204-.657-.644.136-.953l11.574-4.462c.537-.194 1.006.131.91.697z"/></svg> ${_tgNameMatch[1].trim()}</span>`
4164          : `<span style="color:var(--gray5)">${srcUrl||'—'}</span>`;
4165    const dtInfo  = DTYPE_LABEL[srcType];
4166    const isTelegram = !srcType && (c.src.startsWith('Telegram:') || c.src.includes('t.me/'));
4167    const dtCell  = dtInfo
4168      ? `<span style="font-size:11px;padding:2px 7px;border-radius:20px;white-space:nowrap;background:${dtInfo.bg};color:${dtInfo.color}">${dtInfo.icon} ${dtInfo.text}</span>`
4169      : isTelegram
4170        ? `<span style="font-size:11px;padding:2px 7px;border-radius:20px;white-space:nowrap;background:rgba(41,182,246,0.15);color:#29b6f6">
4170<svg width="11" height="11" viewBox="0 0 24 24" fill="#29b6f6" style="vertical-align:-1px;margin-left:3px"><path d="M11.944 0A12 12 0 1 0 24 12 12 12 0 0 0 11.944 0zm5.992 8.287-2.021 9.525c-.148.658-.55.818-1.113.508l-3-2.21-1.448 1.393c-.16.16-.295.295-.605.295l.213-3.052 5.56-5.023c.242-.214-.054-.333-.373-.12l-6.871 4.326-2.962-.924c-.644-.204-.657-.644.136-.953l11.574-4.462c.537-.194 1.006.131.91.697z"/></svg> טלגרם</span>`
4171        : `<span style="color:var(--gray5);font-size:11px">—</span>`;
4172    const safeCode = (c.code||'').replace(/'/g,"\\'");
4173    const notesCell = c.notes ? `<span title="${c.notes.replace(/"/g,'&quot;')}" style="cursor:default;color:var(--gray6);font-size:12px">${c.notes.length>40?c.notes.slice(0,40)+'…':c.notes}</span>` : `<span style="color:var(--gray4)">—</span>`;
4174    const rowNum = ADM.page * ADM.perPage + idx + 1;
4175    // Derive a useful link for the store: use the source page URL if available, else /go
4176    const _srcRaw = (c.source||'');
4177    const _srcUrl = _srcRaw.includes('|') ? _srcRaw.split('|').slice(1).join('|') : '';
4178    const _storeHref = _srcUrl && _srcUrl.startsWith('http') ? _srcUrl : `/go?store=${encodeURIComponent(c.store)}`;
4179    // Favicon for admin table row
4180    const _admFavUrls = storeToFavicon(c.store, c.source) || [];
4181    const _admFavUrl  = _admFavUrls[0] || '';
4182    const _admFavFb   = _admFavUrls.slice(1).join('|');
4183    const _admFavHtml = _admFavUrl
4184      ? `<img src="${_admFavUrl}" onerror="_faviconErr(this,'${_admFavFb}','${(c.store||'').replace(/'/g,"\\'")}')" style="width:16px;height:16px;border-radius:3px;object-fit:contain;vertical-align:middle;flex-shrink:0">`
4185      : `<span style="display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:3px;background:var(--gray3);font-size:8px;font-weight:700;color:var(--white);flex-shrink:0">${(c.store||'?').replace(/[^a-zA-Zא-ת]/g,'')[0]||'?'}</span>`;
4186    return `<tr>
4187      <td style="color:var(--gray5);font-size:11px;text-align:center">${rowNum}</td>
4188      <td style="text-align:center;padding:0 4px">${_admFavHtml}</td>
4189      <td class="td-store"><a href="${_storeHref}" target="_blank" style="color:var(--white);text-decoration:none;border-bottom:1px dotted var(--gray5)" onmouseover="this.style.color='var(--accent)';this.style.borderBottomColor='var(--accent)'" onmouseout="this.style.color='var(--white)';this.style.borderBottomColor='var(--gray5)'">${STORE_HE[c.store] || c.store}</a></td>
4190      <td>${(c.code||'').toUpperCase()==='NO_CODE' ? `<span style="color:var(--gray5);font-size:13px">-</span>` : `<span class="td-code" style="font-size:13px;letter-spacing:1px">${c.code}</span>`}</td>
4191      <td>${c.discount||'—'}</td>
4192      <td class="col-notes">${notesCell}</td>
4193      <td>${CAT_HE[c.cat]||c.cat||'—'}</td>
4194      <td class="col-source">${srcCell}</td>
4195      <td class="col-source">${dtCell}</td>
4196      <td style="font-size:11px;color:var(--gray5);white-space:nowrap">${c.createdAt ? new Date(c.createdAt).toLocaleDateString('he-IL',{day:'2-digit',month:'2-digit',year:'2-digit'}) : '—'}</td>
4197      <td style="font-size:11px;color:var(--gray5);white-space:nowrap">${c.publishedAt ? new Date(c.publishedAt).toLocaleDateString('he-IL',{day:'2-digit',month:'2-digit',year:'2-digit'}) : '—'}</td>
4198      <td><div class="td-actions">
4199        <button class="td-btn" onclick="copyCode('${safeCode}','${(c.store||'').replace(/'/g,"\\'")}','')">העתק</button>
4200        <button class="td-btn danger" onclick="adminDelete('${c.id}',this,'${(c.store||'').replace(/'/g,"\\'")}','${(c.src||'').replace(/'/g,"\\'")}')">מחק</button>
4201      </div></td>
4202    </tr>`;
4203  }).join('') || `<tr><td colspan="12" style="text-align:center;color:var(--gray5);padding:24px">אין תוצאות</td></tr>`;
4204
4205  ['admin-tbody','coupons-tbody'].forEach(tid => {
4206    const el = document.getElementById(tid);
4207    if (el) el.innerHTML = html;
4208  });
4209
4210  const nums    = _pageNums(ADM.page, pages);
4211  const numBtns = nums.map(n => n === '…'
4212    ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
4213    : `<button class="td-btn${n-1===ADM.page?' page-active':''}" onclick="admGoPage(${n-1})" style="min-width:32px">${n}</button>`
4214  ).join('');
4215
4216  const pagHtml = `
4217  <div style="font-size:11px;color:var(--gray5);text-align:center;padding:8px 0 2px">${total} קופונים</div>
4218  <div style="display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 18px 14px;border-top:1px solid var(--gray3);flex-wrap:wrap">
4219    <button class="td-btn" onclick="admPage(-1)" ${ADM.page===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
4220    ${numBtns}
4221    <button class="td-btn" onclick="admPage(1)" ${ADM.page>=pages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
4222    <span style="display:flex;align-items:center;gap:6px;margin-right:8px;border-right:1px solid var(--gray3);padding-right:12px">
4223      <span style="font-size:12px;color:var(--gray5)">עמוד</span>
4224      <input type="number" min="1" max="${pages}" value="${ADM.page+1}"
4225        style="width:52px;background:var(--gray3);border:1px solid var(--gray4);border-radius:6px;padding:3px 6px;font-size:12px;color:var(--white);text-align:center;outline:none;font-family:'DM Sans',sans-serif"
4226        onkeydown="if(event.key==='Enter'){const p=Math.max(1,Math.min(${pages},+this.value||1));admGoPage(p-1)}"
4227        onblur="const p=Math.max(1,Math.min(${pages},+this.value||1));admGoPage(p-1)"
4228      />
4229      <span style="font-size:12px;color:var(--gray5)">מתוך ${pages}</span>
4230    </span>
4231  </div>`;
4232  ['admin-pagination','coupons-pagination'].forEach(pid => {
4233    const el = document.getElementById(pid);
4234    if (el) el.innerHTML = pagHtml;
4235  });
4236}
4237
4238function admPage(dir)  { ADM.page = Math.max(0, ADM.page + dir); renderAdminTable(); }
4239function admGoPage(n)  { ADM.page = n; renderAdminTable(); }
4240function filterAdmin(v){ ADM.filter = v; ADM.page = 0; renderAdminTable(); }
4241function admSort(sel)   { ADM.sort   = sel.value; ADM.page = 0; renderAdminTable(); }
4242function admCat(sel)    { ADM.cat    = sel.value; ADM.page = 0; renderAdminTable(); }
4243function admDisc(sel)   { ADM.disc   = sel.value; ADM.page = 0; renderAdminTable(); }
4244function admGeo(sel)    { ADM.geo    = sel.value; ADM.page = 0; renderAdminTable(); }
4245
4246async function adminDelete(id, btn, storeName, storeSrc) {
4247  if (!id) return;
4248  const row = btn.closest('tr');
4249
4250  // Look up domain from stores map (populated from DB)
4251  const storeDomain = STORE_DOMAINS[(storeName || '').toLowerCase().trim()] || '';
4252
4253  const choice = await _showDeleteModal(storeName || '', storeDomain);
4254  if (!choice) return;
4255
4256  btn.disabled = true;
4257
4258  if (choice.action === 'one') {
4259    const res = await fetch(`/api/admin/coupons/${id}`, { method: 'DELETE' }).catch(() => null);
4260    if (res && res.ok) {
4261      if (row) { row.style.transition='opacity 0.3s'; row.style.opacity='0'; setTimeout(()=>row.remove(),300); }
4262      const idx = COUPONS.findIndex(c => c.id === id);
4263      if (idx > -1) COUPONS.splice(idx, 1);
4264      const idx2 = ALL_COUPONS.findIndex(c => c.id === id);
4265      if (idx2 > -1) ALL_COUPONS.splice(idx2, 1);
4266      if (choice.block && choice.domain) {
4267        await fetch('/api/admin/blocked-sites', {
4268          method: 'POST', headers: {'Content-Type':'application/json'},
4269          body: JSON.stringify({ name: storeName, domain: choice.domain, reason: 'נמחק מהאדמין' })
4270        }).catch(() => null);
4271        showToast('🚫 האתר נחסם');
4272      }
4273      renderAdminTable();
4274    } else { btn.disabled = false; }
4275
4276  } else {
4277    const deleteStore = choice.action === 'store';
4278    const res = await fetch('/api/admin/store-and-coupons', {
4279      method: 'DELETE',
4280      headers: {'Content-Type':'application/json'},
4281      body: JSON.stringify({ store: storeName, delete_store: deleteStore })
4282    }).catch(() => null);
4283    if (res && res.ok) {
4284      const d = await res.json();
4285      let msg = `🗑 נמחקו ${d.coupons_deleted} קופונים${deleteStore && d.stores_deleted ? ' + החנות' : ''}`;
4286      if (choice.block && choice.domain) {
4287        const bRes = await fetch('/api/admin/blocked-sites', {
4288          method: 'POST',
4289          headers: {'Content-Type':'application/json'},
4290          body: JSON.stringify({ name: storeName, domain: choice.domain, reason: 'נמחק מהאדמין' })
4291        }).catch(() => null);
4292        if (bRes && bRes.ok) msg += ' + נחסם';
4293      }
4294      showToast(msg);
4295      renderAdminTable();
4296    } else { btn.disabled = false; }
4297  }
4298}
4299
4300function _showDeleteModal(storeName, storeDomain) {
4301  return new Promise(resolve => {
4302    const overlay = document.createElement('div');
4303    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:center;justify-content:center';
4304    overlay.innerHTML = `
4305      <div id="_del_modal" style="background:#1a1a1a;border:1px solid #333;border-radius:14px;padding:28px 32px;min-width:320px;max-width:440px;text-align:right;direction:rtl;font-family:'DM Sans',sans-serif">
4306        <div style="font-size:16px;font-weight:600;margin-bottom:6px;color:#fff">מחיקת קופון</div>
4307        <div style="font-size:13px;color:#888;margin-bottom:18px">חנות: <span style="color:#fff">${storeName || '—'}</span></div>
4308        <div style="display:flex;flex-direction:column;gap:8px">
4309          <label style="display:flex;align-items:center;gap:10px;cursor:pointer;padding:10px 14px;border:1px solid #444;border-radius:8px;background:#2a2a2a">
4310            <input type="radio" name="_del_action" value="one" checked style="accent-color:var(--accent);cursor:pointer" />
4311            <span style="font-size:13px;color:#fff">🗑 מחק קופון זה בלבד</span>
4312          </label>
4313          <label style="display:flex;align-items:center;gap:10px;cursor:pointer;padding:10px 14px;border:1px solid #444;border-radius:8px;background:#2a2a2a">
4314            <input type="radio" name="_del_action" value="coupons" style="accent-color:var(--accent);cursor:pointer" />
4315            <span style="font-size:13px;color:#fff">🗑🗑 מחק את כל הקופונים של החנות</span>
4316          </label>
4317          <label style="display:flex;align-items:center;gap:10px;cursor:pointer;padding:10px 14px;border:1px solid #8b2222;border-radius:8px;background:#3a1a1a">
4318            <input type="radio" name="_del_action" value="store" style="accent-color:#e05555;cursor:pointer" />
4319            <span style="font-size:13px;color:#ff6b6b">💣 מחק את כל הקופונים + החנות מ-stores</span>
4320          </label>
4321        </div>
4322        <div style="margin-top:16px;padding-top:14px;border-top:1px solid #2a2a2a">
4323          <label style="display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:#aaa">
4324            <input type="checkbox" id="_del_block_cb" style="width:16px;height:16px;accent-color:#e05555;cursor:pointer" />
4325            <span>🚫 הוסף גם לרשימת החסימה</span>
4326          </label>
4327          <div id="_del_block_domain" style="display:none;margin-top:10px">
4328            <input id="_del_block_input" type="text" value="${storeDomain || ''}" placeholder="domain.com" style="background:#2a2a2a;border:1px solid #444;border-radius:8px;padding:7px 12px;font-size:13px;color:#fff;outline:none;font-family:'DM Sans',sans-serif;width:100%;box-sizing:border-box;direction:ltr;text-align:left" />
4329          </div>
4330        </div>
4331        <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:20px">
4332          <button id="_del_cancel" style="background:transparent;border:1px solid #444;border-radius:8px;padding:8px 18px;color:#aaa;cursor:pointer;font-size:13px;font-family:'DM Sans',sans-serif">ביטול</button>
4333          <button id="_del_ok" style="background:var(--accent);border:none;border-radius:8px;padding:8px 22px;color:#000;cursor:pointer;font-size:13px;font-weight:600;font-family:'DM Sans',sans-serif">אישור</button>
4334        </div>
4335      </div>`;
4336
4337    document.body.appendChild(overlay);
4338
4339    const cb = overlay.querySelector('#_del_block_cb');
4340    const domainWrap = overlay.querySelector('#_del_block_domain');
4341    cb.addEventListener('change', () => { domainWrap.style.display = cb.checked ? 'block' : 'none'; });
4342
4343    function _resolve() {
4344      const action = overlay.querySelector('input[name="_del_action"]:checked')?.value || 'one';
4345      const block = cb.checked;
4346      const domain = overlay.querySelector('#_del_block_input')?.value?.trim() || '';
4347      document.body.removeChild(overlay);
4348      resolve({ action, block, domain });
4349    }
4350
4351    overlay.querySelector('#_del_ok').addEventListener('click', e => { e.stopPropagation(); _resolve(); });
4352    overlay.querySelector('#_del_cancel').addEventListener('click', e => {
4353      e.stopPropagation();
4354      document.body.removeChild(overlay);
4355      resolve(null);
4356    });
4357  });
4358}
4359
4360function filterPub() {
4361  pubSearch = document.getElementById('pub-search').value;
4362  // Do NOT clear search results on input — results persist until next AI search starts
4363}
4364
4365// ── Admin auth ───────────────────────────────────────────────────────────────
4366function _isAdminAuthed() {
4367  return sessionStorage.getItem(_ADMIN_KEY) === '1';
4368}
4369function _getUserRole() {
4370  return sessionStorage.getItem('couponai_user_role') || 'operator';
4371}
4372function _isPowerUser() {
4373  // admin or operator see full technical details
4374  return _isAdminAuthed() && (_getUserRole() === 'admin' || _getUserRole() === 'operator');
4375}
4376
4377function _showLoginModal() {
4378  document.getElementById('login-overlay').style.display = 'flex';
4379  setTimeout(() => document.getElementById('login-username').focus(), 50);
4380}
4381
4382function _hideLoginModal() {
4383  document.getElementById('login-overlay').style.display = 'none';
4384  document.getElementById('login-username').value = '';
4385  document.getElementById('login-password').value = '';
4386  document.getElementById('login-error').style.display = 'none';
4387}
4388
4389async function submitLogin(e) {
4390  if (e) e.preventDefault();
4391  const username = (document.getElementById('login-username').value || '').trim();
4392  const pass     = document.getElementById('login-password').value;
4393  const err      = document.getElementById('login-error');
4394  err.style.display = 'none';
4395  try {
4396    const res = await fetch('/api/admin/login', {
4397      method: 'POST',
4398      headers: {'Content-Type':'application/json'},
4399      body: JSON.stringify({username, password: pass, did: _getDeviceId()})
4400    });
4401    if (res.ok) {
4402      const data = await res.json();
4403      sessionStorage.setItem(_ADMIN_KEY, '1');
4404      sessionStorage.setItem('couponai_admin_user', data.username || username);
4405      sessionStorage.setItem('couponai_user_role', data.role || 'operator');
4406      document.body.classList.add('is-staff');
4407      // Tag a login event immediately so device→user mapping exists in DB
4408      trackEvent('login', 'כניסה לניהול');
4409      await _syncAnalyticsToServer(false).catch(() => {});
4410      _hideLoginModal();
4411      _doShowPage('admin');
4412    } else {
4413      err.style.display = 'block';
4414      document.getElementById('login-password').value = '';
4415      document.getElementById('login-password').focus();
4416    }
4417  } catch(e) {
4418    err.style.display = 'block';
4419  }
4420}
4421
4422function adminLogout() {
4423  sessionStorage.removeItem(_ADMIN_KEY);
4424  sessionStorage.removeItem('couponai_admin_user');
4425  sessionStorage.removeItem('couponai_user_role');
4426  document.body.classList.remove('is-staff');
4427  showPage('public');
4428}
4429
4430// Restore is-staff on page reload if session is still active
4431if (sessionStorage.getItem(_ADMIN_KEY) === '1') {
4432  document.body.classList.add('is-staff');
4433}
4434
4435// ── User management ──────────────────────────────────────────────────────────
4436function showToast(msg, duration) {
4437  const t = document.getElementById('toast');
4438  if (!t) return;
4439  t.textContent = msg;
4440  t.classList.add('show');
4441  clearTimeout(t._hideTimer);
4442  const ms = duration || (msg.includes('❌') || msg.includes('⚠️') ? 10000 : 4000);
4443  t._hideTimer = setTimeout(() => t.classList.remove('show'), ms);
4444}
4445
4446async function _loadUsers() {
4447  const tbody = document.getElementById('users-tbody');
4448  if (!tbody) return;
4449  tbody.innerHTML = '<tr><td colspan="7" style="padding:20px;text-align:center;color:var(--gray5)">טוען...</td></tr>';
4450  const res = await fetch('/api/admin/users', { cache: 'no-store' });
4451  const users = await res.json();
4452  if (!users.length) {
4453    tbody.innerHTML = '<tr><td colspan="7" style="padding:20px;text-align:center;color:var(--gray5)">אין משתמשים</td></tr>';
4454    return;
4455  }
4456  const _ROLE_BADGE = {
4457    admin:    `<span style="font-size:11px;padding:2px 9px;border-radius:20px;background:rgba(252,129,129,0.15);color:#fc8181;font-weight:600">👑 אדמין</span>`,
4458    operator: `<span style="font-size:11px;padding:2px 9px;border-radius:20px;background:rgba(255,228,94,0.15);color:var(--accent2);font-weight:600">🔧 אופרטור</span>`,
4459    user:     `<span style="font-size:11px;padding:2px 9px;border-radius:20px;background:rgba(100,100,100,0.2);color:var(--gray6);font-weight:600">👤 משתמש</span>`,
4460  };
4461  const myRole = _getUserRole();
4462  tbody.innerHTML = users.map((u, i) => {
4463    const role = u.role || 'operator';
4464    const badge = _ROLE_BADGE[role] || _ROLE_BADGE.operator;
4465    const deleteBtn = myRole === 'admin'
4466      ? `<button class="td-btn" onclick="_deleteUser('${u.id}','${u.username}')" style="color:#ff6b6b;border-color:rgba(255,107,107,0.3)">מחק</button>`
4467      : `<button class="td-btn" disabled title="רק אדמין יכול למחוק משתמשים" style="opacity:0.3;cursor:not-allowed">מחק</button>`;
4468    const lastLogin = u.last_login_at
4469      ? new Date(u.last_login_at).toLocaleString('he-IL', {day:'2-digit',month:'2-digit',year:'2-digit',hour:'2-digit',minute:'2-digit'})
4470      : '—';
4471    const lastIp = u.last_login_ip || '—';
4472    return `<tr style="border-bottom:1px solid var(--gray3)">
4473      <td style="padding:12px 16px;color:var(--gray5);font-size:13px">${i+1}</td>
4474      <td style="padding:12px 16px;font-size:14px;font-weight:500">${u.username}</td>
4475      <td style="padding:12px 16px">${badge}</td>
4476      <td style="padding:12px 16px;font-size:12px;color:var(--gray5)">${new Date(u.created_at).toLocaleDateString('he-IL')}</td>
4477      <td style="padding:12px 16px;font-size:12px;color:var(--gray6)">${lastLogin}</td>
4478      <td style="padding:12px 16px;font-size:11px;color:var(--gray5);font-family:monospace">${lastIp}</td>
4479      <td style="padding:12px 16px;display:flex;gap:6px">
4480        <button class="td-btn" onclick="_openEditUserModal('${u.id}','${u.username}','${role}')">ערוך</button>
4481        ${deleteBtn}
4482      </td>
4483    </tr>`;
4484  }).join('');
4485}
4486
4487async function _openAddUserModal() {
4488  const username = prompt('שם משתמש חדש:');
4489  if (!username) return;
4490  const password = prompt('סיסמה (מינ׳ 6 תווים):');
4491  if (!password) return;
4492  const roleRaw = prompt('תפקיד (admin / operator / user):', 'operator');
4493  const role = ['admin','operator','user'].includes((roleRaw||'').trim().toLowerCase()) ? roleRaw.trim().toLowerCase() : 'operator';
4494  try {
4495    const r = await fetch('/api/admin/users', {
4496      method: 'POST',
4497      headers: {'Content-Type':'application/json'},
4498      body: JSON.stringify({username: username.trim(), password, role})
4499    });
4500    if (r.ok) {
4501      showToast('משתמש נוסף ✓');
4502      await _loadUsers();
4503    } else {
4504      const d = await r.json().catch(() => ({}));
4505      showToast('שגיאה: ' + (d.detail || 'נסה שוב'));
4506    }
4507  } catch(e) {
4508    showToast('שגיאת רשת — נסה שוב');
4509  }
4510}
4511
4512function _openEditUserModal(id, username, role) {
4513  document.getElementById('eu-id').value = id;
4514  document.getElementById('eu-username').value = username;
4515  document.getElementById('eu-role').value = role || 'operator';
4516  document.getElementById('eu-password').value = '';
4517  document.getElementById('eu-error').style.display = 'none';
4518  document.getElementById('edit-user-overlay').style.display = 'flex';
4519  setTimeout(() => document.getElementById('eu-username').focus(), 50);
4520}
4521
4522function _closeEditUserModal() {
4523  document.getElementById('edit-user-overlay').style.display = 'none';
4524}
4525
4526async function _submitEditUser() {
4527  const id       = document.getElementById('eu-id').value;
4528  const username = document.getElementById('eu-username').value.trim();
4529  const password = document.getElementById('eu-password').value;
4530  const role     = document.getElementById('eu-role').value;
4531  const err      = document.getElementById('eu-error');
4532  if (!username) { err.textContent = 'שם משתמש לא יכול להיות ריק'; err.style.display='block'; return; }
4533  const body = { username, role };
4534  if (password) body.password = password;
4535  const r = await fetch(`/api/admin/users/${id}`, {
4536    method: 'PATCH',
4537    headers: {'Content-Type':'application/json'},
4538    body: JSON.stringify(body)
4539  });
4540  if (r.ok) {
4541    _closeEditUserModal();
4542    showToast('משתמש עודכן ✓');
4543    _loadUsers();
4544  } else {
4545    const d = await r.json();
4546    err.textContent = d.detail || 'שגיאה בעדכון';
4547    err.style.display = 'block';
4548  }
4549}
4550
4551async function _deleteUser(id, username) {
4552  if (!confirm(`למחוק את המשתמש "${username}"?`)) return;
4553  const r = await fetch(`/api/admin/users/${id}`, {
4554    method: 'DELETE',
4555    headers: { 'X-Caller-Role': _getUserRole() }
4556  });
4557  if (r.ok) { showToast('משתמש נמחק'); await _loadUsers(); }
4558  else {
4559    const d = await r.json().catch(() => ({}));
4560    showToast('שגיאה: ' + (d.detail || 'אין הרשאה'));
4561  }
4562}
4563
4564function showPage(id){
4565  if (id === 'admin' && !_isAdminAuthed()) {
4566    _showLoginModal();
4567    return;
4568  }
4569  _doShowPage(id);
4570}
4571
4572function _doShowPage(id){
4573  document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
4574  document.getElementById('page-'+id).classList.add('active');
4575  document.querySelectorAll('.nav-link').forEach((b,i)=>b.classList.toggle('active',(id==='public'&&i===0)||(id==='admin'&&i===1)));
4576  // Re-render charts + refresh all data when admin becomes visible
4577  if (id === 'admin') {
4578    requestAnimationFrame(async () => {
4579      // 1. Refresh coupon metrics (active/total/stores) from server
4580      try { await loadStats(); } catch(e) {}
4581      // 2. Pull latest analytics events from server then re-render dashboard panels
4582      try { await _loadAnalyticsFromServer(); } catch(e) {}
4583      try { _renderPopSearchBar(); } catch(e) {}
4584      try { _renderSlowBar(); } catch(e) {}
4585      try { loadAnalytics(); }      catch(e) {}
4586      try { loadSpeedAnalytics(); } catch(e) {}
4587      try { loadPopupAnalytics(); } catch(e) {}
4588      // 3. Refresh source panel + coupon table
4589      try { _fetchSourceData(); }   catch(e) {}
4590      try { renderAdminTable(); }   catch(e) {}
4591      try { _refreshOrphanCount(); } catch(e) {}
4592    });
4593  }
4594}
4595// Apply is-mobile class — uses ?pw= param in preview mode, innerWidth on real devices
4596(function() {
4597  function _applyMobileClass() {
4598    var w = window._previewWidth || window.innerWidth;
4599    if (w <= 640) {
4600      document.body.classList.add('is-mobile');
4601    } else {
4602      document.body.classList.remove('is-mobile');
4603    }
4604  }
4605  _applyMobileClass();
4606  window.addEventListener('resize', _applyMobileClass);
4607})();
4608
4609const _VIEW_TITLES = { dashboard:'דשבורד', coupons:'כל הקופונים', sources:'טלגרם', 'coupon-sites':'אתרי קופונים בלבד', 'automation-scans':'סריקות', web:'אתרים שנסרקו', instagram:'אינפלואנסרים', newsletters:'ניוזלטרים', affiliate:'שותפויות', 'blocked-sites':'אתרים חסומים', stores:'חנויות', pending:'לבדיקה', 'no-url':'אין אתר יעד', users:'משתמשים', settings:'הגדרות', logs:'לוגים', 'hide-mechanism':'מנגנון הסתרה', expired:'פג תוקף', 'search-anim':'אנימציית חיפוש', mole:'משחקים', minigames:'אנימציות טעינה', debug:'Pipeline Debugger', 'null-domain':'אין דומיין', 'orphan-coupons':'קופונים ללא דומיין', 'analytics-usage':'אנליטיקס שימוש', 'analytics-speed':'זמני חיפוש', 'analytics-popup':'פופאפ קופונים', 'analytics-extension':'תוסף Chrome', 'analytics-buttons':'תפריט וכפתורים', 'store-pages-demo':'דמו דפי חנויות', 'home-demo':'מוקאפ עמוד בית', 'contact-messages':'הודעות צור קשר', 'user-profiles':'פרופילי משתמשים', 'search-intel':'ניתוח חיפושים', 'demand-gaps':'ביקוש ללא קופונים', 'traffic-sources':'מקורות תנועה', 'geo-map':'גיאוגרפיה', 'device-stats':'מכשירים ודפדפנים', 'js-errors':'שגיאות JS' };
4610
4611let _activeAdminView = 'dashboard';
4612
4613// ── Trusted coupon sites (אתרי קופונים בלבד) ────────────────────────────────
4614const INPUT_PRICE_PER_TOKEN  = 0.80 / 1_000_000;
4615const OUTPUT_PRICE_PER_TOKEN = 4.00 / 1_000_000;
4616
4617function _calcSiteCost(tokIn, tokOut) {
4618  return (tokIn * INPUT_PRICE_PER_TOKEN) + (tokOut * OUTPUT_PRICE_PER_TOKEN);
4619}
4620
4621function _fmtCost(dollars) {
4622  if (dollars === 0) return '—';
4623  if (dollars < 0.01) return '<$0.01';
4624  return '$' + dollars.toFixed(2);
4625}
4626
4627function _fmtTokens(n) {
4628  if (!n) return '—';
4629  return n >= 1000 ? (n/1000).toFixed(1) + 'K' : String(n);
4630}
4631
4632async function loadCouponSites() {
4633  const tbody = document.getElementById('coupon-sites-tbody');
4634  try {
4635    const r = await fetch('/api/admin/trusted-coupon-sites');
4636    const data = await r.json();
4637    document.getElementById('src-coupon-sites-count').textContent = data.length;
4638    if (!data.length) {
4639      tbody.innerHTML = '<tr><td colspan="6" style="color:var(--gray5);text-align:center;padding:20px">אין אתרים</td></tr>';
4640      return;
4641    }
4642    tbody.innerHTML = data.map((s, i) => {
4643      const method = s.extraction_method || 'bs';
4644      const isBS   = method === 'bs';
4645      const methodBadge = isBS
4646        ? `<span style="background:#1a3a1a;color:#68d391;border:1px solid #2d6a4f;border-radius:5px;padding:2px 8px;font-size:11px;font-family:monospace;cursor:pointer" onclick="toggleExtractionMethod('${s.id}','ai')" title="לחץ לעבור ל-AI">BS</span>`
4647        : `<span style="background:#3a1a2a;color:#fc8181;border:1px solid #7b2d3f;border-radius:5px;padding:2px 8px;font-size:11px;font-family:monospace;cursor:pointer" onclick="toggleExtractionMethod('${s.id}','bs')" title="לחץ לעבור ל-BS">AI</span>`;
4648      return `
4649      <tr>
4650        <td style="color:var(--gray5)">${i+1}</td>
4651        <td><b>${s.name}</b></td>
4652        <td><a href="${s.url}" target="_blank" style="color:var(--accent);font-size:12px">${s.url}</a></td>
4653        <td style="font-family:monospace;font-size:11px;color:var(--gray5)">${s.store_url_pattern || '—'}</td>
4654        <td>${methodBadge}</td>
4655        <td><span style="color:${s.active ? 'var(--accent)' : 'var(--gray5)'}; font-size:12px">${s.active ? '✅ פעיל' : '⏸ כבוי'}</span></td>
4656        <td>
4657          <button class="btn-sm btn-ghost" onclick="toggleTrustedSite('${s.id}', ${!s.active})">${s.active ? 'כבה' : 'הפעל'}</button>
4658          <button class="btn-sm btn-ghost" style="color:#fc8181" onclick="deleteTrustedSite('${s.id}')">מחק</button>
4659        </td>
4660      </tr>`;
4661    }).join('');
4662  } catch(e) {
4663    tbody.innerHTML = '<tr><td colspan="6" style="color:#fc8181;text-align:center;padding:20px">שגיאה בטעינה</td></tr>';
4664  }
4665}
4666
4667function updateJobBEstCost() {
4668  const sites = 25;
4669  const cap   = parseInt(document.getElementById('cfg-per-site-cap')?.value || 15);
4670  const maxT  = parseInt(document.getElementById('cfg-max-tokens')?.value   || 1500);
4671  // Estimate: avg ~800 input + maxT/2 output tokens per call
4672  const estIn  = sites * cap * 800;
4673  const estOut = sites * cap * (maxT / 2);
4674  const estCost = (estIn * 0.80 / 1_000_000) + (estOut * 4.00 / 1_000_000);
4675  const el = document.getElementById('job-b-est-cost');
4676  if (el) el.textContent = `עלות מקס׳ משוערת: $${estCost.toFixed(2)}`;
4677}
4678
4679async function saveJobBConfig() {
4680  const btn = document.getElementById('job-b-config-save-btn');
4681  const statusEl = document.getElementById('job-b-config-status');
4682  btn.disabled = true;
4683  btn.textContent = 'שומר...';
4684  try {
4685    const body = {
4686      max_tokens:          parseInt(document.getElementById('cfg-max-tokens').value),
4687      max_urls_per_site:   parseInt(document.getElementById('cfg-max-urls').value),
4688      il_coupon_max_pages: parseInt(document.getElementById('cfg-il-coupon-max').value),
4689      per_site_call_cap:   parseInt(document.getElementById('cfg-per-site-cap').value),
4690      max_cost_usd:        parseFloat(document.getElementById('cfg-max-cost').value),
4691    };
4692    const r = await fetch('/api/admin/job-b-config', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
4693    const res = await r.json();
4694    statusEl.style.display = 'block';
4695    statusEl.style.color = 'var(--accent)';
4696    statusEl.textContent = '✅ ההגדרות נשמרו — יחולו בריצה הבאה (02:00)';
4697  } catch(e) {
4698    statusEl.style.display = 'block';
4699    statusEl.style.color = '#fc8181';
4700    statusEl.textContent = '❌ שגיאה בשמירה';
4701  }
4702  btn.disabled = false;
4703  btn.textContent = 'שמור';
4704}
4705
4706async function addTrustedSitePrompt() {
4707  const name = prompt('שם האתר:');
4708  if (!name) return;
4709  const url = prompt('כתובת (URL):');
4710  if (!url) return;
4711  const pattern = prompt('תבנית URL לחנות (למשל: /coupons/{slug}/) — אפשר להשאיר ריק:') || '';
4712  try {
4713    await fetch('/api/admin/trusted-coupon-sites', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({name, url, store_url_pattern: pattern || null, active: true})});
4714    showToast('✅ האתר נוסף');
4715    loadCouponSites();
4716  } catch(e) { showToast('❌ שגיאה בהוספה'); }
4717}
4718
4719async function toggleTrustedSite(id, active) {
4720  await fetch(`/api/admin/trusted-coupon-sites/${id}`, {method:'PATCH', headers:{'Content-Type':'application/json'}, body: JSON.stringify({active})});
4721  loadCouponSites();
4722}
4723
4724async function toggleExtractionMethod(id, method) {
4725  await fetch(`/api/admin/trusted-coupon-sites/${id}`, {method:'PATCH', headers:{'Content-Type':'application/json'}, body: JSON.stringify({extraction_method: method})});
4726  loadCouponSites();
4727}
4728
4729async function deleteTrustedSite(id) {
4730  if (!confirm('למחוק אתר זה?')) return;
4731  await fetch(`/api/admin/trusted-coupon-sites/${id}`, {method:'DELETE'});
4732  showToast('🗑 האתר נמחק');
4733  loadCouponSites();
4734}
4735
4736// ── Automation scans view ────────────────────────────────────────────────────
4737function _renderJobLog(prefix, log) {
4738  if (!log) return;
4739  const el = document.getElementById(`${prefix}-log`);
4740  if (el) el.style.display = 'block';
4741  const ts = log.run_at ? new Date(log.run_at).toLocaleString('he-IL') : '—';
4742  const fin = log.finished_at ? ` (סיים: ${new Date(log.finished_at).toLocaleString('he-IL')})` : '';
4743  const statusColor = log.status === 'success' ? 'var(--accent)' : log.status === 'error' ? '#fc8181' : '#f6c90e';
4744  const statusLabel = log.status === 'success' ? '✅ הצליח' : log.status === 'error' ? '❌ שגיאה' : '⏳ רץ...';
4745  const timeEl = document.getElementById(`${prefix}-log-time`);
4746  const statusEl = document.getElementById(`${prefix}-log-status`);
4747  if (timeEl) timeEl.textContent = ts + fin;
4748  if (statusEl) { statusEl.textContent = statusLabel; statusEl.style.color = statusColor; }
4749
4750  if (prefix === 'job-b') {
4751    const s = document.getElementById('job-b-log-sites');
4752    const c = document.getElementById('job-b-log-coupons');
4753    if (s) s.textContent = log.sites_scanned ?? '—';
4754    if (c) c.textContent = log.coupons_found ?? '—';
4755  } else {
4756    const s = document.getElementById('job-c-log-stores');
4757    const c = document.getElementById('job-c-log-coupons');
4758    if (s) s.textContent = `${log.stores_scanned ?? '—'} / ${log.stores_total ?? '—'}`;
4759    if (c) c.textContent = log.coupons_found ?? '—';
4760  }
4761
4762  const errors = log.errors || [];
4763  const errBox = document.getElementById(`${prefix}-log-errors`);
4764  const errList = document.getElementById(`${prefix}-log-errors-list`);
4765  if (errBox && errList) {
4766    if (errors.length) {
4767      errBox.style.display = 'block';
4768      errList.innerHTML = errors.map(e => `<li>${e}</li>`).join('');
4769    } else {
4770      errBox.style.display = 'none';
4771    }
4772  }
4773}
4774
4775function _showSiteErrors(siteName, errors) {
4776  let modal = document.getElementById('_site-errors-modal');
4777  if (!modal) {
4778    modal = document.createElement('div');
4779    modal.id = '_site-errors-modal';
4780    modal.setAttribute('style','position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:9999;display:flex;align-items:center;justify-content:center');
4781    modal.onclick = e => { if (e.target === modal) modal.remove(); };
4782    document.body.appendChild(modal);
4783  }
4784  modal.innerHTML = '';
4785  const box = document.createElement('div');
4786  box.setAttribute('style','background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:24px;max-width:680px;width:90%;max-height:80vh;overflow-y:auto');
4787  const title = document.createElement('div');
4788  title.setAttribute('style','font-size:15px;font-weight:700;color:#fc8181;margin-bottom:16px');
4789  title.textContent = `❌ שגיאות — ${siteName} (${errors.length})`;
4790  box.appendChild(title);
4791  errors.forEach((err, i) => {
4792    const row = document.createElement('div');
4793    row.setAttribute('style','font-size:11px;color:#ccc;padding:6px 8px;border-bottom:1px solid #222;font-family:monospace;word-break:break-all;direction:ltr;text-align:left');
4794    row.textContent = `${i+1}. ${err}`;
4795    box.appendChild(row);
4796  });
4797  const close = document.createElement('button');
4798  close.setAttribute('style','margin-top:16px;padding:6px 18px;background:#333;border:none;border-radius:6px;color:#fff;cursor:pointer;font-size:12px');
4799  close.textContent = 'סגור';
4800  close.onclick = () => modal.remove();
4801  box.appendChild(close);
4802  modal.appendChild(box);
4803}
4804
4805async function loadAutomationScans() {
4806  // Fire both requests in parallel — the toggle shouldn't sit in its "loading" state
4807  // waiting on the job-logs fetch, which is unrelated and can be slower.
4808  const jobLogsPromise   = fetch('/api/admin/job-logs').then(r => r.json()).catch(() => null);
4809  const jobBConfigPromise = fetch('/api/admin/job-b-config').then(r => r.json()).catch(() => null);
4810
4811  const logs = await jobLogsPromise;
4812  if (logs) {
4813    _renderJobLog('job-b', logs.job_b);
4814    _renderJobLog('job-c', logs.job_c);
4815  }
4816
4817  const cfg = await jobBConfigPromise;
4818  if (cfg) {
4819    if (cfg.max_tokens          != null) document.getElementById('cfg-max-tokens').value    = cfg.max_tokens;
4820    if (cfg.max_urls_per_site   != null) document.getElementById('cfg-max-urls').value      = cfg.max_urls_per_site;
4821    if (cfg.il_coupon_max_pages != null) document.getElementById('cfg-il-coupon-max').value = cfg.il_coupon_max_pages;
4822    if (cfg.per_site_call_cap   != null) document.getElementById('cfg-per-site-cap').value  = cfg.per_site_call_cap;
4823    if (cfg.max_cost_usd        != null) document.getElementById('cfg-max-cost').value       = cfg.max_cost_usd;
4824    _applyJobBToggleUI(cfg.enabled !== false);
4825    updateJobBEstCost();
4826  } else {
4827    // Fetch failed — leave it visibly "loading" rather than silently stuck on a guess.
4828    _applyJobBToggleUI(null);
4829  }
4830
4831  // Job B: load trusted coupon sites
4832  try {
4833    const r = await fetch('/api/admin/trusted-coupon-sites');
4834    const sites = await r.json();
4835    const tbody = document.getElementById('job-b-tbody');
4836    document.getElementById('job-b-status').textContent = `${sites.length} אתרים`;
4837    // Show last-run per-site details if available, otherwise show site list
4838    const lastRun = await fetch('/api/admin/job-logs').then(r=>r.json()).catch(()=>({}));
4839    const siteDetails = (lastRun.job_b?.site_details || []);
4840
4841    if (siteDetails.length) {
4842      document.getElementById('job-b-status').textContent = `${sites.length} אתרים`;
4843      tbody.innerHTML = '';
4844      siteDetails.forEach((s, i) => {
4845        const errList = Array.isArray(s.errors) ? s.errors : [];
4846        const tr = document.createElement('tr');
4847        const td0 = document.createElement('td');
4848        td0.setAttribute('style','color:var(--gray5)'); td0.textContent = i + 1;
4849        const td1 = document.createElement('td');
4850        const b = document.createElement('b'); b.textContent = s.name || '';
4851        const sm = document.createElement('span');
4852        sm.setAttribute('style','display:block;font-size:10px;color:var(--gray5)');
4853        sm.textContent = s.domain || '';
4854        td1.appendChild(b); td1.appendChild(sm);
4855        const td2 = document.createElement('td');
4856        const badge = document.createElement('span');
4857        badge.setAttribute('style','font-size:11px;color:' + (s.method==='sitemap' ? 'var(--accent)' : s.method==='pagination' ? '#63b3ed' : 'var(--gray5)'));
4858        badge.textContent = s.method === 'sitemap' ? 'sitemap' : s.method === 'pagination' ? 'pagination' : 'none';
4859        td2.appendChild(badge);
4860        const td3 = document.createElement('td');
4861        td3.setAttribute('style','text-align:center;color:var(--gray5)');
4862        td3.textContent = s.pages_scraped ?? 0;
4863        const td4 = document.createElement('td');
4864        td4.setAttribute('style','text-align:center');
4865        const bNew = document.createElement('b');
4866        bNew.setAttribute('style','color:' + ((s.new_found > 0) ? '#ffe45e' : 'var(--gray5)'));
4867        bNew.textContent = (s.new_found > 0 ? '+' : '') + (s.new_found ?? 0);
4868        td4.appendChild(bNew);
4869        const td5 = document.createElement('td');
4870        td5.setAttribute('style','text-align:center;color:var(--gray5)');
4871        td5.textContent = s.skipped ?? 0;
4872        const td6 = document.createElement('td');
4873        td6.setAttribute('style','text-align:center');
4874        const errSpan = document.createElement('span');
4875        if (errList.length) {
4876          errSpan.setAttribute('style','font-size:11px;color:#fc8181;cursor:pointer;text-decoration:underline dotted');
4877          errSpan.textContent = '❌ ' + errList.length;
4878          errSpan.onclick = () => _showSiteErrors(s.name, errList);
4879        } else {
4880          errSpan.setAttribute('style','font-size:11px;color:var(--gray5)');
4881          errSpan.textContent = '—';
4882        }
4883        td6.appendChild(errSpan);
4884        const td7 = document.createElement('td');
4885        td7.setAttribute('style','font-size:11px;color:' + (s.completed ? 'var(--accent)' : '#f6c90e'));
4886        td7.textContent = s.completed ? '✅' : '⏳';
4887        // Tokens in/out
4888        const td8 = document.createElement('td');
4889        td8.setAttribute('style','text-align:center;font-size:11px;color:#f6ad55');
4890        const tokIn = s.tokens_in || 0; const tokOut = s.tokens_out || 0;
4891        td8.textContent = (tokIn || tokOut) ? `${_fmtTokens(tokIn)} / ${_fmtTokens(tokOut)}` : '—';
4892        // Cost
4893        const td9 = document.createElement('td');
4894        const siteCost = _calcSiteCost(tokIn, tokOut);
4895        const costColor = siteCost > 1 ? '#fc8181' : siteCost > 0.3 ? '#f6ad55' : siteCost > 0 ? '#68d391' : 'var(--gray5)';
4896        td9.setAttribute('style',`text-align:center;font-weight:600;color:${costColor}`);
4897        td9.textContent = _fmtCost(siteCost);
4898        tr.append(td0, td1, td2, td3, td4, td5, td6, td8, td9, td7);
4899        tbody.appendChild(tr);
4900      });
4901      // Total cost badge
4902      const totalCost = siteDetails.reduce((sum, s) => sum + _calcSiteCost(s.tokens_in || 0, s.tokens_out || 0), 0);
4903      const costEl = document.getElementById('job-b-log-cost');
4904      if (costEl) {
4905        const costColor = totalCost > 10 ? '#fc8181' : totalCost > 5 ? '#f6ad55' : '#68d391';
4906        costEl.textContent = totalCost > 0 ? _fmtCost(totalCost) : '—';
4907        costEl.style.color = totalCost > 0 ? costColor : 'var(--gray5)';
4908      }
4909    } else {
4910      document.getElementById('job-b-status').textContent = `${sites.length} אתרים`;
4911      tbody.innerHTML = '';
4912      (Array.isArray(sites) ? sites : []).forEach((s, i) => {
4913        const tr = document.createElement('tr');
4914        const td0 = document.createElement('td'); td0.setAttribute('style','color:var(--gray5)'); td0.textContent = i + 1;
4915        const td1 = document.createElement('td'); td1.colSpan = 2;
4916        const b = document.createElement('b'); b.textContent = s.name || '';
4917        const sm = document.createElement('span'); sm.setAttribute('style','display:block;font-size:10px;color:var(--gray5)'); sm.textContent = s.url || '';
4918        td1.appendChild(b); td1.appendChild(sm);
4919        const td2 = document.createElement('td'); td2.colSpan = 6; td2.setAttribute('style','color:var(--gray5);font-size:12px'); td2.textContent = 'ממתין לריצה הראשונה (02:00)';
4920        const td3 = document.createElement('td'); const sp = document.createElement('span'); sp.setAttribute('style','color:#f6c90e;font-size:12px'); sp.textContent = '⏳ ממתין'; td3.appendChild(sp);
4921        tr.append(td0, td1, td2, td3);
4922        tbody.appendChild(tr);
4923      });
4924    }
4925  } catch(e) {
4926    console.error('[Job B]', e);
4927    document.getElementById('job-b-tbody').innerHTML = `<tr><td colspan="10" style="color:#fc8181;text-align:center;padding:12px">שגיאה: ${e.message || e}</td></tr>`;
4928  }
4929
4930  // Job B history
4931  try {
4932    const jbHist = await fetch('/api/admin/job-b-history').then(r => r.json());
4933    const histEl = document.getElementById('job-b-history');
4934    const history = (jbHist.history || []).slice().reverse();
4935    if (histEl) {
4936      if (!history.length) {
4937        histEl.textContent = 'אין היסטוריה עדיין — תירשם לאחר הריצה הראשונה בשעה 02:00';
4938      } else {
4939        const IN_PRICE = 0.80 / 1_000_000, OUT_PRICE = 4.00 / 1_000_000;
4940        histEl.innerHTML = `<table style="width:100%;border-collapse:collapse">
4941          <thead><tr>
4942            <th style="text-align:right;padding:6px 10px;color:var(--gray5);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">תאריך</th>
4943            <th style="text-align:center;padding:6px 10px;color:var(--gray5);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">אתרים</th>
4944            <th style="text-align:center;padding:6px 10px;color:var(--accent);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">קופונים</th>
4945            <th style="text-align:center;padding:6px 10px;color:#fc8181;font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">עלות $</th>
4946            <th style="text-align:center;padding:6px 10px;color:var(--gray5);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">סטטוס</th>
4947          </tr></thead>
4948          <tbody>${history.map(h => {
4949            const cost = ((h.tokens_in || 0) * IN_PRICE + (h.tokens_out || 0) * OUT_PRICE).toFixed(3);
4950            const statusColor = h.status === 'success' ? 'var(--accent)' : h.status === 'error' ? '#fc8181' : '#f6c90e';
4951            const statusLabel = h.status === 'success' ? '✅' : h.status === 'error' ? '❌' : '⏳';
4952            return `<tr>
4953              <td style="padding:6px 10px;font-size:12px;color:var(--white)">${h.date}</td>
4954              <td style="padding:6px 10px;font-size:12px;text-align:center;color:var(--gray6)">${h.sites_scanned}</td>
4955              <td style="padding:6px 10px;font-size:12px;text-align:center;color:${h.coupons_found > 0 ? '#ffe45e' : 'var(--gray5)'}">${h.coupons_found > 0 ? '+' : ''}${h.coupons_found}</td>
4956              <td style="padding:6px 10px;font-size:12px;text-align:center;color:#fc8181">$${cost}</td>
4957              <td style="padding:6px 10px;text-align:center;color:${statusColor}">${statusLabel}</td>
4958            </tr>`;
4959          }).join('')}</tbody>
4960        </table>`;
4961      }
4962    }
4963  } catch(e) { /* non-critical */ }
4964
4965  // Job C: load store sweep stats
4966  try {
4967    const r = await fetch('/api/admin/sweep-status');
4968    const d = await r.json();
4969    document.getElementById('job-c-total').textContent = d.total ?? '—';
4970    document.getElementById('job-c-scanned-today').textContent = d.scanned_today ?? '—';
4971    document.getElementById('job-c-pending').textContent = d.pending ?? '—';
4972    document.getElementById('job-c-next').textContent = d.next_store ?? '—';
4973    document.getElementById('job-c-status').textContent = `${d.total ?? 0} חנויות`;
4974    const tbody = document.getElementById('job-c-tbody');
4975    const stores = d.stores || [];
4976    if (!stores.length) {
4977      tbody.innerHTML = '<tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:12px">אין נתונים</td></tr>';
4978    } else {
4979      tbody.innerHTML = stores.map((s, i) => {
4980        const scanned = s.last_scanned_at ? new Date(s.last_scanned_at).toLocaleStr
4980ing('he-IL') : '—';
4981        const isToday = s.last_scanned_at && new Date(s.last_scanned_at).toDateString() === new Date().toDateString();
4982        const total   = s.coupon_count != null ? `<b style="color:var(--accent)">${s.coupon_count}</b>` : '—';
4983        const before  = s.before  != null ? s.before  : '—';
4984        const newF    = s.new_found != null
4985          ? `<b style="color:${s.new_found > 0 ? '#ffe45e' : 'var(--gray5)'}">${s.new_found > 0 ? '+' : ''}${s.new_found}</b>`
4986          : '—';
4987        return `<tr>
4988          <td style="color:var(--gray5)">${i+1}</td>
4989          <td>${s.name}</td>
4990          <td style="font-size:12px;color:var(--gray5)">${scanned}</td>
4991          <td style="text-align:center">${total}</td>
4992          <td style="text-align:center;color:var(--gray5)">${before}</td>
4993          <td style="text-align:center">${newF}</td>
4994          <td><span style="color:${isToday ? 'var(--accent)' : '#f6c90e'};font-size:12px">${isToday ? '✅ נסרק היום' : '⏳ ממתין'}</span></td>
4995        </tr>`;
4996      }).join('');
4997    }
4998
4999    // Daily history
5000    const histEl = document.getElementById('job-c-history');
5001    const history = (d.history || []).slice().reverse(); // newest first
5002    if (histEl) {
5003      if (!history.length) {
5004        histEl.textContent = 'אין היסטוריה עדיין — תירשם לאחר הריצה הראשונה בשעה 02:00';
5005      } else {
5006        histEl.innerHTML = `<table style="width:100%;border-collapse:collapse">
5007          <thead><tr>
5008            <th style="text-align:right;padding:6px 10px;color:var(--gray5);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">תאריך</th>
5009            <th style="text-align:center;padding:6px 10px;color:var(--gray5);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">חנויות שנסרקו</th>
5010            <th style="text-align:center;padding:6px 10px;color:var(--accent);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">קופונים חדשים</th>
5011            <th style="text-align:center;padding:6px 10px;color:var(--gray5);font-weight:500;font-size:11px;border-bottom:1px solid var(--gray3)">סטטוס</th>
5012          </tr></thead>
5013          <tbody>${history.map(h => {
5014            const statusColor = h.status === 'success' ? 'var(--accent)' : h.status === 'error' ? '#fc8181' : '#f6c90e';
5015            const statusLabel = h.status === 'success' ? '✅' : h.status === 'error' ? '❌' : '⏳';
5016            return `<tr>
5017              <td style="padding:6px 10px;font-size:12px;color:var(--white)">${h.date}</td>
5018              <td style="padding:6px 10px;font-size:12px;text-align:center;color:var(--gray6)">${h.stores_scanned}</td>
5019              <td style="padding:6px 10px;font-size:12px;text-align:center;color:${h.coupons_found > 0 ? '#ffe45e' : 'var(--gray5)'}">${h.coupons_found > 0 ? '+' : ''}${h.coupons_found}</td>
5020              <td style="padding:6px 10px;text-align:center;color:${statusColor}">${statusLabel}</td>
5021            </tr>`;
5022          }).join('')}</tbody>
5023        </table>`;
5024      }
5025    }
5026  } catch(e) {
5027    console.error('[Job C]', e);
5028    document.getElementById('job-c-tbody').innerHTML = `<tr><td colspan="7" style="color:#fc8181;text-align:center;padding:12px">שגיאה: ${e.message || e}</td></tr>`;
5029  }
5030}
5031
5032// ── Analytics panel DOM mover ─────────────────────────────────────────────────
5033// Panels live in dashboard slot divs. When a sidebar analytics item is clicked
5034// the panel is physically moved to the dedicated view div (and back on leave).
5035const _ANALYTICS_PANEL_MAP = [
5036  { view:'analytics-usage', panel:'analytics-panel', slot:'analytics-panel-slot' },
5037  { view:'analytics-speed', panel:'speed-panel',     slot:'speed-panel-slot'     },
5038  { view:'analytics-popup',      panel:'popup-panel', slot:'popup-panel-slot' },
5039  { view:'analytics-extension',  panel:'ext-panel',  slot:'ext-panel-slot'   },
5040];
5041function _returnAnalyticsToDash() {
5042  _ANALYTICS_PANEL_MAP.forEach(({panel, slot}) => {
5043    const p=document.getElementById(panel), s=document.getElementById(slot);
5044    if (p && s && p.parentElement !== s) s.appendChild(p);
5045  });
5046}
5047
5048function setAdminView(btn,view){
5049  document.querySelectorAll('.sidebar-item').forEach(b=>b.classList.remove('on'));
5050  btn.classList.add('on');
5051
5052  const ALL_VIEWS = ['dashboard','coupons','sources','coupon-sites','automation-scans','web','instagram','newsletters','affiliate','blocked-sites','trusted-international','
5052stores','store-categories','pending','no-url','users','settings','logs','hide-mechanism','expired','search-anim','mole','minigames','debug','null-domain','orphan-coupons','analytics-usage','analytics-speed','analytics-popup','analytics-extension','analytics-buttons','store-pages-demo','home-demo','contact-messages','no-origin','coupon-preview','user-profiles','search-intel','demand-gaps','traffic-sources','geo-map','device-stats','js-errors'];
5053
5054  // Return any displaced analytics panels back to dashboard slots
5055  _returnAnalyticsToDash();
5056
5057  ALL_VIEWS.forEach(v=>{
5058    const el=document.getElementById('view-'+v);
5059    if(el)el.style.display=v===view?'block':'none';
5060  });
5061  _activeAdminView = view;
5062
5063  // For analytics views: move the panel node into the dedicated view div
5064  const aEntry = _ANALYTICS_PANEL_MAP.find(e=>e.view===view);
5065  if (aEntry) {
5066    const p = document.getElementById(aEntry.panel);
5067    const viewEl = document.getElementById('view-'+view);
5068    if (p && viewEl) viewEl.appendChild(p);
5069    _loadAnalyticsFromServer().then(() => {
5070      if (view==='analytics-usage')     { try{loadAnalytics();}catch(e){} }
5071      if (view==='analytics-speed')     { try{loadSpeedAnalytics();}catch(e){} }
5072      if (view==='analytics-popup')     { try{loadPopupAnalytics();}catch(e){} }
5073      if (view==='analytics-extension') { try{loadExtensionAnalytics();}catch(e){} }
5074    });
5075  }
5076  if (view==='user-profiles')   { try{loadUserProfiles();}catch(e){console.error(e)} }
5077  if (view==='search-intel')    { try{loadSearchIntel();}catch(e){console.error(e)} }
5078  if (view==='demand-gaps')     { try{loadDemandGaps();}catch(e){console.error(e)} }
5079  if (view==='traffic-sources') { try{loadTrafficSources();}catch(e){console.error(e)} }
5080  if (view==='geo-map')         { try{loadGeoMap();}catch(e){console.error(e)} }
5081  if (view==='device-stats')    { try{loadDeviceStats();}catch(e){console.error(e)} }
5082  if (view==='js-errors')       { try{loadJsErrors();}catch(e){console.error(e)} }
5083  if (view==='analytics-buttons') { try{loadButtonClickAnalytics();}catch(e){} }
5084
5085  if(view==='logs')           loadLogs();
5086  if(view==='sources')        { initSourcesView(); loadUserbotStatus(); }
5087  if(view==='coupon-sites')       loadCouponSites();
5088  if(view==='automation-scans')   loadAutomationScans();
5089  if(view==='web')            initWebView();
5090  if(view==='instagram')      initInstagramView();
5091  if(view==='users')          _loadUsers();
5092  if(view==='pending')        loadPendingView();
5093  if(view==='newsletters')    loadNewsletterStores();
5094  if(view==='affiliate')      _affRender();
5095  if(view==='no-url')         loadNoUrlCoupons();
5096  if(view==='expired')        renderExpiredDemo();
5097  if(view==='mole')           wamStart();
5098  if(view==='search-anim')    _mgInitTips();
5099  if(view==='minigames')      demoGameStart();
5100  if(view==='debug')          initDebugView();
5101  if(view==='stores')         loadStoresView();
5102  if(view==='null-domain')    loadNullDomainStores();
5103  if(view==='no-origin')      loadNoOriginStores();
5104  if(view==='coupon-preview') loadCouponPreview();
5105  if(view==='orphan-coupons') loadOrphanCoupons();
5106  if(view==='blocked-sites')       loadBlockedSites();
5107  if(view==='trusted-international') loadTrustedInternational();
5108  // Update mobile title and close drawer
5109  const titleEl = document.getElementById('mob-view-title');
5110  if (titleEl) titleEl.textContent = _VIEW_TITLES[view] || view;
5111  closeMobMenu();
5112}
5113
5114// ── Hide Mechanism view JS ────────────────────────────────────────────────────
5115// 1 — Slide Banner: green strip lifts up to reveal code
5116function hmRevealStrip(el, code) {
5117  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5118  el.dataset.r = '1';
5119  const strip = el.querySelector('.hm-strip');
5120  strip.style.transform = 'translateY(-110%)';
5121  strip.style.opacity = '0';
5122  strip.style.pointerEvents = 'none';
5123  navigator.clipboard?.writeText(code).catch(()=>{});
5124  setTimeout(() => showToast('✓ הועתק!'), 200);
5125}
5126
5127// 2 — Blur Focus: heavy blur → hover partially focuses → click fully reveals
5128function hmRevealBlurFocus(el, code) {
5129  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5130  el.dataset.r = '1';
5131  const blurEl = el.querySelector('.hm-blur-code');
5132  el.classList.remove('hm-blur-wrap2'); // stop hover rule
5133  blurEl.style.filter = 'blur(0)';
5134  el.style.borderColor = 'rgba(0,232,122,.5)';
5135  el.style.cursor = 'pointer';
5136  navigator.clipboard?.writeText(code).catch(()=>{});
5137  showToast('✓ הועתק!');
5138}
5139
5140// 3 — Split Doors: two panels slide apart like elevator doors
5141function hmRevealDoors(el, code) {
5142  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5143  el.dataset.r = '1';
5144  el.querySelector('.hm-door-l').style.transform = 'translateX(-101%)';
5145  el.querySelector('.hm-door-r').style.transform = 'translateX(101%)';
5146  navigator.clipboard?.writeText(code).catch(()=>{});
5147  setTimeout(() => showToast('✓ הועתק!'), 220);
5148}
5149
5150// 4 — Night Vision: scanline overlay fades out
5151function hmRevealScan(el, code) {
5152  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5153  el.dataset.r = '1';
5154  const ov = el.querySelector('.hm-scan-overlay');
5155  ov.style.opacity = '0';
5156  navigator.clipboard?.writeText(code).catch(()=>{});
5157  setTimeout(() => showToast('✓ הועתק!'), 180);
5158}
5159
5160// 5 — Stamp Lift: diagonal stamp rotates and flies off
5161function hmRevealStamp(el, code) {
5162  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5163  el.dataset.r = '1';
5164  const stamp = el.querySelector('.hm-stamp');
5165  stamp.style.animation = 'hm-stamp-out .5s cubic-bezier(.4,0,.2,1) forwards';
5166  el.querySelector('.hm-code-box').style.borderColor = 'rgba(0,232,122,.4)';
5167  navigator.clipboard?.writeText(code).catch(()=>{});
5168  setTimeout(() => showToast('✓ הועתק!'), 300);
5169}
5170
5171// 6 — Eye Toggle: password-style eye icon reveals code
5172function hmRevealEye(el, code) {
5173  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5174  el.dataset.r = '1';
5175  el.querySelector('.hm-eye-hidden').style.display = 'none';
5176  const rev = el.querySelector('.hm-eye-revealed');
5177  rev.style.display = 'block';
5178  el.classList.remove('cc-highlight'); void el.offsetWidth; el.classList.add('cc-highlight');
5179  const eye = el.querySelector('.hm-eye-btn');
5180  eye.style.color = 'var(--accent)';
5181  // swap to eye-off icon
5182  eye.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
5183  navigator.clipboard?.writeText(code).catch(()=>{});
5184  showToast('✓ הועתק!');
5185}
5186
5187// Button-style demos — reveal on click with full-field pop
5188function hmRevealBtn(el, code) {
5189  if (el.dataset.r) { navigator.clipboard?.writeText(code).catch(()=>{}); showToast('✓ הועתק!'); return; }
5190  el.dataset.r = '1';
5191  // Solid Fill: switch to dark bg + green border so text is readable
5192  if (el.classList.contains('hm-btn-solid')) {
5193    el.style.background = 'var(--gray2)';
5194    el.style.border = '1px solid rgba(0,232,122,0.5)';
5195    el.style.filter = '';
5196  }
5197  const masked = el.querySelector('.hm-btn-masked');
5198  masked.innerHTML = `<span style="color:#00e87a;font-weight:800;font-size:16px;letter-spacing:2px;font-family:monospace">${code}</span>`;
5199  const eye = el.querySelector('.hm-btn-eye');
5200  if (eye) eye.remove();
5201  // Full-field green pop
5202  el.classList.remove('cc-highlight'); void el.offsetWidth; el.classList.add('cc-highlight');
5203  navigator.clipboard?.writeText(code).catch(()=>{});
5204  showToast('✓ הועתק!');
5205}
5206
5207// Shine effects — play on click, reset then re-apply class
5208// BOX_CLASSES: applied to the .cc-code container (wrapper-level)
5209// Everything else goes on the <span> text node
5210function hmShinePlay(n) {
5211  const box = document.getElementById('hs'+n);
5212  const txt = document.getElementById('hs'+n+'-t');
5213  if (!box || !txt) return;
5214  // Reset
5215  txt.className = '';
5216  box.className = 'cc-code';
5217  void txt.offsetWidth; // force reflow
5218  // Box-level classes (wrapper animation)
5219  const BOX_CLASSES = { 3:'hm-shine-3-wrap', 10:'hm-shine-10-wrap' };
5220  if (BOX_CLASSES[n]) box.classList.add(BOX_CLASSES[n]);
5221  else txt.classList.add('hm-shine-'+n);
5222}
5223
5224// ── Minigames view JS ─────────────────────────────────────────────────────────
5225let _mgTipIdx = 0, _mgTipTimer = null, _mgLogTimer = null, _mgLogStart = null;
5226const _MG_TIPS = [
5227  { icon:'💳', text:'כרטיסי אשראי מסוימים נותנים <strong>קאשבק נוסף</strong> על קנייה עם קוד קופון — תמיד שווה לבדוק!' },
5228  { icon:'🛒', text:'השאר מוצרים בעגלה יום-יומיים — הרבה חנויות שולחות <strong>קוד הנחה</strong> בדוא"ל כדי לגמור את הקנייה.' },
5229  { icon:'📱', text:'אפליקציות של חנויות ישראליות כמו <strong>KSP ו-Bug</strong> מציעות לפעמים מבצעים בלעדיים לאפליקציה.' },
5230  { icon:'⏰', text:'<strong>שישי בצהריים</strong> הוא הזמן הכי טוב למצוא קופונים ישראלים — רוב החנויות מפרסמות לסוף השבוע.' },
5231  { icon:'🔔', text:'מעקב אחרי ערוצי טלגרם של אינפלואנסרים ישראלים מניב לפעמים קופונים <strong>לא מפורסמים</strong>.' },
5232];
5233function _mgInitTips() {
5234  _mgTipIdx = 0;
5235  if (_mgTipTimer) clearInterval(_mgTipTimer);
5236  _mgTipTimer = setInterval(() => { _mgTipIdx = (_mgTipIdx + 1) % _MG_TIPS.length; _mgShowTip(_mgTipIdx); }, 4000);
5237  _mgShowTip(0);
5238  // log timer
5239  if (_mgLogTimer) clearInterval(_mgLogTimer);
5240  _mgLogStart = Date.now();
5241  _mgLogTimer = setInterval(() => {
5242    const el = document.getElementById('mg-log-timer');
5243    if (el) el.textContent = ((Date.now() - _mgLogStart) / 1000).toFixed(1) + 's';
5244  }, 100);
5245}
5246function _mgShowTip(i) {
5247  const icon = document.getElementById('mg-tip-icon');
5248  const text = document.getElementById('mg-tip-text');
5249  const bar  = document.getElementById('mg-tip-bar');
5250  if (!icon || !text) return;
5251  icon.textContent = _MG_TIPS[i].icon;
5252  text.innerHTML   = _MG_TIPS[i].text;
5253  for (let j = 0; j < 5; j++) {
5254    const d = document.getElementById('mg-td' + j);
5255    if (d) d.style.background = j === i ? 'var(--accent)' : 'var(--gray3)';
5256  }
5257  if (bar) { bar.style.animation = 'none'; requestAnimationFrame(() => { bar.style.animation = 'mg-progress 4s linear forwards'; }); }
5258}
5259function mgReplayLog() {
5260  const lines = document.querySelectorAll('#mg-log1 .mg-log-line');
5261  lines.forEach(l => { l.style.opacity = 0; l.style.animation = 'none'; });
5262  _mgLogStart = Date.now();
5263  requestAnimationFrame(() => { lines.forEach(l => { l.style.animation = ''; }); });
5264}
5265
5266// ── Demo game (real mini-game preview) ───────────────────────────────────────
5267let _demoTipInterval = null, _demoTipIdx = -1;
5268function demoGameStart() {
5269  const skelGrid = document.getElementById('demo-skel-grid');
5270  if (skelGrid) skelGrid.innerHTML = Array(6).fill(0).map(_skelCard).join('');
5271  _demoTipIdx = _randomTipIdx();
5272  _demoShowTip(_demoTipIdx);
5273  clearInterval(_demoTipInterval);
5274  _demoTipInterval = setInterval(() => { _demoTipIdx = _randomTipIdx(); _demoShowTip(_demoTipIdx); }, 16000);
5275}
5276function demoGameStop() {
5277  clearInterval(_demoTipInterval); _demoTipInterval = null;
5278  const g = document.getElementById('demo-skel-grid');
5279  if (g) g.innerHTML = '';
5280  const icon = document.getElementById('demo-tip-icon');
5281  const text = document.getElementById('demo-tip-text');
5282  const bar  = document.getElementById('demo-tip-bar');
5283  if (icon) icon.textContent = '💡';
5284  if (text) text.textContent = 'לחץ הפעל כדי לראות את המשחק';
5285  if (bar)  { bar.style.transition = 'none'; bar.style.width = '0%'; }
5286}
5287function _demoShowTip(i) {
5288  const icon = document.getElementById('demo-tip-icon');
5289  const text = document.getElementById('demo-tip-text');
5290  const bar  = document.getElementById('demo-tip-bar');
5291  if (!icon || !text) return;
5292  icon.textContent = _TIPS[i].icon;
5293  text.textContent = _TIPS[i].text;
5294  if (bar) {
5295    bar.style.transition = 'none'; bar.style.width = '0%';
5296    requestAnimationFrame(() => requestAnimationFrame(() => {
5297      bar.style.transition = 'width 16s linear'; bar.style.width = '100%';
5298    }));
5299  }
5300}
5301
5302// ── Whack-a-Mole game ────────────────────────────────────────────────────────
5303let _wamInterval = null;
5304const _WAM_EMOJIS = ['🤑','💸','🏷️','🎁','💰','🛍️','🎫','💎'];
5305const _WAM_COLS = 4, _WAM_ROWS = 2, _WAM_TOTAL = _WAM_COLS * _WAM_ROWS;
5306let _wamScore = 0, _wamActive = -1, _wamCells = [];
5307
5308function wamStart() {
5309  wamStop();
5310  _wamScore = 0;
5311  const scoreEl = document.getElementById('wam-view-score');
5312  const grid    = document.getElementById('wam-view-grid');
5313  if (!grid) return;
5314  if (scoreEl) scoreEl.textContent = '0 נקודות';
5315  grid.innerHTML = '';
5316  _wamCells = Array.from({ length: _WAM_TOTAL }, (_, i) => {
5317    const btn = document.createElement('button');
5318    btn.style.cssText = 'font-size:32px;background:var(--gray3);border:2px solid var(--gray4);border-radius:12px;padding:10px;cursor:pointer;transition:transform 0.1s;width:70px;height:70px;display:flex;align-items:center;justify-content:center;';
5319    btn.textContent = '🕳️';
5320    btn.onclick = () => {
5321      if (_wamActive === i) {
5322        _wamScore++;
5323        if (scoreEl) scoreEl.textContent = _wamScore + ' נקודות';
5324        btn.style.transform = 'scale(1.3)';
5325        setTimeout(() => { btn.style.transform = ''; }, 150);
5326        _wamActive = -1;
5327        _wamCells.forEach(c => { c.textContent = '🕳️'; c.style.borderColor = 'var(--gray4)'; });
5328      }
5329    };
5330    grid.appendChild(btn);
5331    return btn;
5332  });
5333  _wamInterval = setInterval(() => {
5334    if (_wamActive >= 0) { _wamCells[_wamActive].textContent = '🕳️'; _wamCells[_wamActive].style.borderColor = 'var(--gray4)'; }
5335    _wamActive = Math.floor(Math.random() * _WAM_TOTAL);
5336    _wamCells[_wamActive].textContent = _WAM_EMOJIS[Math.floor(Math.random() * _WAM_EMOJIS.length)];
5337    _wamCells[_wamActive].style.borderColor = 'var(--accent)';
5338  }, 900);
5339}
5340
5341function wamStop() {
5342  if (_wamInterval) { clearInterval(_wamInterval); _wamInterval = null; }
5343  _wamActive = -1;
5344  _wamCells.forEach(c => { c.textContent = '🕳️'; c.style.borderColor = 'var(--gray4)'; });
5345}
5346
5347function openMobMenu() {
5348  document.getElementById('admin-sidebar').classList.add('mob-open');
5349  document.getElementById('mob-backdrop').classList.add('show');
5350  document.body.style.overflow = 'hidden';
5351}
5352
5353function closeMobMenu() {
5354  document.getElementById('admin-sidebar').classList.remove('mob-open');
5355  document.getElementById('mob-backdrop').classList.remove('show');
5356  document.body.style.overflow = '';
5357}
5358
5359// ── Feature 1: Pending Review view ───────────────────────────────────────────
5360
5361let _pendingGeo = 'all';
5362let _pendingAllData = [];
5363
5364function _setPendingGeo(geo, btn) {
5365  _pendingGeo = geo;
5366  ['all','il','int'].forEach(g => {
5367    const el = document.getElementById(`pending-geo-${g}`);
5368    if (el) { el.style.background = g===geo ? 'var(--accent)' : ''; el.style.color = g===geo ? '#000' : ''; el.classList.toggle('btn-ghost', g!==geo); }
5369  });
5370  _renderPendingTable();
5371}
5372
5373function _isPendingIL(domain) {
5374  return domain.endsWith('.co.il') || domain.endsWith('.il') ||
5375         ['yemama','10bis','shufersal','ksp','castro','golf','bug','jemix','couponet','icoupons',
5376          'secretcoupon','israelicoupons','israelcoupon','groo','buyme','influ','aliclub','pepper',
5377          'dealtas','dealhunter','lastprice','mishloha','gett','isrotel','ivory','zer4u'].some(k => domain.includes(k));
5378}
5379
5380function _getPendingSourceType(domain) {
5381  const d = domain.toLowerCase();
5382  if (d.includes('t.me') || d.includes('telegram') || d.includes('tg.'))
5383    return { icon:'📣', label:'טלגרם', color:'#7289da', bg:'rgba(114,137,218,0.12)' };
5384  if (['coupon','deal','promo','discount','code','offer','קופון','מבצע','חסכון','saving'].some(k => d.includes(k)))
5385    return { icon:'🏷️', label:'אתר קופונים', color:'var(--accent2)', bg:'rgba(255,228,94,0.1)' };
5386  if (d.endsWith('.co.il') || d.endsWith('.il') || _isPendingIL(d))
5387    return { icon:'🇮🇱', label:'אתר ישראלי', color:'var(--accent)', bg:'rgba(0,232,122,0.08)' };
5388  if (['blog','news','article','post','review','magazine'].some(k => d.includes(k)))
5389    return { icon:'📰', label:'בלוג / מדיה', color:'var(--gray6)', bg:'rgba(150,150,150,0.1)' };
5390  if (['facebook','instagram','twitter','reddit','tiktok','youtube','pinterest','whatsapp'].some(k => d.includes(k)))
5391    return { icon:'📱', label:'רשת חברתית', color:'#a78bfa', bg:'rgba(167,139,250,0.1)' };
5392  if (['amazon','ebay','aliexpress','rakuten','walmart','target'].some(k => d.includes(k)))
5393    return { icon:'🛒', label:'חנות בינלאומית', color:'#f97316', bg:'rgba(249,115,22,0.1)' };
5394  return { icon:'❓', label:'לא מוכר', color:'var(--gray4)', bg:'rgba(100,100,100,0.08)' };
5395}
5396
5397function _renderPendingTable() {
5398  const tbody = document.getElementById('pending-tbody');
5399  const titleEl = document.getElementById('pending-table-title');
5400  if (!tbody) return;
5401
5402  const filtered = _pendingAllData.filter(d => {
5403    if (_pendingGeo === 'il')  return _isPendingIL(d.domain);
5404    if (_pendingGeo === 'int') return !_isPendingIL(d.domain);
5405    return true;
5406  });
5407
5408  const total = _pendingAllData.reduce((s, d) => s + d.count, 0);
5409  const filtTotal = filtered.reduce((s, d) => s + d.count, 0);
5410  if (titleEl) titleEl.textContent = `${filtered.length} דומיינים · ${filtTotal} קופונים לבדיקה${_pendingGeo!=='all' ? ` (מתוך ${_pendingAllData.length} / ${total})` : ''}`;
5411
5412  if (!filtered.length) {
5413    tbody.innerHTML = `<tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:24px">אין קופונים לבדיקה 🎉</td></tr>`;
5414    return;
5415  }
5416
5417  tbody.innerHTML = filtered.map((d, idx) => {
5418    const safeDomain = d.domain.replace(/'/g, "\\'");
5419    const href = d.url || `https://${d.domain}`;
5420    const codesHtml = (d.sample_codes || []).map(c =>
5421      `<span style="font-family:monospace;font-size:11px;background:var(--gray3);border:1px dashed var(--gray4);border-radius:4px;padding:2px 7px;letter-spacing:1px;color:var(--gray6);direction:ltr;display:inline-block;margin:1px">${c}</span>`
5422    ).join(' ');
5423    const storesHtml = (d.stores || []).slice(0,4).join(', ');
5424    const ilFlag = _isPendingIL(d.domain) ? '🇮🇱 ' : '🌍 ';
5425    const srcType = _getPendingSourceType(d.domain);
5426    return `<tr>
5427      <td style="color:var(--gray5);font-size:11px;text-align:center">${idx + 1}</td>
5428      <td class="td-store"><a href="${href}" target="_blank" style="color:var(--accent);text-decoration:none">${ilFlag}${d.domain}</a></td>
5429      <td style="text-align:center"><span style="display:inline-flex;align-items:center;gap:4px;background:${srcType.bg};color:${srcType.color};border-radius:20px;padding:2px 9px;font-size:11px;white-space:nowrap">${srcType.icon} ${srcType.label}</span></td>
5430      <td style="text-align:center;font-weight:600;color:var(--white)">${d.count}</td>
5431      <td style="font-size:12px;color:var(--gray6)">${storesHtml || '—'}</td>
5432      <td>${codesHtml || '—'}</td>
5433      <td><div class="td-actions">
5434        <button class="td-btn" style="color:#00e87a" onclick="approvePendingSource('${safeDomain}',this)">✅ אשר</button>
5435        <button class="td-btn danger" onclick="rejectPendingSource('${safeDomain}',this)">🗑 דחה</button>
5436      </div></td>
5437    </tr>`;
5438  }).join('');
5439}
5440
5441async function loadPendingView() {
5442  const tbody = document.getElementById('pending-tbody');
5443  const titleEl = document.getElementById('pending-table-title');
5444  if (!tbody) return;
5445  tbody.innerHTML = `<tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:24px">🔍 סורק מקורות חדשים...</td></tr>`;
5446  // Auto-backfill silently — reclassify any unknowns from unapproved domains
5447  try { await fetch('/api/admin/backfill-pending', { method: 'POST' }); } catch(_) {}
5448  tbody.innerHTML = `<tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:24px">טוען...</td></tr>`;
5449  try {
5450    const res = await fetch('/api/admin/pending-sources');
5451    _pendingAllData = await res.json();
5452    // Update sidebar badge with total
5453    const badge = document.getElementById('src-pending-count');
5454    const total = _pendingAllData.reduce((s, d) => s + d.count, 0);
5455    if (badge) badge.textContent = total || '0';
5456    _renderPendingTable();
5457  } catch(e) {
5458    tbody.innerHTML = `<tr><td colspan="7" style="color:#ff6060;text-align:center;padding:24px">שגיאה בטעינת נתונים</td></tr>`;
5459  }
5460}
5461
5462async function approvePendingSource(domain, btn) {
5463  btn.disabled = true;
5464  btn.textContent = '...';
5465  try {
5466    const res = await fetch('/api/admin/approve-source', {
5467      method: 'POST',
5468      headers: {'Content-Type':'application/json'},
5469      body: JSON.stringify({domain})
5470    });
5471    const d = await res.json();
5472    showToast(`✅ אושרו ${d.updated} קופונים מ-${domain}`);
5473    loadPendingView();
5474  } catch(e) {
5475    showToast('❌ שגיאה באישור מקור');
5476    btn.disabled = false;
5477    btn.textContent = '✅ אשר';
5478  }
5479}
5480
5481async function rejectPendingSource(domain, btn) {
5482  if (!confirm(`למחוק את כל הקופונים מ-${domain}?`)) return;
5483  btn.disabled = true;
5484  btn.textContent = '...';
5485  try {
5486    const res = await fetch('/api/admin/reject-source', {
5487      method: 'POST',
5488      headers: {'Content-Type':'application/json'},
5489      body: JSON.stringify({domain})
5490    });
5491    const d = await res.json();
5492    showToast(`🗑 נמחקו ${d.deleted} קופונים מ-${domain}`);
5493    loadPendingView();
5494  } catch(e) {
5495    showToast('❌ שגיאה בדחיית מקור');
5496    btn.disabled = false;
5497    btn.textContent = '🗑 דחה';
5498  }
5499}
5500
5501// ── No-URL coupons view ──────────────────────────────────────────────────────
5502
5503let _noUrlData     = [];
5504let _noUrlPage     = 0;
5505const _noUrlPerPg  = 25;
5506let _noUrlGeo      = 'all';       // 'all' | 'il' | 'intl'
5507let _noUrlExpanded = new Set();   // set of store names that are expanded
5508
5509// Detect IL vs INTL from source URLs of the store's coupons
5510function _noUrlIsIL(r) {
5511  for (const c of (r.coupons || [])) {
5512    const src = c.source || '';
5513    const url = src.includes('|') ? src.split('|').slice(1).join('|') : src;
5514    try {
5515      const host = url.startsWith('http') ? new URL(url).hostname : url;
5516      if (/\.co\.il|\.org\.il|\.net\.il|\.ac\.il|\.gov\.il/.test(host)) return true;
5517    } catch(e) {}
5518    if (/\.co\.il|\.org\.il|\.net\.il/.test(src)) return true;
5519  }
5520  return false;
5521}
5522
5523function _noUrlFilteredData() {
5524  if (_noUrlGeo === 'il')   return _noUrlData.filter(r => _noUrlIsIL(r));
5525  if (_noUrlGeo === 'intl') return _noUrlData.filter(r => !_noUrlIsIL(r));
5526  return _noUrlData;
5527}
5528
5529// Extract the first real http(s) URL from any source format.
5530// Handles: [secondary]|url, /Web: url, Telegram:name|url, bare url, etc.
5531function _noUrlParseUrl(src) {
5532  if (!src) return '';
5533  // Telegram — t.me link
5534  const tg = src.match(/https?:\/\/t\.me\/[^\s\]|]+/);
5535  if (tg) return tg[0];
5536  // Any other https?:// URL in the string
5537  const m = src.match(/https?:\/\/[^\s\]|,]+/);
5538  return m ? m[0].replace(/[)\]>'"]+$/, '') : '';
5539}
5540
5541// Aggregator domains — source URLs from these are NOT the target store
5542const _AGGREGATOR_HOSTS = new Set([
5543  'dealspotr.com','hotdeals.com','moneysaveme.com','promocode.co.il',
5544  'coupert.com','couponfollow.com','rabato.com','israelcoupon.co.il',
5545  'tiendeo.co.il','retailmenot.com','groupon.com','slickdeals.net',
5546  'coupons.com','vouchercodes.co.uk','picodi.com','zap.co.il',
5547]);
5548
5549// Known aggregator patterns: extract store slug from URL path → suggest real domain
5550const _AGGREGATOR_PATTERNS = [
5551  { re: /dealspotr\.com\/(?:#!\/)?@?([^/?#\s]+)/i,          suffix: '.com'    },
5552  { re: /hotdeals\.com\/(?:coupons|stores?)\/([^/?#\s]+)/i,  suffix: '.com'    },
5553  { re: /moneysaveme\.com\/store\/([^/?#\s]+)/i,             suffix: '.com'    },
5554  { re: /promocode\.co\.il\/(?:stores?\/)?([^/?#\s]+)/i,     suffix: '.co.il'  },
5555  { re: /coupert\.com\/(?:[a-z]{2}\/)?([^/?#\s]+)/i,         suffix: '.com'    },
5556  { re: /couponfollow\.com\/([^/?#\s]+)-coupons/i,            suffix: '.com'    },
5557  { re: /rabato\.com\/(?:il\/)?([^/?#\s]+)/i,                suffix: '.com'    },
5558  { re: /israelcoupon\.co\.il\/(?:coupon|store)\/([^/?#\s]+)/i, suffix: '.co.il' },
5559];
5560
5561function _noUrlGuessDomain(coupons) {
5562  // 1. If source URLs are direct store pages (not aggregators), use the most common host
5563  const directCount = {};
5564  for (const c of (coupons || [])) {
5565    const url = _noUrlParseUrl(c.source || '');
5566    if (!url) continue;
5567    try {
5568      const host = new URL(url).hostname.replace(/^www\./, '');
5569      if (host && !_AGGREGATOR_HOSTS.has(host) && !host.includes('t.me') && host.includes('.'))
5570        directCount[host] = (directCount[host] || 0) + 1;
5571    } catch(e) {}
5572  }
5573  const top = Object.entries(directCount).sort((a,b) => b[1]-a[1])[0];
5574  if (top) return top[0];   // ← direct source domain wins (e.g. jemix.co.il for Pandezzz)
5575
5576  // 2. Extract store slug from known aggregator URL patterns
5577  for (const c of (coupons || [])) {
5578    const url = _noUrlParseUrl(c.source || '');
5579    for (const { re, suffix } of _AGGREGATOR_PATTERNS) {
5580      const m = url.match(re);
5581      if (m && m[1]) {
5582        const slug = m[1].replace(/-coupons?$/i,'').replace(/-/g,'').toLowerCase();
5583        if (slug.length > 2) return slug + suffix;
5584      }
5585    }
5586  }
5587  return '';
5588}
5589
5590function _noUrlSrcDisplay(src) {
5591  if (!src) return '<span style="color:var(--gray4)">—</span>';
5592  const url = _noUrlParseUrl(src);
5593  if (!url) {
5594    if (/^Telegram:/i.test(src))
5595      return `<span style="color:#29b6f6;font-size:11px">📨 ${src.replace(/^Telegram:/i,'').split('|')[0].trim()}</span>`;
5596    return `<span style="color:var(--gray5);font-size:11px">${src.slice(0,60)}</span>`;
5597  }
5598  const isTg = url.includes('t.me/');
5599  let domain = url;
5600  try { domain = new URL(url).hostname.replace(/^www\./, ''); } catch(e) {}
5601  return `<a href="${url}" target="_blank" title="${url}" style="color:${isTg?'#29b6f6':'var(--accent)'};font-size:11px;white-space:nowrap">${isTg?'📨 ':''}${domain}</a>`;
5602}
5603
5604async function loadNullDomainStores() {
5605  const tbody = document.getElementById('nulldomain-tbody');
5606  if (!tbody) return;
5607  tbody.innerHTML = `<tr><td colspan="9" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
5608  const selAll = document.getElementById('nd-select-all');
5609  const delBtn = document.getElementById('nd-delete-btn');
5610  if (selAll) selAll.checked = false;
5611  if (delBtn) delBtn.style.display = 'none';
5612  try {
5613    const res  = await fetch('/api/admin/stores/null-domain');
5614    const data = await res.json();
5615    const badge = document.getElementById('src-nulldomain-count');
5616    if (badge) badge.textContent = data.length || '0';
5617    if (!data.length) {
5618      tbody.innerHTML = `<tr><td colspan="9" style="text-align:center;color:var(--accent);padding:28px">✓ כל החנויות מכילות דומיין</td></tr>`;
5619      return;
5620    }
5621    tbody.innerHTML = data.map((s, i) => {
5622      const aliases   = (s.aliases || []).join(', ') || '—';
5623      const nameVal   = s.name.replace(/"/g,'&quot;');
5624      const heVal     = (s.store_he || '').replace(/"/g,'&quot;');
5625      return `<tr data-store-id="${s.id}">
5626        <td style="text-align:center">
5627          <input type="checkbox" class="nd-cb" data-id="${s.id}" onchange="_ndCbChanged()"
5628            style="cursor:pointer;width:15px;height:15px;accent-color:var(--accent)">
5629        </td>
5630        <td style="color:var(--gray5);font-size:12px">${i + 1}</td>
5631        <td>
5632          <input id="nd-name-${s.id}" value="${nameVal}" placeholder="שם באנגלית" dir="ltr"
5633            style="width:100%;background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);outline:none">
5634        </td>
5635        <td>
5636          <input id="nd-he-${s.id}" value="${heVal}" placeholder="שם בעברית" dir="rtl"
5637            style="width:100%;background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);outline:none">
5638        </td>
5639        <td style="text-align:center;color:var(--gray5)">${s.coupon_count || 0}</td>
5640        <td style="font-size:12px;color:var(--gray5);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${aliases}">${aliases}</td>
5641        <td style="font-size:11px;color:var(--gray5);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${(s.sample_source||'').replace(/"/g,'&quot;')}">${s.sample_source ? _ndFormatSource(s.sample_source) : '—'}</td>
5642        <td>
5643          <input id="nd-domain-${s.id}" type="text" placeholder="castro.com" dir="ltr"
5644            style="width:100%;background:var(--gray2);border:1px solid #7c2d12;border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);outline:none">
5645        </td>
5646        <td style="display:flex;gap:5px;align-items:center">
5647          <button onclick="_ndSave('${s.id}')"
5648            style="padding:5px 10px;background:var(--accent);color:#000;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap">
5649            שמור
5650          </button>
5651          <button onclick="_ndShowCoupons('${s.id}','${s.name.replace(/'/g,"\\'")}')"
5652            style="padding:5px 8px;background:var(--gray2);color:var(--gray6);border:1px solid var(--gray3);border-radius:6px;font-size:12px;cursor:pointer">
5653            📋
5654          </button>
5655        </td>
5656      </tr>`;
5657    }).join('');
5658  } catch(e) {
5659    tbody.innerHTML = `<tr><td colspan="9" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
5660  }
5661}
5662
5663function _ndFormatSource(src) {
5664  if (!src) return '—';
5665  if (src.startsWith('Newsletter:')) return '📧 ' + src.slice(11);
5666  if (src.includes('t.me/')) {
5667    const m = src.match(/t\.me\/([^/\s?]+)/);
5668    return '📢 ' + (m ? m[1] : 'Telegram');
5669  }
5670  try { return '🌐 ' + new URL(src).hostname; } catch(_) {}
5671  return src.length > 40 ? src.slice(0, 40) + '…' : src;
5672}
5673
5674function _ndCbChanged() {
5675  const cbs = document.querySelectorAll('.nd-cb');
5676  const checked = document.querySelectorAll('.nd-cb:checked');
5677  const selAll = document.getElementById('nd-select-all');
5678  const delBtn = document.getElementById('nd-delete-btn');
5679  if (selAll) selAll.checked = cbs.length > 0 && checked.length === cbs.length;
5680  if (delBtn) {
5681    delBtn.style.display = checked.length > 0 ? '' : 'none';
5682    delBtn.textContent = `🗑 מחק נבחרים (${checked.length})`;
5683  }
5684}
5685
5686function _ndToggleAll(checked) {
5687  document.querySelectorAll('.nd-cb').forEach(cb => cb.checked = checked);
5688  _ndCbChanged();
5689}
5690
5691async function _ndDeleteSelected() {
5692  const checked = document.querySelectorAll('.nd-cb:checked');
5693  if (!checked.length) return;
5694  const ids = Array.from(checked).map(cb => cb.dataset.id);
5695  if (!confirm(`למחוק ${ids.length} חנויות וכל הקופונים שלהן?`)) return;
5696  const delBtn = document.getElementById('nd-delete-btn');
5697  if (delBtn) delBtn.disabled = true;
5698  try {
5699    const r = await fetch('/api/admin/stores/bulk-delete', {
5700      method: 'DELETE',
5701      headers: {'Content-Type':'application/json'},
5702      body: JSON.stringify({ store_ids: ids })
5703    });
5704    const d = await r.json();
5705    showToast(`🗑 נמחקו ${d.stores_deleted} חנויות ו-${d.coupons_deleted} קופונים`);
5706    loadNullDomainStores();
5707  } catch(e) {
5708    alert('שגיאה במחיקה');
5709    if (delBtn) delBtn.disabled = false;
5710  }
5711}
5712
5713let _storesData = [];
5714let _storesSortKey = 'name', _storesSortDir = 1, _storesPage = 0, _storesOriginFilterVal = 'all';
5715const _storesPerPage = 50;
5716
5717async function loadStoresView() {
5718  const tbody = document.getElementById('stores-tbody');
5719  if (!tbody) return;
5720  tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
5721  try {
5722    const res = await fetch('/api/admin/stores/all');
5723    _storesData = await res.json();
5724    const badge = document.getElementById('src-stores-count');
5725    if (badge) badge.textContent = _storesData.length;
5726    _storesPage = 0;
5727    _storesRender();
5728  } catch(e) {
5729    tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
5730  }
5731}
5732
5733function _storesFilter() {
5734  _storesPage = 0;
5735  _storesRender();
5736}
5737
5738function _storesSetSortKey(key) {
5739  _storesSortKey = key; _storesSortDir = 1; _storesPage = 0;
5740  const dirBtn = document.getElementById('stores-sort-dir-btn');
5741  if (dirBtn) dirBtn.textContent = '↑';
5742  _storesRender();
5743}
5744
5745function _storesToggleDir() {
5746  _storesSortDir *= -1; _storesPage = 0;
5747  const dirBtn = document.getElementById('stores-sort-dir-btn');
5748  if (dirBtn) dirBtn.textContent = _storesSortDir === 1 ? '↑' : '↓';
5749  _storesRender();
5750}
5751
5752function storesGoPage(p) { _storesPage = p; _storesRender(); }
5753
5754function _storesOriginFilter(val) {
5755  _storesOriginFilterVal = val;
5756  _storesPage = 0;
5757  ['all','il','int','null'].forEach(k => {
5758    const btn = document.getElementById('stores-origin-' + k);
5759    if (btn) { btn.style.background = k===val ? 'var(--accent)' : ''; btn.style.color = k===val ? '#000' : ''; }
5760  });
5761  _storesRender();
5762}
5763
5764function _storesRender() {
5765  const q = (document.getElementById('stores-search')?.value || '').toLowerCase();
5766  let rows = _storesData.slice();
5767  if (q) rows = rows.filter(s =>
5768    s.name.toLowerCase().includes(q) ||
5769    (s.store_he || '').includes(q) ||
5770    (s.domain || '').toLowerCase().includes(q) ||
5771    (s.aliases || []).join(' ').toLowerCase().includes(q)
5772  );
5773  if (_storesOriginFilterVal === 'il')   rows = rows.filter(s => s.origin === 'il');
5774  if (_storesOriginFilterVal === 'int')  rows = rows.filter(s => s.origin === 'int');
5775  if (_storesOriginFilterVal === 'null') rows = rows.filter(s => !s.origin);
5776
5777  rows.sort((a, b) => {
5778    let va, vb;
5779    if (_storesSortKey === 'name')    { va = a.name.toLowerCase(); vb = b.name.toLowerCase(); }
5780    else if (_storesSortKey === 'coupons') { va = a.coupon_count || 0; vb = b.coupon_count || 0; }
5781    else if (_storesSortKey === 'domain')  { va = (a.domain || '').toLowerCase(); vb = (b.domain || '').toLowerCase(); }
5782    if (va < vb) return -1 * _storesSortDir;
5783    if (va > vb) return  1 * _storesSortDir;
5784    return 0;
5785  });
5786
5787  const totalPages = Math.max(1, Math.ceil(rows.length / _storesPerPage));
5788  if (_storesPage >= totalPages) _storesPage = totalPages - 1;
5789  const slice = rows.slice(_storesPage * _storesPerPage, (_storesPage + 1) * _storesPerPage);
5790
5791  // pagination
5792  const pgDiv = document.getElementById('stores-pagination');
5793  if (pgDiv) {
5794    if (totalPages <= 1) { pgDiv.innerHTML = ''; } else {
5795      const nums = _pageNums(_storesPage, totalPages);
5796      const numBtns = nums.map(n => n === '…'
5797        ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
5798        : `<button class="td-btn${n-1===_storesPage?' page-active':''}" onclick="storesGoPage(${n-1})" style="min-width:32px">${n}</button>`
5799      ).join('');
5800      pgDiv.innerHTML = `
5801        <div style="display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 18px 14px;border-top:1px solid var(--gray3);flex-wrap:wrap">
5802          <button class="td-btn" onclick="storesGoPage(${_storesPage-1})" ${_storesPage===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
5803          ${numBtns}
5804          <button class="td-btn" onclick="storesGoPage(${_storesPage+1})" ${_storesPage>=totalPages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
5805        </div>`;
5806    }
5807  }
5808
5809  _renderStoresTable(slice);
5810}
5811
5812function _renderStoresTable(rows) {
5813  const tbody = document.getElementById('stores-tbody');
5814  if (!tbody) return;
5815  if (!rows.length) {
5816    tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:var(--gray5);padding:24px">לא נמצאו חנויות</td></tr>`;
5817    return;
5818  }
5819  tbody.innerHTML = rows.map((s, i) => {
5820    const aliases = (s.aliases || []).join(', ');
5821    const domainVal = s.domain || '';
5822    const hasDomain = !!domainVal;
5823    const logoUrl = s.logo_url || '';
5824    const logoCell = logoUrl
5825      ? `<span style="color:#00e87a;font-size:13px;font-weight:700">✓</span>`
5826      : `<span style="color:var(--gray5);font-size:13px">✗</span>`;
5827    const storeHeVal = (s.store_he || '').replace(/"/g,'&quot;');
5828    return `<tr data-sid="${s.id}">
5829      <td style="color:var(--gray5);font-size:12px">${_storesPage * _storesPerPage + i + 1}</td>
5830      <td>
5831        <input id="store-name-${s.id}" value="${s.name.replace(/"/g,'&quot;')}" data-orig="${s.name.replace(/"/g,'&quot;')}"
5832          style="width:100%;background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);direction:ltr">
5833      </td>
5834      <td>
5835        <input id="store-he-${s.id}" value="${storeHeVal}" placeholder="שם בעברית"
5836          style="width:100%;background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);direction:rtl">
5837      </td>
5838      <td style="text-align:center;color:var(--gray5)">${s.coupon_count || 0}</td>
5839      <td>
5840        <input id="store-domain-${s.id}" value="${domainVal}" placeholder="domain.com"
5841          style="width:100%;background:var(--gray2);border:1px solid ${hasDomain ? 'var(--gray3)' : '#7c2d12'};border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);direction:ltr">
5842      </td>
5843      <td>
5844        <input id="store-aliases-${s.id}" value="${aliases}" placeholder="alias1, alias2"
5845          style="width:100%;background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:5px 8px;font-size:12px;color:var(--white);direction:ltr">
5846      </td>
5847      <td style="text-align:center">${logoCell}</td>
5848      <td style="text-align:center">
5849        <select onchange="_storeSetOrigin('${s.id}',this.value)"
5850          style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:3px 5px;font-size:11px;color:var(--white);cursor:pointer;outline:none">
5851          <option value="" ${!s.origin?'selected':''}>❓</option>
5852          <option value="il" ${s.origin==='il'?'selected':''}>🇮🇱 IL</option>
5853          <option value="int" ${s.origin==='int'?'selected':''}>🌍 INT</option>
5854        </select>
5855      </td>
5856      <td>
5857        <div style="display:flex;gap:6px">
5858          <button class="btn-sm" onclick="_storeSave('${s.id}')"
5859            style="padding:5px 10px;font-size:11px;background:var(--accent);color:#000">שמור</button>
5860          <button class="btn-sm" onclick="_storeDelete('${s.id}','${s.name.replace(/'/g,"\\'")}',${ s.coupon_count||0},'${(s.domain||'').replace(/'/g,"\\'")}')"
5861            style="padding:5px 10px;font-size:11px;background:#fc818122;color:#fc8181;border:1px solid #fc818144">מחק</button>
5862        </div>
5863      </td>
5864    </tr>`;
5865  }).join('');
5866}
5867
5868async function _storeSave(storeId) {
5869  const nameInput    = document.getElementById('store-name-' + storeId);
5870  const storeHeInput = document.getElementById('store-he-' + storeId);
5871  const domainInput  = document.getElementById('store-domain-' + storeId);
5872  const aliasesInput = document.getElementById('store-aliases-' + storeId);
5873  if (!domainInput) return;
5874  const newName  = nameInput ? nameInput.value.trim() : null;
5875  const origName = nameInput ? nameInput.dataset.orig : null;
5876  const storeHe  = storeHeInput ? storeHeInput.value.trim() : null;
5877  const domain   = domainInput.value.trim().replace(/^https?:\/\//i,'').replace(/^www\d*\./i,'').split('/')[0].toLowerCase();
5878  const aliases  = aliasesInput ? aliasesInput.value.split(',').map(a=>a.trim()).filter(Boolean) : undefined;
5879  const btn = domainInput.closest('tr').querySelector('button');
5880  if (btn) { btn.disabled = true; btn.textContent = '...'; }
5881  // Language validation
5882  const _hasHebrew   = /[א-תװ-״]/.test(newName || '');
5883  const _heHasHebrew = /[א-תװ-״]/.test(storeHe || '');
5884  const _heHasLatin  = /[a-zA-Z]/.test(storeHe || '');
5885  if (_hasHebrew) { showToast('⚠️ שם חנות חייב להיות באנגלית בלבד'); if (btn) { btn.disabled=false; btn.textContent='שמור'; } return; }
5886  // Allow pure brand acronyms (H&M, BSTN) — only block mixing Hebrew + Latin
5887  if (_heHasLatin && _heHasHebrew) { showToast('⚠️ שם עברי לא יכול לערבב עברית ואנגלית'); if (btn) { btn.disabled=false; btn.textContent='שמור'; } return; }
5888  try {
5889    // Rename if name changed
5890    if (newName && newName !== origName) {
5891      const rr = await fetch(`/api/admin/stores/${storeId}/rename`, {
5892        method: 'POST',
5893        headers: {'Content-Type':'application/json'},
5894        body: JSON.stringify({ name: newName })
5895      });
5896      if (!rr.ok) {
5897        const errText = await rr.text().catch(()=>'');
5898        showToast(rr.status === 409 ? '⚠️ שם כבר קיים' : rr.status === 400 ? '⚠️ ' + (errText || 'שגיאה בשינוי שם') : 'שגיאה בשינוי שם');
5899        return;
5900      }
5901      if (nameInput) nameInput.dataset.orig = newName;
5902    }
5903    // Update domain + aliases + store_he
5904    const body = {};
5905    if (domain) body.domain = domain; else body.domain = null;
5906    if (aliases !== undefined) body.aliases = aliases;
5907    body.store_he = storeHe || null;
5908    const r = await fetch(`/api/admin/stores/${storeId}`, {
5909      method: 'PATCH',
5910      headers: {'Content-Type':'application/json'},
5911      body: JSON.stringify(body)
5912    });
5913    if (r.ok) {
5914      showToast('✓ נשמר');
5915      if (domainInput) domainInput.style.borderColor = domain ? 'var(--accent)' : '#7c2d12';
5916    } else {
5917      const errText = await r.text().catch(()=>'');
5918      showToast(r.status === 409 ? '⚠️ דומיין כבר שייך לחנות אחרת' : r.status === 400 ? '⚠️ ' + (errText || 'שגיאה') : 'שגיאה בשמירה');
5919    }
5920  } catch(e) {
5921    showToast('שגיאה בשמירה');
5922  } finally {
5923    if (btn) { btn.disabled = false; btn.textContent = 'שמור'; }
5924  }
5925}
5926
5927
5928function _showResultModal(icon, title, body) {
5929  document.getElementById('result-modal-icon').textContent  = icon;
5930  document.getElementById('result-modal-title').textContent = title;
5931  document.getElementById('result-modal-body').textContent  = body;
5932  document.getElementById('result-modal').style.display = 'flex';
5933}
5934function _closeResultModal() {
5935  document.getElementById('result-modal').style.display = 'none';
5936}
5937
5938async function _storeDelete(storeId, storeName, couponCount, storeDomain) {
5939  const choice = await _showStoreDeleteModal(storeName, couponCount, storeDomain || '');
5940  if (!choice) return;
5941  const row = document.querySelector(`tr[data-sid="${storeId}"]`);
5942  if (row) row.style.opacity = '0.4';
5943  try {
5944    if (choice.action === 'merge') {
5945      const r = await fetch(`/api/admin/stores/${storeId}/merge-into`, {
5946        method: 'POST',
5947        headers: {'Content-Type':'application/json'},
5948        body: JSON.stringify({ target_domain: choice.targetDomain })
5949      });
5950      const d = await r.json();
5951      if (!r.ok) {
5952        if (row) row.style.opacity = '';
5953        _showResultModal('❌', 'שגיאה במיזוג', d.detail || JSON.stringify(d));
5954        return;
5955      }
5956      _showResultModal('✅', `"${storeName}" מוזגה אל "${d.dst}"`,
5957        `${d.coupons_moved} קופונים הועברו${d.coupons_dropped ? `, ${d.coupons_dropped} כפולים נמחקו` : ''}.`);
5958      if (row) row.remove();
5959      _storesData = _storesData.filter(s => s.id !== storeId);
5960    } else {
5961      const r = await fetch('/api/admin/stores/bulk-delete', {
5962        method: 'DELETE',
5963        headers: {'Content-Type':'application/json'},
5964        body: JSON.stringify({ store_ids: [storeId] })
5965      });
5966      const d = await r.json();
5967      if (!r.ok) {
5968        if (row) row.style.opacity = '';
5969        _showResultModal('❌', 'שגיאה במחיקה', d.detail || JSON.stringify(d));
5970        return;
5971      }
5972      const warn = couponCount > 0 && d.coupons_deleted === 0
5973        ? `\n\n⚠️ שים לב: החנות נמחקה אך ${couponCount} קופונים לא הוסרו.`
5974        : '';
5975      _showResultModal('✅', `"${storeName}" נמחקה`, `${d.coupons_deleted} קופונים הוסרו.${warn}`);
5976      if (row) row.remove();
5977      _storesData = _storesData.filter(s => s.id !== storeId);
5978    }
5979  } catch(e) {
5980    if (row) row.style.opacity = '';
5981    _showResultModal('❌', 'שגיאה', e.message || 'שגיאת רשת');
5982  }
5983}
5984
5985function _showStoreDeleteModal(storeName, couponCount, storeDomain) {
5986  return new Promise(resolve => {
5987    const overlay = document.createElement('div');
5988    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:center;justify-content:center';
5989    overlay.innerHTML = `
5990      <div style="background:#1a1a1a;border:1px solid #333;border-radius:14px;padding:28px 32px;min-width:340px;max-width:460px;text-align:right;direction:rtl;font-family:'DM Sans',sans-serif">
5991        <div style="font-size:16px;font-weight:600;margin-bottom:6px;color:#fff">מחיקת חנות</div>
5992        <div style="font-size:13px;color:#888;margin-bottom:18px">חנות: <span style="color:#fff">${storeName || '—'}</span>${couponCount ? `<span style="color:#888"> · ${couponCount} קופונים</span>` : ''}</div>
5993        <div style="display:flex;flex-direction:column;gap:8px">
5994          <label style="display:flex;align-items:center;gap:10px;cursor:pointer;padding:10px 14px;border:1px solid #8b2222;border-radius:8px;background:#3a1a1a">
5995            <input type="radio" name="_sdel_action" value="delete" checked style="accent-color:#e05555;cursor:pointer;flex-shrink:0" />
5996            <span style="font-size:13px;color:#ff6b6b">💣 מחק חנות וכל הקופונים</span>
5997          </label>
5998          <label id="_sdel_merge_label" style="display:flex;align-items:flex-start;gap:10px;cursor:pointer;padding:10px 14px;border:1px solid #444;border-radius:8px;background:#2a2a2a">
5999            <input type="radio" name="_sdel_action" value="merge" style="accent-color:var(--accent);cursor:pointer;flex-shrink:0;margin-top:2px" />
6000            <div style="flex:1">
6001              <div style="font-size:13px;color:#fff;margin-bottom:8px">🔀 מחק חנות ומזג קופונים ושדות ל:</div>
6002              <input id="_sdel_target" type="text" placeholder="domain.com" value="${storeDomain || ''}"
6003                style="background:#1a1a1a;border:1px solid #555;border-radius:8px;padding:7px 12px;font-size:13px;color:#fff;outline:none;font-family:'DM Sans',sans-serif;width:100%;box-sizing:border-box;direction:ltr;text-align:left" />
6004            </div>
6005          </label>
6006        </div>
6007        <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:20px">
6008          <button id="_sdel_cancel" style="background:transparent;border:1px solid #444;border-radius:8px;padding:8px 18px;color:#aaa;cursor:pointer;font-size:13px;font-family:'DM Sans',sans-serif">ביטול</button>
6009          <button id="_sdel_ok" style="background:var(--accent);border:none;border-radius:8px;padding:8px 22px;color:#000;cursor:pointer;font-size:13px;font-weight:600;font-family:'DM Sans',sans-serif">אישור</button>
6010        </div>
6011      </div>`;
6012    document.body.appendChild(overlay);
6013
6014    // clicking merge label focuses the input
6015    overlay.querySelector('#_sdel_merge_label').addEventListener('click', () => {
6016      overlay.querySelector('input[value="merge"]').checked = true;
6017      setTimeout(() => overlay.querySelector('#_sdel_target').focus(), 50);
6018    });
6019    overlay.querySelector('#_sdel_target').addEventListener('focus', () => {
6020      overlay.querySelector('input[value="merge"]').checked = true;
6021    });
6022
6023    overlay.querySelector('#_sdel_ok').addEventListener('click', e => {
6024      e.stopPropagation();
6025      const action = overlay.querySelector('input[name="_sdel_action"]:checked')?.value || 'delete';
6026      const targetDomain = overlay.querySelector('#_sdel_target')?.value?.trim() || '';
6027      if (action === 'merge' && !targetDomain) {
6028        overlay.querySelector('#_sdel_target').style.borderColor = '#e05555';
6029        overlay.querySelector('#_sdel_target').focus();
6030        return;
6031      }
6032      document.body.removeChild(overlay);
6033      resolve({ action, targetDomain });
6034    });
6035    overlay.querySelector('#_sdel_cancel').addEventListener('click', e => {
6036      e.stopPropagation();
6037      document.body.removeChild(overlay);
6038      resolve(null);
6039    });
6040  });
6041}
6042
6043async function _storeSetOrigin(storeId, val) {
6044  const origin = val === '' ? null : val;
6045  try {
6046    const r = await fetch(`/api/admin/stores/${storeId}/set-origin`, {
6047      method: 'PATCH',
6048      headers: {'Content-Type':'application/json'},
6049      body: JSON.stringify({ origin })
6050    });
6051    if (r.ok) {
6052      showToast(origin ? `✓ origin = ${origin}` : '✓ origin cleared');
6053      const s = _storesData.find(x => x.id === storeId);
6054      if (s) s.origin = origin;
6055    } else {
6056      showToast('שגיאה בעדכון מדינה');
6057    }
6058  } catch(e) {
6059    showToast('שגיאה בעדכון מדינה');
6060  }
6061}
6062
6063// ── אין מדינה view ────────────────────────────────────────────────────────────
6064let _noOriginData = [], _noOriginPage = 0;
6065const _noOriginPerPage = 50;
6066
6067// ── Store Categories view ──────────────────────────────────────────────────
6068const ALL_CATS = ['Beauty','Fashion','Finance','Food','Health','Home','Kids','Leisure','Sports','Tech','Travel','Vacation'];
6069const CAT_COLORS = {
6070  Beauty:'#e879a0',Fashion:'#a78bfa',Finance:'#34d399',Food:'#fb923c',
6071  Health:'#f87171',Home:'#60a5fa',Kids:'#fbbf24',Leisure:'#4ade80',
6072  Sports:'#38bdf8',Tech:'#818cf8',Travel:'#f472b6',Vacation:'#a3e635'
6073};
6074let _catData = [], _catPage = 0;
6075const _catPerPage = 50;
6076
6077async function loadStoreCategoriesView() {
6078  const tbody = document.getElementById('cat-tbody');
6079  if (!tbody) return;
6080  tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
6081  try {
6082    const res = await fetch('/api/admin/stores/all');
6083    _catData = await res.json();
6084    const badge = document.getElementById('src-cat-count');
6085    if (badge) badge.textContent = _catData.length;
6086    _catPage = 0;
6087    _catRender();
6088  } catch(e) {
6089    tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
6090  }
6091}
6092
6093function _catFilter() { _catPage = 0; _catRender(); }
6094
6095function catGoPage(p) { _catPage = p; _catRender(); }
6096
6097function _catRender() {
6098  const q = (document.getElementById('cat-search')?.value || '').toLowerCase();
6099  const filt = document.getElementById('cat-filter-sel')?.value || 'all';
6100  let rows = _catData.slice();
6101  if (q) rows = rows.filter(s =>
6102    s.name.toLowerCase().includes(q) ||
6103    (s.store_he || '').includes(q) ||
6104    (s.domain || '').toLowerCase().includes(q)
6105  );
6106  if (filt === 'empty') rows = rows.filter(s => !(s.categories || []).length);
6107  if (filt === 'has')   rows = rows.filter(s => (s.categories || []).length > 0);
6108
6109  const totalPages = Math.max(1, Math.ceil(rows.length / _catPerPage));
6110  if (_catPage >= totalPages) _catPage = totalPages - 1;
6111  const slice = rows.slice(_catPage * _catPerPage, (_catPage + 1) * _catPerPage);
6112
6113  const pgDiv = document.getElementById('cat-pagination');
6114  if (pgDiv) {
6115    if (totalPages <= 1) { pgDiv.innerHTML = ''; } else {
6116      const nums = _pageNums(_catPage, totalPages);
6117      const numBtns = nums.map(n => n === '…'
6118        ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
6119        : `<button class="td-btn${n-1===_catPage?' page-active':''}" onclick="catGoPage(${n-1})" style="min-width:32px">${n}</button>`
6120      ).join('');
6121      pgDiv.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 18px 14px;border-top:1px solid var(--gray3);flex-wrap:wrap">
6122        <button class="td-btn" onclick="catGoPage(${_catPage-1})" ${_catPage===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
6123        ${numBtns}
6124        <button class="td-btn" onclick="catGoPage(${_catPage+1})" ${_catPage>=totalPages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
6125      </div>`;
6126    }
6127  }
6128
6129  const tbody = document.getElementById('cat-tbody');
6130  if (!tbody) return;
6131  if (!slice.length) {
6132    tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--gray5);padding:24px">לא נמצאו חנויות</td></tr>`;
6133    return;
6134  }
6135  tbody.innerHTML = slice.map((s, i) => {
6136    const cats = s.categories || [];
6137    const tagBtns = ALL_CATS.map(c => {
6138      const on = cats.includes(c);
6139      const col = CAT_COLORS[c];
6140      return `<button onclick="_catToggle('${s.id}','${c}')" style="
6141        border:1px solid ${col};border-radius:20px;padding:2px 10px;font-size:11px;cursor:pointer;
6142        background:${on ? col : 'transparent'};color:${on ? '#000' : col};
6143        font-weight:${on?'700':'400'};transition:all 0.15s;margin:2px">${c}</button>`;
6144    }).join('');
6145    return `<tr id="cat-row-${s.id}">
6146      <td style="color:var(--gray5);font-size:12px">${_catPage * _catPerPage + i + 1}</td>
6147      <td style="font-size:13px;font-weight:600">${s.name}</td>
6148      <td style="font-size:13px;direction:rtl">${s.store_he || '<span style="color:var(--gray5)">—</span>'}</td>
6149      <td style="font-size:12px;color:var(--gray6)">${s.domain || '<span style="color:var(--gray5)">—</span>'}</td>
6150      <td><div style="display:flex;flex-wrap:wrap;gap:0">${tagBtns}</div></td>
6151      <td id="cat-status-${s.id}" style="text-align:center;font-size:13px"></td>
6152    </tr>`;
6153  }).join('');
6154}
6155
6156async function _catToggle(storeId, cat) {
6157  const store = _catData.find(s => s.id === storeId);
6158  if (!store) return;
6159  const statusEl = document.getElementById(`cat-status-${storeId}`);
6160  const cats = store.categories ? [...store.categories] : [];
6161  const idx = cats.indexOf(cat);
6162  if (idx === -1) cats.push(cat); else cats.splice(idx, 1);
6163  store.categories = cats;
6164  if (statusEl) statusEl.innerHTML = `<span style="color:var(--gray5);font-size:11px">שומר...</span>`;
6165  try {
6166    const res = await fetch(`/api/admin/stores/${storeId}`, {
6167      method: 'PATCH',
6168      headers: {'Content-Type':'application/json'},
6169      body: JSON.stringify({categories: cats})
6170    });
6171    if (!res.ok) throw new Error(await res.text());
6172    if (statusEl) { statusEl.innerHTML = `<span style="color:#00e87a">✓</span>`; setTimeout(()=>{ if(statusEl) statusEl.innerHTML=''; }, 1800); }
6173    _catRender();
6174  } catch(e) {
6175    store.categories = cats.includes(cat) ? cats.filter(c=>c!==cat) : [...cats, cat]; // revert
6176    if (statusEl) statusEl.innerHTML = `<span style="color:#fc8181" title="${e.message}">✗</span>`;
6177  }
6178}
6179// ── end Store Categories ───────────────────────────────────────────────────
6180
6181async function loadNoOriginStores() {
6182  const tbody = document.getElementById('no-origin-tbody');
6183  if (!tbody) return;
6184  tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
6185  try {
6186    const res = await fetch('/api/admin/stores/all');
6187    const all = await res.json();
6188    _noOriginData = all.filter(s => !s.origin);
6189    const badge = document.getElementById('src-noorigin-count');
6190    if (badge) badge.textContent = _noOriginData.length;
6191    _noOriginPage = 0;
6192    _renderNoOriginTable();
6193  } catch(e) {
6194    tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
6195  }
6196}
6197
6198function _renderNoOriginTable() {
6199  const tbody = document.getElementById('no-origin-tbody');
6200  if (!tbody) return;
6201  const totalPages = Math.max(1, Math.ceil(_noOriginData.length / _noOriginPerPage));
6202  if (_noOriginPage >= totalPages) _noOriginPage = totalPages - 1;
6203  const slice = _noOriginData.slice(_noOriginPage * _noOriginPerPage, (_noOriginPage + 1) * _noOriginPerPage);
6204
6205  const pgDiv = document.getElementById('no-origin-pagination');
6206  if (pgDiv) {
6207    if (totalPages <= 1) { pgDiv.innerHTML = ''; } else {
6208      const nums = _pageNums(_noOriginPage, totalPages);
6209      const numBtns = nums.map(n => n === '…'
6210        ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
6211        : `<button class="td-btn${n-1===_noOriginPage?' page-active':''}" onclick="_noOriginGoPage(${n-1})" style="min-width:32px">${n}</button>`
6212      ).join('');
6213      pgDiv.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 18px 14px;border-top:1px solid var(--gray3);flex-wrap:wrap">
6214        <button class="td-btn" onclick="_noOriginGoPage(${_noOriginPage-1})" ${_noOriginPage===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
6215        ${numBtns}
6216        <button class="td-btn" onclick="_noOriginGoPage(${_noOriginPage+1})" ${_noOriginPage>=totalPages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
6217      </div>`;
6218    }
6219  }
6220
6221  if (!slice.length) {
6222    tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:24px">אין חנויות ללא מדינה</td></tr>`;
6223    return;
6224  }
6225  tbody.innerHTML = slice.map((s, i) => `<tr>
6226    <td style="color:var(--gray5);font-size:12px">${_noOriginPage * _noOriginPerPage + i + 1}</td>
6227    <td>${s.name}${s.store_he ? ` <span style="color:var(--gray5);font-size:11px">(${s.store_he})</span>` : ''}</td>
6228    <td style="color:var(--gray6);font-size:12px">${s.domain || '<span style="color:#fc8181">—</span>'}</td>
6229    <td style="text-align:center;color:var(--gray5)">${s.coupon_count || 0}</td>
6230    <td style="text-align:center">
6231      <select onchange="_noOriginSet('${s.id}',this)"
6232        style="background:var(--gray2);border:1px solid var(--gray3);border-radius:6px;padding:3px 8px;font-size:12px;color:var(--white);cursor:pointer;outline:none">
6233        <option value="">❓ לא ידוע</option>
6234        <option value="il">🇮🇱 IL</option>
6235        <option value="int">🌍 INT</option>
6236      </select>
6237    </td>
6238  </tr>`).join('');
6239}
6240
6241function _noOriginGoPage(p) { _noOriginPage = p; _renderNoOriginTable(); }
6242
6243async function _noOriginSet(storeId, sel) {
6244  const origin = sel.value || null;
6245  try {
6246    const r = await fetch(`/api/admin/stores/${storeId}/set-origin`, {
6247      method: 'PATCH', headers: {'Content-Type':'application/json'},
6248      body: JSON.stringify({ origin })
6249    });
6250    if (r.ok) {
6251      showToast(origin ? `✓ origin = ${origin}` : '✓ cleared');
6252      _noOriginData = _noOriginData.filter(s => s.id !== storeId);
6253      const badge = document.getElementById('src-noorigin-count');
6254      if (badge) badge.textContent = _noOriginData.length;
6255      _renderNoOriginTable();
6256    } else { showToast('שגיאה'); sel.value = ''; }
6257  } catch(e) { showToast('שגיאה'); sel.value = ''; }
6258}
6259
6260// ── תצוגת קופונים — coupon preview ──────────────────────────────────────────
6261let _cprevAllData = [], _cprevFilter = 'all', _cprevPage = 0, _cprevDomainMap = {};
6262const _cprevPerPage = 48;
6263
6264async function loadCouponPreview() {
6265  const grid = document.getElementById('coupon-preview-grid');
6266  if (!grid) return;
6267  grid.innerHTML = `<div style="color:var(--gray5);text-align:center;grid-column:1/-1;padding:40px">טוען...</div>`;
6268  try {
6269    const [cRes, sRes] = await Promise.all([
6270      fetch('/api/admin/coupons/all-preview'),
6271      fetch('/api/admin/stores/all')
6272    ]);
6273    _cprevAllData = await cRes.json();
6274    const stores = await sRes.json();
6275    const originMap = {}, domainMap = {};
6276    stores.forEach(s => {
6277      originMap[s.name] = s.origin || null;
6278      if (s.domain) domainMap[s.name] = s.domain;
6279    });
6280    _cprevDomainMap = domainMap;
6281    _cprevAllData.forEach(c => { c._origin = originMap[c.store] || null; });
6282    _cprevPage = 0;
6283    _cprevRender();
6284  } catch(e) {
6285    grid.innerHTML = `<div style="color:#fc8181;text-align:center;grid-column:1/-1;padding:40px">שגיאה בטעינה</div>`;
6286  }
6287}
6288
6289function _cprevSetFilter(val) {
6290  _cprevFilter = val;
6291  _cprevPage = 0;
6292  ['all','il','int'].forEach(k => {
6293    const btn = document.getElementById('cprev-filter-' + k);
6294    if (!btn) return;
6295    btn.style.background = k===val ? 'var(--accent)' : '';
6296    btn.style.color = k===val ? '#000' : '';
6297    btn.classList.toggle('btn-ghost', k !== val);
6298  });
6299  _cprevRender();
6300}
6301
6302function _cprevGrouped() {
6303  let rows = _cprevAllData.slice();
6304  if (_cprevFilter === 'il')  rows = rows.filter(c => c._origin === 'il');
6305  if (_cprevFilter === 'int') rows = rows.filter(c => c._origin === 'int');
6306  // Group by store, preserve insertion order (first coupon per store sets order)
6307  const map = new Map();
6308  rows.forEach(c => {
6309    const s = c.store || '';
6310    if (!map.has(s)) map.set(s, []);
6311    map.get(s).push(c);
6312  });
6313  return [...map.entries()].sort((a,b) => a[0].localeCompare(b[0]));
6314}
6315
6316function _cprevCouponCard(c) {
6317  const disc  = c.discount ? formatDiscount(c.discount) : '';
6318  const code  = c.code || '';
6319  const notes = c.notes || '';
6320  return `<div class="coupon-card" style="min-width:220px;max-width:260px;flex:0 0 auto">
6321    ${disc  ? `<div class="cc-discount" style="font-size:19px;margin-bottom:6px">${disc}</div>` : ''}
6322    ${notes ? `<div class="cc-notes" style="margin-bottom:8px">${notes}</div>` : ''}
6323    ${code  ? `<div style="background:var(--gray3);border:1px solid rgba(0,232,122,0.35);border-radius:8px;padding:9px 12px;font-family:monospace;font-size:14px;font-weight:700;color:var(--white);letter-spacing:2px;text-align:center;direction:ltr">${code}</div>` : ''}
6324  </div>`;
6325}
6326
6327function _cprevRender() {
6328  const grid  = document.getElementById('coupon-preview-grid');
6329  const pgDiv = document.getElementById('coupon-preview-pagination');
6330  if (!grid) return;
6331
6332  const groups = _cprevGrouped();
6333  const totalPages = Math.max(1, Math.ceil(groups.length / _cprevPerPage));
6334  if (_cprevPage >= totalPages) _cprevPage = totalPages - 1;
6335  const slice = groups.slice(_cprevPage * _cprevPerPage, (_cprevPage + 1) * _cprevPerPage);
6336
6337  if (pgDiv) {
6338    if (totalPages <= 1) { pgDiv.innerHTML = ''; } else {
6339      const nums = _pageNums(_cprevPage, totalPages);
6340      const numBtns = nums.map(n => n === '…'
6341        ? `<span style="color:var(--gray5);padding:0 4px">…</span>`
6342        : `<button class="td-btn${n-1===_cprevPage?' page-active':''}" onclick="_cprevGoPage(${n-1})" style="min-width:32px">${n}</button>`
6343      ).join('');
6344      pgDiv.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;gap:4px;flex-wrap:wrap">
6345        <button class="td-btn" onclick="_cprevGoPage(${_cprevPage-1})" ${_cprevPage===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
6346        ${numBtns}
6347        <button class="td-btn" onclick="_cprevGoPage(${_cprevPage+1})" ${_cprevPage>=totalPages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
6348      </div>`;
6349    }
6350  }
6351
6352  if (!slice.length) {
6353    grid.innerHTML = `<div style="color:var(--gray5);text-align:center;grid-column:1/-1;padding:40px">אין קופונים</div>`;
6354    return;
6355  }
6356
6357  grid.innerHTML = slice.map(([storeName, coupons]) => {
6358    const origin = coupons[0]._origin;
6359    const originBadge = origin === 'il'
6360      ? `<span style="background:#1a3a1a;color:#4ade80;border-radius:4px;padding:1px 6px;font-size:10px;font-weight:700">🇮🇱</span>`
6361      : origin === 'int'
6362      ? `<span style="background:#1a1a3a;color:#93c5fd;border-radius:4px;padding:1px 6px;font-size:10px;font-weight:700">🌍</span>`
6363      : `<span style="background:var(--gray3);color:var(--gray5);border-radius:4px;padding:1px 6px;font-size:10px">❓</span>`;
6364    const domain = _cprevDomainMap[storeName] || '';
6365    const faviconUrls = storeToFavicon(storeName, '') || [];
6366    const favicon = faviconUrls[0] || null;
6367    const faviconFallbacksStr = faviconUrls.slice(1).join('|');
6368    const safeStore = storeName.replace(/'/g, "\\'");
6369    const countPill = `<span style="background:var(--gray3);color:var(--gray5);border-radius:12px;padding:2px 8px;font-size:11px;font-weight:600">${coupons.length} קופונים</span>`;
6370
6371    return `<div style="grid-column:1/-1;border:1px solid var(--gray3);border-radius:14px;overflow:hidden;margin-bottom:4px">
6372      <!-- store header -->
6373      <div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--gray2);border-bottom:1px solid var(--gray3);direction:ltr">
6374        <div class="cc-logo" style="width:52px;height:52px;flex-shrink:0;border-radius:12px">
6375          ${favicon
6376            ? `<img src="${favicon}" alt="${safeStore}" onerror="_faviconErr(this,'${faviconFallbacksStr}','${safeStore}')">`
6377            : _storeInitial(storeName)}
6378        </div>
6379        <div style="flex:1;display:flex;align-items:center;gap:8px;min-width:0;direction:rtl">
6380          <span style="font-size:20px;font-weight:800;color:var(--white);white-space:nowrap">${storeName}</span>
6381          ${originBadge}
6382          ${countPill}
6383          ${domain ? `<span style="font-size:12px;color:var(--gray5);direction:ltr">${domain}</span>` : ''}
6384        </div>
6385      </div>
6386      <!-- coupon cards row -->
6387      <div style="display:flex;gap:12px;padding:12px 16px;overflow-x:auto;background:#111;flex-wrap:wrap">
6388        ${coupons.map(_cprevCouponCard).join('')}
6389      </div>
6390    </div>`;
6391  }).join('');
6392}
6393
6394function _cprevGoPage(p) { _cprevPage = p; _cprevRender(); window.scrollTo(0,0); }
6395
6396let _orphanSearchResults = [];
6397let _allStoresForOrphan  = [];
6398
6399async function _orphanSearchStores() {
6400  const modal = document.getElementById('orphan-search-modal');
6401  const list  = document.getElementById('orphan-modal-list');
6402  const sub   = document.getElementById('orphan-modal-sub');
6403  _orphanSearchResults = [];
6404  modal.style.display = 'flex';
6405  list.innerHTML = `<div style="color:var(--gray5);text-align:center;padding:32px">מחפש התאמות...</div>`;
6406  sub.textContent = 'מחפש...';
6407  _orphanUpdateSummary();
6408  try {
6409    const [searchRes, storesRes] = await Promise.all([
6410      fetch('/api/admin/coupons/orphans/search-stores'),
6411      fetch('/api/admin/stores/all')
6412    ]);
6413    _orphanSearchResults = await searchRes.json();
6414    const storesData = await storesRes.json();
6415    _allStoresForOrphan = (Array.isArray(storesData) ? storesData : (storesData.stores || [])).sort((a,b) => a.name.localeCompare(b.name));
6416    const found = _orphanSearchResults.filter(d => d.matched_store_id);
6417    sub.textContent = `נמצאו ${found.length} התאמות מתוך ${_orphanSearchResults.length} חנויות`;
6418    _orphanRenderList();
6419  } catch(e) {
6420    list.innerHTML = `<div style="color:#f87171;text-align:center;padding:24px">שגיאה בחיפוש</div>`;
6421    sub.textContent = 'שגיאה';
6422  }
6423}
6424
6425function _orphanRenderList() {
6426  const list = document.getElementById('orphan-modal-list');
6427  const storeOptions = _allStoresForOrphan.map(s =>
6428    `<option value="${s.id}">${s.name}${s.domain ? ' ('+s.domain+')' : ''}</option>`
6429  ).join('');
6430
6431  list.innerHTML = _orphanSearchResults.map((d, i) => {
6432    const hasMatch = !!d.matched_store_id;
6433    const border = hasMatch ? '#166534' : 'var(--gray3)';
6434    const icon   = hasMatch ? `<span style="color:#4ade80;font-size:15px;flex-shrink:0">✓</span>`
6435                            : `<span style="color:#f87171;font-size:15px;flex-shrink:0">✗</span>`;
6436    const matchLabel = hasMatch
6437      ? `<span style="color:#86efac">${d.matched_store_name}</span> <span style="color:var(--gray6)">(${d.matched_domain || '?'})</span>`
6438      : `<span style="color:var(--gray5)">לא נמצאה חנות</span>`;
6439
6440    return `<div id="orphan-row-${i}" style="display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--gray2);border-radius:8px;border:1px solid ${border}">
6441      ${icon}
6442      <div style="flex:1;min-width:0">
6443        <div style="font-size:13px;font-weight:600;color:var(--white)">${d.orphan_name}</div>
6444        <div id="orphan-label-${i}" style="font-size:11px;color:var(--gray5);margin-top:2px">${d.coupon_count} קופונים → ${matchLabel}</div>
6445        <select id="orphan-sel-${i}" onchange="_orphanPickStore(${i},this.value)"
6446          style="display:none;margin-top:6px;width:100%;background:var(--gray3);color:var(--white);border:1px solid var(--gray4);border-radius:6px;padding:4px 6px;font-size:12px">
6447          <option value="">— דחה / אל תמזג —</option>
6448          ${storeOptions}
6449        </select>
6450      </div>
6451      <button onclick="_orphanToggleEdit(${i})"
6452        style="flex-shrink:0;background:none;border:1px solid var(--gray4);border-radius:6px;color:var(--gray5);font-size:11px;padding:3px 7px;cursor:pointer">✎</button>
6453    </div>`;
6454  }).join('');
6455
6456  _orphanUpdateSummary();
6457}
6458
6459function _orphanToggleEdit(i) {
6460  const sel = document.getElementById(`orphan-sel-${i}`);
6461  const lbl = document.getElementById(`orphan-label-${i}`);
6462  const isOpen = sel.style.display !== 'none';
6463  if (isOpen) {
6464    sel.style.display = 'none';
6465    lbl.style.display = '';
6466  } else {
6467    sel.style.display = '';
6468    lbl.style.display = 'none';
6469    sel.value = _orphanSearchResults[i].matched_store_id || '';
6470  }
6471}
6472
6473function _orphanPickStore(i, storeId) {
6474  const d = _orphanSearchResults[i];
6475  if (!storeId) {
6476    d.matched_store_id   = null;
6477    d.matched_store_name = null;
6478    d.matched_domain     = null;
6479  } else {
6480    const s = _allStoresForOrphan.find(s => s.id === storeId);
6481    d.matched_store_id   = storeId;
6482    d.matched_store_name = s ? s.name   : storeId;
6483    d.matched_domain     = s ? s.domain : null;
6484  }
6485  _orphanToggleEdit(i);
6486  _orphanRenderList();
6487}
6488
6489function _orphanUpdateSummary() {
6490  const found    = _orphanSearchResults.filter(d => d.matched_store_id);
6491  const notFound = _orphanSearchResults.filter(d => !d.matched_store_id);
6492  const totalCoupons  = found.reduce((s, d) => s + d.coupon_count, 0);
6493  const leftover      = notFound.reduce((s, d) => s + d.coupon_count, 0);
6494  const sumEl      = document.getElementById('orphan-modal-summary');
6495  const approveBtn = document.getElementById('orphan-modal-approve');
6496  if (sumEl) sumEl.textContent = found.length
6497    ? `${totalCoupons} קופונים ימוזגו ל-${found.length} חנויות · ${leftover} קופונים יישארו`
6498    : '';
6499  if (approveBtn) {
6500    approveBtn.disabled      = found.length === 0;
6501    approveBtn.style.opacity = found.length > 0 ? '1' : '0.4';
6502  }
6503}
6504
6505function _orphanModalClose() {
6506  document.getElementById('orphan-search-modal').style.display = 'none';
6507}
6508
6509async function _orphanModalApprove() {
6510  const merges = _orphanSearchResults
6511    .filter(d => d.matched_store_id)
6512    .map(d => ({ orphan_name: d.orphan_name, target_store_id: d.matched_store_id }));
6513  if (!merges.length) return;
6514  const btn = document.getElementById('orphan-modal-approve');
6515  btn.disabled = true;
6516  btn.textContent = '⏳ ממזג...';
6517  try {
6518    const r = await fetch('/api/admin/coupons/orphans/merge', {
6519      method: 'POST',
6520      headers: { 'Content-Type': 'application/json' },
6521      body: JSON.stringify({ merges })
6522    });
6523    const d = await r.json();
6524    if (!r.ok) throw new Error(d.detail || d.message || `HTTP ${r.status}`);
6525    _orphanModalClose();
6526    showToast(`✓ מוזגו ${d.merged_coupons} קופונים ל-${merges.length} חנויות`);
6527    loadOrphanCoupons();
6528  } catch(e) {
6529    alert('שגיאה במיזוג: ' + e.message);
6530    btn.disabled = false;
6531    btn.textContent = 'אשר מיזוג';
6532  }
6533}
6534
6535async function loadOrphanCoupons() {
6536  const tbody = document.getElementById('orphan-tbody');
6537  if (!tbody) return;
6538  tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
6539  try {
6540    const res = await fetch('/api/admin/coupons/orphans');
6541    const data = await res.json();
6542    _updateOrphanBadge(data.length);
6543    const delBtn = document.getElementById('orphan-delete-btn');
6544    if (delBtn) delBtn.style.display = data.length > 0 ? '' : 'none';
6545    const searchBtn = document.getElementById('orphan-search-btn');
6546    if (searchBtn) searchBtn.style.display = data.length > 0 ? '' : 'none';
6547    if (!data.length) {
6548      tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--accent);padding:28px">✓ אין קופונים יתומים</td></tr>`;
6549      return;
6550    }
6551    tbody.innerHTML = data.map((c) => {
6552      const src = (c.source || '—').replace(/</g,'&lt;');
6553      const date = c.created_at ? c.created_at.slice(0,10) : '—';
6554      return `<tr>
6555        <td style="padding:6px 4px 6px 10px">
6556          <input type="checkbox" class="orphan-row-check" data-id="${c.id}"
6557            onchange="_orphanCheckChanged()"
6558            style="cursor:pointer;accent-color:var(--accent)">
6559        </td>
6560        <td style="font-weight:600;color:#fca5a5">${c.store}</td>
6561        <td style="font-family:monospace;color:var(--white)">${c.code || '—'}</td>
6562        <td style="font-size:11px;color:var(--gray5);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${src}">${src}</td>
6563        <td style="font-size:11px;color:var(--gray5)">${date}</td>
6564      </tr>`;
6565    }).join('');
6566  } catch(e) {
6567    tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
6568  }
6569}
6570
6571function _updateOrphanBadge(count) {
6572  const badge = document.getElementById('src-orphan-count');
6573  if (!badge) return;
6574  badge.textContent = count || '0';
6575  if (count > 0) {
6576    badge.style.background = '#7f1d1d';
6577    badge.style.color = '#fca5a5';
6578  } else {
6579    badge.style.background = '';
6580    badge.style.color = '';
6581  }
6582}
6583
6584async function _refreshOrphanCount() {
6585  try {
6586    const r = await fetch('/api/admin/coupons/orphans');
6587    const d = await r.json();
6588    _updateOrphanBadge(d.length);
6589  } catch(_) {}
6590}
6591
6592async function _deleteOrphanCoupons() {
6593  if (!confirm('למחוק את כל הקופונים היתומים?')) return;
6594  const btn = document.getElementById('orphan-delete-btn');
6595  if (btn) { btn.disabled = true; btn.textContent = '⏳ מוחק...'; }
6596  try {
6597    const r = await fetch('/api/admin/coupons/delete-orphans', { method: 'DELETE' });
6598    const d = await r.json();
6599    showToast(`🗑 נמחקו ${d.coupons_deleted} קופונים יתומים`);
6600    loadOrphanCoupons();
6601  } catch(e) {
6602    alert('שגיאה במחיקה');
6603    if (btn) { btn.disabled = false; btn.textContent = '🗑 מחק הכל'; }
6604  }
6605}
6606
6607function _orphanCheckChanged() {
6608  const all   = [...document.querySelectorAll('.orphan-row-check')];
6609  const checked = all.filter(c => c.checked);
6610  const countEl = document.getElementById('orphan-selected-count');
6611  const selBtn  = document.getElementById('orphan-delete-selected-btn');
6612  const allChk  = document.getElementById('orphan-check-all');
6613  if (countEl) countEl.textContent = checked.length;
6614  if (selBtn)  selBtn.style.display = checked.length > 0 ? '' : 'none';
6615  if (allChk)  allChk.indeterminate = checked.length > 0 && checked.length < all.length;
6616  if (allChk)  allChk.checked = all.length > 0 && checked.length === all.length;
6617}
6618
6619function _orphanToggleAll(checked) {
6620  document.querySelectorAll('.orphan-row-check').forEach(c => c.checked = checked);
6621  _orphanCheckChanged();
6622}
6623
6624async function _deleteSelectedOrphans() {
6625  const ids = [...document.querySelectorAll('.orphan-row-check:checked')].map(c => c.dataset.id);
6626  if (!ids.length) return;
6627  if (!confirm(`למחוק ${ids.length} קופונים נבחרים?`)) return;
6628  const btn = document.getElementById('orphan-delete-selected-btn');
6629  if (btn) { btn.disabled = true; btn.textContent = '⏳ מוחק...'; }
6630  try {
6631    const r = await fetch('/api/admin/coupons/bulk-delete', {
6632      method: 'POST',
6633      headers: { 'Content-Type': 'application/json' },
6634      body: JSON.stringify({ ids })
6635    });
6636    const d = await r.json();
6637    if (!r.ok) throw new Error(d.detail || d.message || `HTTP ${r.status}`);
6638    showToast(`🗑 נמחקו ${d.deleted} קופונים`);
6639    loadOrphanCoupons();
6640  } catch(e) {
6641    alert('שגיאה במחיקה: ' + e.message);
6642    if (btn) { btn.disabled = false; btn.textContent = `🗑 מחק נבחרים`; }
6643  }
6644}
6645
6646async function _ndDeleteOrphans() {
6647  if (!confirm('למחוק את כל הקופונים שהחנות שלהם כבר לא קיימת?')) return;
6648  const btn = document.getElementById('nd-delete-orphans-btn');
6649  if (btn) { btn.disabled = true; btn.textContent = '⏳ מוחק...'; }
6650  try {
6651    const r = await fetch('/api/admin/coupons/delete-orphans', { method: 'DELETE' });
6652    const d = await r.json();
6653    showToast(`🧹 נמחקו ${d.coupons_deleted} קופונים יתומים (${d.orphan_stores} חנויות)`);
6654  } catch(e) {
6655    alert('שגיאה במחיקה');
6656  } finally {
6657    if (btn) { btn.disabled = false; btn.textContent = '🧹 מחק קופונים יתומים'; }
6658  }
6659}
6660
6661async function _ndSave(storeId) {
6662  const nameInput   = document.getElementById('nd-name-'   + storeId);
6663  const heInput     = document.getElementById('nd-he-'     + storeId);
6664  const domainInput = document.getElementById('nd-domain-' + storeId);
6665  if (!nameInput || !heInput || !domainInput) return;
6666
6667  const name   = nameInput.value.trim();
6668  const he     = heInput.value.trim();
6669  const domain = domainInput.value.trim();
6670
6671  // All 3 mandatory
6672  if (!name)   { nameInput.style.borderColor   = '#fc8181'; nameInput.focus();   showToast('⚠️ שם אנגלי חובה');   return; }
6673  if (!he)     { heInput.style.borderColor     = '#fc8181'; heInput.focus();     showToast('⚠️ שם עברי חובה');    return; }
6674  if (!domain) { domainInput.style.borderColor = '#fc8181'; domainInput.focus();
6674 showToast('⚠️ דומיין חובה');     return; }
6675
6676  // Language validation
6677  if (/[א-תװ-״]/.test(name)) { nameInput.style.borderColor = '#fc8181'; showToast('⚠️ שם אנגלי — אותיות לטיניות בלבד'); return; }
6678  if (/[a-zA-Z]/.test(he) && /[א-תװ-״]/.test(he)) { heInput.style.borderColor = '#fc8181'; showToast('⚠️ שם עברי — לא לערבב עברית ואנגלית'); return; }
6679
6680  const row = nameInput.closest('tr');
6681  const btns = row ? row.querySelectorAll('button') : [];
6682  btns.forEach(b => b.disabled = true);
6683
6684  try {
6685    const r = await fetch(`/api/admin/stores/${storeId}/set-domain`, {
6686      method: 'POST',
6687      headers: {'Content-Type':'application/json'},
6688      body: JSON.stringify({ domain, name, store_he: he })
6689    });
6690    if (r.ok) {
6691      row.style.opacity = '0.4';
6692      row.style.pointerEvents = 'none';
6693      showToast('✓ נשמר');
6694      setTimeout(loadNullDomainStores, 600);
6695    } else {
6696      const err = await r.json().catch(() => ({}));
6697      showToast('⚠️ ' + (err.detail || 'שגיאה בשמירה'));
6698      btns.forEach(b => b.disabled = false);
6699    }
6700  } catch(e) {
6701    showToast('שגיאה בשמירה');
6702    btns.forEach(b => b.disabled = false);
6703  }
6704}
6705
6706// ── Store Coupons Modal (אין דומיין) ─────────────────────────────────────
6707let _ndcmSourceId   = null;
6708let _ndcmTargetId   = null;
6709let _ndcmAllStores  = [];  // stores WITH domain, for the target picker
6710
6711async function _ndShowCoupons(storeId, storeName) {
6712  _ndcmSourceId  = storeId;
6713  _ndcmTargetId  = null;
6714  _ndcmAllStores = [];
6715
6716  const modal = document.getElementById('nd-coupons-modal');
6717  document.getElementById('ndcm-title').textContent    = storeName;
6718  document.getElementById('ndcm-subtitle').textContent = 'טוען קופונים...';
6719  document.getElementById('ndcm-coupons').innerHTML    = '<div style="padding:24px;text-align:center;color:var(--gray5)">טוען...</div>';
6720  document.getElementById('ndcm-search').value         = '';
6721  document.getElementById('ndcm-dropdown').style.display = 'none';
6722  document.getElementById('ndcm-target-preview').textContent = '';
6723  const confirmBtn = document.getElementById('ndcm-confirm-btn');
6724  confirmBtn.disabled = true;
6725  confirmBtn.style.opacity = '0.45';
6726  modal.style.display = 'flex';
6727
6728  try {
6729    const [couponsRes, storesRes] = await Promise.all([
6730      fetch(`/api/admin/stores/${storeId}/coupons`),
6731      fetch('/api/admin/stores/all'),
6732    ]);
6733    const couponsData = await couponsRes.json();
6734    const allStores   = await storesRes.json();
6735
6736    _ndcmAllStores = allStores.filter(s => s.domain && s.id !== storeId);
6737
6738    const coupons = couponsData.coupons || [];
6739    document.getElementById('ndcm-subtitle').textContent =
6740      `${coupons.length} קופונים · ${couponsData.store}`;
6741
6742    if (!coupons.length) {
6743      document.getElementById('ndcm-coupons').innerHTML =
6744        '<div style="padding:24px;text-align:center;color:var(--gray5)">אין קופונים לחנות זו</div>';
6745      return;
6746    }
6747
6748    const STATUS_COLORS = { active:'#00e87a', inactive:'#fc8181', unknown:'#888', pending_review:'#ffb347' };
6749    document.getElementById('ndcm-coupons').innerHTML = coupons.map(c => {
6750      const statusColor = STATUS_COLORS[c.status] || '#888';
6751      const expiry = c.expiry ? `עד ${c.expiry.slice(0,10)}` : '';
6752      const src    = (c.source || '').replace(/\[primary\]\|/,'').slice(0,60);
6753      return `<div style="border:1px solid var(--gray3);border-radius:8px;padding:10px 14px;margin-bottom:8px;background:var(--gray1)">
6754        <div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
6755          <span style="font-family:monospace;font-size:14px;font-weight:700;color:var(--accent);letter-spacing:1px">${c.code}</span>
6756          <span style="font-size:13px;color:var(--white)">${c.discount || ''}</span>
6757          <span style="font-size:11px;color:${statusColor};background:${statusColor}22;border:1px solid ${statusColor}44;border-radius:4px;padding:1px 6px">${c.status || ''}</span>
6758          ${expiry ? `<span style="font-size:11px;color:var(--gray5)">${expiry}</span>` : ''}
6759          <span style="font-size:11px;color:var(--gray5);margin-right:auto">👍 ${c.votes||0}</span>
6760        </div>
6761        ${c.notes ? `<div style="font-size:11px;color:var(--gray6);margin-top:4px">${c.notes}</div>` : ''}
6762        ${src ? `<div style="font-size:10px;color:var(--gray4);margin-top:3px;direction:ltr;text-align:left">${src}</div>` : ''}
6763      </div>`;
6764    }).join('');
6765
6766  } catch(e) {
6767    document.getElementById('ndcm-coupons').innerHTML =
6768      '<div style="padding:24px;text-align:center;color:#fc8181">שגיאה בטעינה</div>';
6769  }
6770}
6771
6772function _ndCloseCouponsModal() {
6773  document.getElementById('nd-coupons-modal').style.display = 'none';
6774  document.getElementById('ndcm-dropdown').style.display = 'none';
6775  _ndcmSourceId = null;
6776  _ndcmTargetId = null;
6777}
6778
6779function _ndcmFilter() {
6780  const q = (document.getElementById('ndcm-search')?.value || '').toLowerCase().trim();
6781  const dd = document.getElementById('ndcm-dropdown');
6782  if (!q) { dd.style.display = 'none'; return; }
6783
6784  const filtered = _ndcmAllStores.filter(s =>
6785    s.name.toLowerCase().includes(q) ||
6786    (s.store_he || '').toLowerCase().includes(q) ||
6787    (s.domain || '').toLowerCase().includes(q)
6788  ).slice(0, 30);
6789
6790  if (!filtered.length) {
6791    dd.innerHTML = '<div style="padding:10px 14px;font-size:12px;color:var(--gray5)">לא נמצאו חנויות עם דומיין</div>';
6792  } else {
6793    dd.innerHTML = filtered.map(s => `
6794      <div onclick="_ndcmSelectTarget('${s.id}','${s.name.replace(/'/g,"\\'")}','${s.domain}')"
6795        style="padding:9px 14px;cursor:pointer;border-bottom:1px solid var(--gray3);display:flex;justify-content:space-between;font-size:13px;color:var(--gray6)"
6796        onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background=''">
6797        <span><strong style="color:var(--white)">${s.name}</strong>${s.store_he ? ' · ' + s.store_he : ''}</span>
6798        <span style="font-size:11px;color:var(--gray5)">${s.domain}</span>
6799      </div>`).join('');
6800  }
6801  dd.style.display = 'block';
6802}
6803
6804function _ndcmSelectTarget(targetId, targetName, targetDomain) {
6805  _ndcmTargetId = targetId;
6806  document.getElementById('ndcm-search').value = targetName;
6807  document.getElementById('ndcm-dropdown').style.display = 'none';
6808  document.getElementById('ndcm-target-preview').innerHTML =
6809    `➜ <strong style="color:var(--white)">${targetName}</strong> <span style="color:var(--gray5)">(${targetDomain})</span>`;
6810  const btn = document.getElementById('ndcm-confirm-btn');
6811  btn.disabled = false;
6812  btn.style.opacity = '1';
6813}
6814
6815async function _ndcmConfirmMove() {
6816  if (!_ndcmSourceId || !_ndcmTargetId) return;
6817  const targetName = document.getElementById('ndcm-search').value;
6818  if (!confirm(`להעביר את כל הקופונים לחנות "${targetName}" ולמחוק את החנות הנוכחית?`)) return;
6819
6820  const btn = document.getElementById('ndcm-confirm-btn');
6821  btn.disabled = true;
6822  btn.textContent = '⏳ מעביר...';
6823
6824  try {
6825    const r = await fetch(`/api/admin/stores/${_ndcmSourceId}/merge-into/${_ndcmTargetId}`, { method: 'POST' });
6826    if (r.ok) {
6827      const data = await r.json();
6828      _ndCloseCouponsModal();
6829      const dupeNote = data.coupons_deduplicated > 0 ? ` · ${data.coupons_deduplicated} כפולים נמחקו` : '';
6830      showToast(`✓ הועבר לתוך "${data.merged_into}" · ${data.coupons_moved} קופונים${dupeNote}`);
6831      loadNullDomainStores();
6832    } else {
6833      const err = await r.json().catch(() => ({}));
6834      showToast('⚠️ שגיאה: ' + (err.detail || 'העברה נכשלה'));
6835    }
6836  } catch(e) {
6837    showToast('שגיאה בהעברה');
6838  } finally {
6839    btn.disabled = false;
6840    btn.textContent = 'העבר ומחק ⟵';
6841    btn.style.opacity = '1';
6842  }
6843}
6844// ─────────────────────────────────────────────────────────────────────────────
6845
6846async function loadTrustedInternational() {
6847  const tbody = document.getElementById('trusted-intl-tbody');
6848  if (!tbody) return;
6849  tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
6850  try {
6851    const items = await fetch('/api/admin/trusted-international').then(r => r.json());
6852    const badge = document.getElementById('src-trusted-intl-count');
6853    if (badge) badge.textContent = items.length || '0';
6854    if (!items.length) {
6855      tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;color:var(--gray5);padding:28px">אין דומיינים ברשימה</td></tr>`;
6856      return;
6857    }
6858    tbody.innerHTML = items.map((s, i) => {
6859      const date = s.created_at ? new Date(s.created_at).toLocaleDateString('he-IL') : '—';
6860      return `<tr style="border-bottom:1px solid var(--gray3)">
6861        <td style="padding:10px 12px;color:var(--gray5);font-size:12px">${i + 1}</td>
6862        <td style="padding:10px 12px">
6863          <a href="https://${s.domain}" target="_blank" style="color:var(--accent);text-decoration:none;font-weight:600" dir="ltr">${s.domain}</a>
6864        </td>
6865        <td style="padding:10px 12px;color:var(--gray6);font-size:13px">${date}</td>
6866        <td style="padding:10px 12px;text-align:center">
6867          <button class="btn-sm btn-ghost" style="color:#fc8181" onclick="_tiDelete('${s.domain}',this)">מחק</button>
6868        </td>
6869      </tr>`;
6870    }).join('');
6871  } catch(e) {
6872    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
6873  }
6874}
6875
6876async function _tiAddPrompt() {
6877  const domain = prompt('הכנס דומיין בינלאומי (לדוגמה: nike.com):');
6878  if (!domain) return;
6879  try {
6880    const res = await fetch('/api/admin/trusted-international', {
6881      method: 'POST',
6882      headers: {'Content-Type':'application/json'},
6883      body: JSON.stringify({domain: domain.trim()})
6884    });
6885    const data = await res.json();
6886    if (data.ok) { showToast(`✅ ${data.domain} נוסף לרשימה`); loadTrustedInternational(); }
6887    else showToast('❌ שגיאה בהוספה');
6888  } catch(e) { showToast('❌ שגיאה בהוספה'); }
6889}
6890
6891async function _tiDelete(domain, btn) {
6892  if (!confirm(`למחוק את ${domain} מהרשימה?\nקופונים חדשים ממנו דרך promocodis יידלגו.`)) return;
6893  btn.disabled = true;
6894  try {
6895    await fetch(`/api/admin/trusted-international/${encodeURIComponent(domain)}`, {method:'DELETE'});
6896    showToast(`✅ ${domain} הוסר מהרשימה`);
6897    loadTrustedInternational();
6898  } catch(e) { btn.disabled = false; showToast('❌ שגיאה במחיקה'); }
6899}
6900
6901async function loadBlockedSites() {
6902  const tbody = document.getElementById('blocked-sites-tbody');
6903  if (!tbody) return;
6904  tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
6905  try {
6906    const sites = await fetch('/api/admin/blocked-sites').then(r => r.json());
6907    const badge = document.getElementById('src-blocked-count');
6908    if (badge) badge.textContent = sites.length || '0';
6909    if (!sites.length) {
6910      tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--gray5);padding:28px">אין אתרים חסומים</td></tr>`;
6911      return;
6912    }
6913    tbody.innerHTML = sites.map((s, i) => {
6914      const domain = s.domain || (s.url || '').replace(/^https?:\/\/(www\.)?/, '').split('/')[0];
6915      return `<tr style="border-bottom:1px solid var(--gray3)">
6916        <td style="padding:10px 12px;color:var(--gray5);font-size:12px">${i + 1}</td>
6917        <td style="padding:10px 12px">
6918          <a href="${s.url}" target="_blank" style="color:var(--accent);text-decoration:none;font-weight:600">${s.name || domain}</a>
6919          <div style="font-size:11px;color:var(--gray5);margin-top:2px" dir="ltr">${domain}</div>
6920        </td>
6921        <td style="padding:10px 12px;color:var(--gray6);font-size:13px">${s.description || '—'}</td>
6922        <td style="padding:10px 12px;color:var(--gray6);font-size:13px">${s.block_reason || '—'}</td>
6923        <td style="padding:10px 12px;text-align:center">
6924          <button class="btn-sm btn-ghost" onclick="_bsUnblock('${s.id}','${s.name || domain}',this)">↩ הסר חסימה</button>
6925        </td>
6926      </tr>`;
6927    }).join('');
6928  } catch(e) {
6929    tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
6930  }
6931}
6932
6933async function _bsUnblock(siteId, name, btn) {
6934  if (!confirm(`להסיר את החסימה של ${name}?\nהאתר לא יופיע יותר כחסום.`)) return;
6935  btn.disabled = true;
6936  try {
6937    await fetch(`/api/admin/blocked-sites/${siteId}/unblock`, {method:'PATCH'});
6938    showToast(`✅ ${name} הוסר מרשימת האתרים החסומים`);
6939    loadBlockedSites();
6940  } catch(e) {
6941    btn.disabled = false;
6942    showToast('❌ שגיאה בהסרת החסימה');
6943  }
6944}
6945
6946async function loadNoUrlCoupons() {
6947  const tbody = document.getElementById('nourl-tbody');
6948  if (!tbody) return;
6949  tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:var(--gray5);padding:24px">טוען...</td></tr>`;
6950  try {
6951    const res  = await fetch('/api/admin/no-url-coupons');
6952    _noUrlData = await res.json();
6953    _noUrlPage = 0;
6954    _noUrlExpanded.clear();
6955    const badge = document.getElementById('src-nourl-count');
6956    if (badge) badge.textContent = _noUrlData.length || '0';
6957    _renderNoUrlTable();
6958  } catch(e) {
6959    tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:#fc8181;padding:24px">שגיאה בטעינה</td></tr>`;
6960  }
6961}
6962
6963function _noUrlSetGeo(geo, btn) {
6964  _noUrlGeo  = geo;
6965  _noUrlPage = 0;
6966  document.querySelectorAll('.nourl-geo-btn').forEach(b => b.classList.remove('on'));
6967  btn.classList.add('on');
6968  _renderNoUrlTable();
6969}
6970
6971function _noUrlToggle(store) {
6972  if (_noUrlExpanded.has(store)) _noUrlExpanded.delete(store);
6973  else _noUrlExpanded.add(store);
6974  _renderNoUrlTable();
6975}
6976
6977function _renderNoUrlTable() {
6978  const tbody   = document.getElementById('nourl-tbody');
6979  const pagEl   = document.getElementById('nourl-pagination');
6980  if (!tbody) return;
6981
6982  const filtered = _noUrlFilteredData();
6983  const pages    = Math.max(1, Math.ceil(filtered.length / _noUrlPerPg));
6984  if (_noUrlPage >= pages) _noUrlPage = 0;
6985  const slice    = filtered.slice(_noUrlPage * _noUrlPerPg, (_noUrlPage + 1) * _noUrlPerPg);
6986
6987  if (!filtered.length) {
6988    tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:var(--accent);padding:32px">✅ כל החנויות עם קופונים יש להם אתר יעד</td></tr>`;
6989    if (pagEl) pagEl.innerHTML = '';
6990    return;
6991  }
6992
6993  const globalOffset = _noUrlPage * _noUrlPerPg;
6994
6995  tbody.innerHTML = slice.map((r, localIdx) => {
6996    const globalIdx  = globalOffset + localIdx;
6997    const safeStore  = r.store.replace(/'/g,"\\'");
6998    const inputId    = `nourl-domain-${globalIdx}`;
6999    const expanded    = _noUrlExpanded.has(r.store);
7000    const isIL        = _noUrlIsIL(r);
7001    const geoBadge    = isIL
7002      ? `<span style="font-size:10px;background:rgba(0,232,122,0.12);color:var(--accent);border-radius:10px;padding:1px 6px;margin-right:5px">🇮🇱</span>`
7003      : `<span style="font-size:10px;background:rgba(99,179,237,0.12);color:#63b3ed;border-radius:10px;padding:1px 6px;margin-right:5px">🌍</span>`;
7004    const suggested   = _noUrlGuessDomain(r.coupons);
7005
7006    // Build expandable coupon sub-rows
7007    const expandRows = expanded ? r.coupons.map(c => {
7008      const code    = (c.code||'').toUpperCase() === 'NO_CODE' ? '—' : (c.code||'—');
7009      const dt      = c.created_at ? new Date(c.created_at).toLocaleDateString('he-IL',{day:'2-digit',month:'2-digit',year:'2-digit'}) : '';
7010      const fullUrl = _noUrlParseUrl(c.source||'');
7011      let srcLink;
7012      if (fullUrl) {
7013        let domain = fullUrl;
7014        try { domain = new URL(fullUrl).hostname.replace(/^www\./, ''); } catch(e) {}
7015        const isTg = fullUrl.includes('t.me/');
7016        srcLink = `<a href="${fullUrl}" target="_blank" title="${fullUrl}"
7017          style="color:${isTg?'#29b6f6':'var(--accent)'};font-size:11px;white-space:nowrap">
7018          ${isTg?'📨 ':''} ${domain}</a>`;
7019      } else {
7020        srcLink = `<span style="color:var(--gray5);font-size:11px">${(c.source||'—').slice(0,60)}</span>`;
7021      }
7022      return `<tr style="background:rgba(255,255,255,0.02);border-top:1px solid var(--gray3)">
7023        <td></td>
7024        <td style="padding-right:28px;font-size:11px;color:var(--gray5);white-space:nowrap">${dt}</td>
7025        <td style="font-family:monospace;font-size:12px;color:var(--accent2);text-align:center">${code}</td>
7026        <td style="font-size:12px;white-space:nowrap">${c.discount||'—'}</td>
7027        <td style="font-size:11px;color:var(--gray6)">${(c.notes||'').slice(0,50)}</td>
7028        <td colspan="2" style="max-width:300px">${srcLink}</td>
7029      </tr>`;
7030    }).join('') : '';
7031
7032    return `<tr id="nourl-row-${globalIdx}" style="border-bottom:${expanded?'none':'1px solid var(--gray3)'}">
7033      <td style="color:var(--gray5);font-size:11px;text-align:center;white-space:nowrap">${globalIdx+1}</td>
7034      <td style="font-weight:600;color:var(--white)">
7035        ${geoBadge}${r.store}
7036        <button onclick="_noUrlToggle('${safeStore}')"
7037          title="${expanded?'סגור':'פתח קופונים'}"
7038          style="margin-right:8px;background:none;border:1px solid var(--gray4);border-radius:4px;color:var(--gray6);font-size:11px;padding:1px 7px;cursor:pointer;line-height:1.4">
7039          ${expanded ? '▲ סגור' : `▼ ${r.count} קופונים`}
7040        </button>
7041      </td>
7042      <td>
7043        <div style="display:flex;align-items:center;gap:6px">
7044          <input id="${inputId}" type="text" placeholder="example.co.il" value="${suggested}"
7045            style="width:160px;background:var(--gray3);border:1px solid ${suggested?'var(--accent)':'var(--gray4)'};border-radius:6px;padding:5px 8px;color:var(--white);font-size:12px;outline:none;box-sizing:border-box"
7046            onkeydown="if(event.key==='Enter')_noUrlSave(${globalIdx},'${safeStore}')">
7047          ${suggested ? `<span style="font-size:10px;color:var(--accent);white-space:nowrap" title="הוצע אוטומטית מכתובת המקור">✦ הצעה</span>` : ''}
7048        </div>
7049      </td>
7050      <td colspan="4">
7051        <div style="display:flex;gap:6px;align-items:center">
7052          <button class="td-btn" onclick="_noUrlSave(${globalIdx},'${safeStore}')">💾 שמור</button>
7053          <button class="td-btn danger" onclick="_noUrlDelete(${globalIdx},'${safeStore}')">🗑 מחק הכל</button>
7054        </div>
7055      </td>
7056    </tr>
7057    ${expandRows}`;
7058  }).join('');
7059
7060  // Pagination
7061  if (pagEl) {
7062    if (pages <= 1) { pagEl.innerHTML = ''; return; }
7063    const nums = _pageNums(_noUrlPage, pages);
7064    pagEl.innerHTML = `<div style="display:flex;gap:6px;justify-content:center;padding:16px 0;flex-wrap:wrap">` +
7065      nums.map(n => n === '…'
7066        ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
7067        : `<button class="td-btn${n-1===_noUrlPage?' page-active':''}" onclick="_noUrlGoPage(${n-1})" style="min-width:32px">${n}</button>`
7068      ).join('') + `</div>`;
7069  }
7070}
7071
7072function _noUrlGoPage(p) { _noUrlPage = p; _renderNoUrlTable(); }
7073
7074async function _noUrlSave(idx, store) {
7075  const input = document.getElementById(`nourl-domain-${idx}`);
7076  const domain = (input?.value || '').trim();
7077  if (!domain) { showToast('⚠️ הכנס דומיין'); return; }
7078  try {
7079    const res = await fetch('/api/admin/no-url-coupons/set-domain', {
7080      method: 'POST', headers: {'Content-Type':'application/json'},
7081      body: JSON.stringify({ store, domain }),
7082    });
7083    const d = await res.json();
7084    if (!res.ok) { showToast(`❌ ${d.detail||'שגיאה'}`); return; }
7085    showToast(`✅ ${store} → ${d.domain}`);
7086    _noUrlData = _noUrlData.filter(r => r.store !== store);
7087    _noUrlExpanded.delete(store);
7088    const badge = document.getElementById('src-nourl-count');
7089    if (badge) badge.textContent = _noUrlData.length;
7090    _renderNoUrlTable();
7091  } catch(e) { showToast('❌ שגיאת רשת'); }
7092}
7093
7094async function _noUrlDelete(idx, store) {
7095  if (!confirm(`מחוק את כל הקופונים של "${store}"?`)) return;
7096  try {
7097    const res = await fetch('/api/admin/no-url-coupons/delete-store', {
7098      method: 'DELETE', headers: {'Content-Type':'application/json'},
7099      body: JSON.stringify({ store }),
7100    });
7101    const d = await res.json();
7102    if (!res.ok) { showToast(`❌ ${d.detail||'שגיאה'}`); return; }
7103    showToast(`🗑 נמחקו ${d.deleted} קופונים של ${store}`);
7104    _noUrlData = _noUrlData.filter(r => r.store !== store);
7105    _noUrlExpanded.delete(store);
7106    const badge = document.getElementById('src-nourl-count');
7107    if (badge) badge.textContent = _noUrlData.length;
7108    _renderNoUrlTable();
7109  } catch(e) { showToast('❌ שגיאת רשת'); }
7110}
7111
7112// ── Affiliate programs ────────────────────────────────────────────────────────
7113
7114const _AFF_LIST = [
7115  // ── Israeli networks (register once → see catalog of Israeli brands) ────────
7116  { id:'wesell', type:'il-network', icon:'🇮🇱', name:'WeSell',
7117    desc:'הרשת הגדולה ביותר בישראל — מאות קמפיינים, 10,000+ שותפים. הירשם לגלות אילו חנויות ישראליות שם (Castro, Golf, Fox ועוד).',
7118    stores:['Castro','Golf','Fox','Renuar','Adika','ACE','Home Center','Steimatzky','Delta','Vardinon','Shilav','Fattal','Issta'],
7119    commission:'משתנה', url:'https://wesell.co.il' },
7120  { id:'affiracle', type:'il-network', icon:'✨', name:'Affiracle',
7121    desc:'פלטפורמה ישראלית ל-100+ מותגים מקומיים — אופנה, טק ומוצרי בית',
7122    stores:['Castro','Golf','Fox','Renuar','Adika','TerminalX','Delta','ACE','Home Center'],
7123    commission:'משתנה', url:'https://affiracle.com/en/' },
7124  { id:'indoleads', type:'network', icon:'🌐', name:'Indoleads',
7125    desc:'רשת גלובלית עם מספר תוכניות ישראליות מאושרות',
7126    stores:['Ivory'],
7127    commission:'3–8%', url:'https://indoleads.com' },
7128  { id:'tradedoubler', type:'network', icon:'🔁', name:'TradeDoubler',
7129    desc:'רשת אירופאית — Wolt ישראל רשומה כאן רשמית',
7130    stores:['Wolt'],
7131    commission:'משתנה', url:'https://www.tradedoubler.com/en/become-a-publisher/' },
7132  // ── International networks ─────────────────────────────────────────────────
7133  { id:'impact', type:'network', icon:'💼', name:'Impact.com',
7134    desc:'הרשת הגינלאומית הגדולה — מאות מותגים אמריקאיים מהגדולים',
7135    stores:["Nike","Adidas","Target","Walmart","Levi's","HelloFresh","EveryPlate","Booking.com"],
7136    commission:'3–15%', url:'https://app.impact.com/signup/publisher' },
7137  { id:'awin', type:'network', icon:'🌍', name:'Awin',
7138    desc:'הרשת האירופאית המובילה — חזקה במיוחד במותגי אופנה',
7139    stores:['H&M','ASOS','Adidas','Booking.com','Zara'],
7140    commission:'3–12%', url:'https://www.awin.com/gb/publishers' },
7141  { id:'cj', type:'network', icon:'🔗', name:'Commission Junction (CJ)',
7142    desc:'רשת ותיקה עם מותגי יוקרה ואמריקאיים',
7143    stores:["Sephora","Nordstrom","Macy's","Ulta","Levi's","HelloFresh","American Express"],
7144    commission:'3–10%', url:'https://www.cj.com/publisher-signup' },
7145  { id:'rakuten', type:'network', icon:'🛍', name:'Rakuten Advertising',
7146    desc:'מותגי פרמיום — תשלומים אמינים ועמלות טובות',
7147    stores:['Sephora','Nordstrom','Walmart','Ulta'],
7148    commission:'2–10%', url:'https://rakutenadvertising.com/publishers/' },
7149  { id:'shareasale', type:'network', icon:'📊', name:'ShareASale',
7150    desc:'אלפי מותגים קטנים ובינוניים — מגוון רחב',
7151    stores:["Macy's",'Nordstrom'],
7152    commission:'5–20%', url:'https://www.shareasale.com/info/publisher.cfm' },
7153  { id:'admitad', type:'network', icon:'🌏', name:'Admitad',
7154    desc:'חזק במותגים גלובליים וסיניים — AliExpress, Temu ועוד',
7155    stores:['AliExpress','Temu','SHEIN'],
7156    commission:'3–9%', url:'https://publishers.admitad.com/en/register/' },
7157  // ── Direct programs ────────────────────────────────────────────────────────
7158  { id:'amazon', type:'direct', icon:'📦', name:'Amazon Associates',
7159    desc:'1–10% על כמעט כל מוצר. קל לשלב, תמיד רלוונטי.',
7160    stores:['Amazon'],
7161    commission:'1–10%', url:'https://affiliate-program.amazon.com' },
7162  { id:'ebay', type:'direct', icon:'🏷', name:'eBay Partner Network',
7163    desc:'עמלה על כל מכירה שמגיעה דרך הקישור שלך',
7164    stores:['eBay'],
7165    commission:'1–4%', url:'https://partnernetwork.ebay.com' },
7166  { id:'iherb', type:'direct', icon:'🌿', name:'iHerb Affiliate',
7167    desc:'5% עמלה — תשלום בדולרים, מוצרי בריאות ותוספים',
7168    stores:['iHerb'],
7169    commission:'5%', url:'https://www.iherb.com/info/affiliates' },
7170  { id:'shein', type:'direct', icon:'👗', name:'SHEIN Affiliate',
7171    desc:'10–20% עמלה — קהל יעד גדול מאוד בישראל',
7172    stores:['SHEIN'],
7173    commission:'10–20%', url:'https://affiliate.shein.com' },
7174  { id:'booking', type:'direct', icon:'🏨', name:'Booking.com Affiliate',
7175    desc:'25–40% מהעמלה של Booking — מהגבוהים בשוק',
7176    stores:['Booking.com'],
7177    commission:'25–40% מ-Booking', url:'https://www.booking.com/affiliate-program/v2/index.html' },
7178  { id:'temu', type:'direct', icon:'🛒', name:'Temu Affiliate',
7179    desc:'עמלה על רכישות חדשות — קהל צומח מהיר',
7180    stores:['Temu'],
7181    commission:'5–20%', url:'https://www.temu.com/affiliate.html' },
7182  { id:'aliexpress', type:'direct', icon:'🇨🇳', name:'AliExpress Portals',
7183    desc:'תוכנית ישירה של AliExpress — עמלה על כל מוצר',
7184    stores:['AliExpress'],
7185    commission:'3–9%', url:'https://portals.aliexpress.com' },
7186  { id:'ksp', type:'direct', icon:'🖥', name:'KSP שותפים',
7187    desc:'תוכנית שותפים עצמאית — 4% על מחשבים, cookie 24 שעות, מינימום 250 ש"ח',
7188    stores:['KSP'],
7189    commission:'4%', url:'https://ksp.co.il/af/' },
7190  { id:'isrotel', type:'direct', icon:'🏖', name:'Isrotel Affiliate',
7191    desc:'5% על ערך ההזמנה המלא — עסקאות ממוצעות 3,000 ש"ח',
7192    stores:['Isrotel'],
7193    commission:'5%', url:'https://console.isrotel.co.il/aff/signup.php' },
7194  { id:'ivory', type:'direct', icon:'🖨', name:'Ivory Affiliate (Indoleads)',
7195    desc:'3.84% — מחשבים ואלקטרוניקה, דרך Indoleads',
7196    stores:['Ivory'],
7197    commission:'3.84%', url:'https://indoleads.com' },
7198  { id:'wolt', type:'direct', icon:'🍕', name:'Wolt Affiliates (TradeDoubler)',
7199    desc:'תוכנית שותפים רשמית של Wolt — דרך רשת TradeDoubler',
7200    stores:['Wolt'],
7201    commission:'משתנה', url:'https://explore.wolt.com/en/isr/affiliates' },
7202  { id:'10bis', type:'direct', icon:'🍔', name:'10bis שותפים',
7203    desc:'קוד הפניה ועמלה על הזמנות מזון',
7204    stores:['10bis'],
7205    commission:'משתנה', url:'https://www.10bis.co.il' },
7206  { id:'terminalx', type:'direct', icon:'👠', name:'TerminalX שותפים',
7207    desc:'אחד מאתרי האופנה הגדולים בישראל — פנה ישירות',
7208    stores:['TerminalX'],
7209    commission:'5–8%', url:'https://www.terminalx.com' },
7210  { id:'bug', type:'direct', icon:'💻', name:'Bug שותפים',
7211    desc:'אלקטרוניקה — פנה ישירות לאגף שיווק',
7212    stores:['Bug'],
7213    commission:'2–4%', url:'https://www.bug.co.il' },
7214  { id:'superpharm', type:'direct', icon:'💊', name:'Super-Pharm שותפים',
7215    desc:'פארמה וקוסמטיקה — פנה ישירות לאגף שיווק',
7216    stores:['Super-Pharm'],
7217    commission:'3–6%', url:'https://www.super-pharm.co.il' },
7218];
7219
7220// Map store name → affiliate program id (for newsletter table column)
7221const _STORE_AFF_MAP = {};
7222_AFF_LIST.forEach(p =>
7222 p.stores.forEach(s => {
7223  if (!_STORE_AFF_MAP[s]) _STORE_AFF_MAP[s] = [];
7224  _STORE_AFF_MAP[s].push(p.id);
7225}));
7226
7227const _AFF_STATUS_KEY = id => `aff_status_${id}`;
7228const _AFF_STATUS_LABELS = {
7229  '':           { text:'לא התחלתי', bg:'rgba(100,100,100,0.15)', color:'var(--gray5)',   icon:'—' },
7230  'pending':    { text:'בהמתנה לאישור', bg:'rgba(255,228,94,0.12)', color:'var(--accent2)', icon:'⏳' },
7231  'registered': { text:'רשום ✅',     bg:'rgba(0,232,122,0.12)',  color:'var(--accent)',  icon:'✅' },
7232  'rejected':   { text:'נדחה',        bg:'rgba(255,80,80,0.1)',   color:'#ff6b6b',        icon:'✕' },
7233};
7234const _AFF_STATUS_CYCLE = ['', 'pending', 'registered', 'rejected'];
7235
7236function _affGetStatus(id) { return localStorage.getItem(_AFF_STATUS_KEY(id)) || ''; }
7237function _affSetStatus(id, status) {
7238  localStorage.setItem(_AFF_STATUS_KEY(id), status);
7239  // Update the status button in-place — no re-sort, rows stay in position
7240  const row = document.querySelector(`tr[data-aff-id="${id}"]`);
7241  if (row) {
7242    const btn = row.querySelector('.aff-status-btn');
7243    const stl = _AFF_STATUS_LABELS[status] || _AFF_STATUS_LABELS[''];
7244    if (btn) {
7245      btn.style.background = stl.bg;
7246      btn.style.color = stl.color;
7247      btn.style.border = `1px solid ${stl.color}33`;
7248      btn.innerHTML = `${stl.icon} ${stl.text}`;
7249    }
7250  }
7251  // update sidebar badge and stats bar
7252  const reg  = _AFF_LIST.filter(p => _affGetStatus(p.id) === 'registered').length;
7253  const pend = _AFF_LIST.filter(p => _affGetStatus(p.id) === 'pending').length;
7254  const none = _AFF_LIST.filter(p => _affGetStatus(p.id) === '').length;
7255  const el = document.getElementById('src-aff-count');
7256  if (el) el.textContent = reg || '—';
7257  const statsBar = document.getElementById('aff-stats-bar');
7258  if (statsBar) statsBar.innerHTML =
7259    `<span style="color:var(--accent)">${reg}</span> רשומים · ` +
7260    `<span style="color:var(--accent2)">${pend}</span> בהמתנה · ` +
7261    `<span style="color:var(--gray5)">${none}</span> לא התחלתי · ` +
7262    `סה"כ ${_AFF_LIST.length} תוכניות`;
7263}
7264function _affCycleStatus(id) {
7265  const cur = _affGetStatus(id);
7266  const idx = _AFF_STATUS_CYCLE.indexOf(cur);
7267  const next = _AFF_STATUS_CYCLE[(idx + 1) % _AFF_STATUS_CYCLE.length];
7268  _affSetStatus(id, next);
7269}
7270
7271let _affActiveFilter = 'all';
7272let _affSortKey = 'status';
7273let _affSortDir = 1;
7274
7275function _affSetFilter(f, btn) {
7276  _affActiveFilter = f;
7277  document.querySelectorAll('[id^="aff-f-"]').forEach(b => b.classList.remove('on'));
7278  btn.classList.add('on');
7279  _affRender();
7280}
7281function _affSetSortKey(key) {
7282  _affSortKey = key; _affSortDir = 1;
7283  const dirBtn = document.getElementById('aff-sort-dir-btn');
7284  if (dirBtn) dirBtn.textContent = '↑';
7285  _affRender();
7286}
7287function _affToggleDir() {
7288  _affSortDir *= -1;
7289  const dirBtn = document.getElementById('aff-sort-dir-btn');
7290  if (dirBtn) dirBtn.textContent = _affSortDir === 1 ? '↑' : '↓';
7291  _affRender();
7292}
7293
7294function _affRender() {
7295  const tbody = document.getElementById('aff-tbody');
7296  const statsBar = document.getElementById('aff-stats-bar');
7297  if (!tbody) return;
7298
7299  let rows = _AFF_LIST.slice();
7300  const f = _affActiveFilter;
7301  if (f === 'network')    rows = rows.filter(p => p.type === 'network');
7302  if (f === 'direct')     rows = rows.filter(p => p.type === 'direct');
7303  if (f === 'il')         rows = rows.filter(p => p.type === 'il' || p.type === 'il-network');
7304  if (f === 'registered') rows = rows.filter(p => _affGetStatus(p.id) === 'registered');
7305  if (f === 'pending')    rows = rows.filter(p => _affGetStatus(p.id) === 'pending');
7306  if (f === 'none')       rows = rows.filter(p => _affGetStatus(p.id) === '');
7307
7308  // sort
7309  if (_affSortKey === 'name') {
7310    rows.sort((a,b) => _affSortDir * a.name.localeCompare(b.name, 'he'));
7311  } else if (_affSortKey === 'commission') {
7312    rows.sort((a,b) => _affSortDir * (parseFloat(a.commission) - parseFloat(b.commission)));
7313  } else { // status (default): registered → pending → none → rejected
7314    const order = { registered:0, pending:1, '':2, rejected:3 };
7315    rows.sort((a,b) => _affSortDir * ((order[_affGetStatus(a.id)]||2) - (order[_affGetStatus(b.id)]||2)));
7316  }
7317
7318  const total = _AFF_LIST.length;
7319  const reg   = _AFF_LIST.filter(p => _affGetStatus(p.id) === 'registered').length;
7320  const pend  = _AFF_LIST.filter(p => _affGetStatus(p.id) === 'pending').length;
7321  const none  = _AFF_LIST.filter(p => _affGetStatus(p.id) === '').length;
7322  if (statsBar) statsBar.innerHTML =
7323    `<span style="color:var(--accent)">${reg}</span> רשומים · ` +
7324    `<span style="color:var(--accent2)">${pend}</span> בהמתנה · ` +
7325    `<span style="color:var(--gray5)">${none}</span> לא התחלתי · ` +
7326    `סה"כ ${total} תוכניות`;
7327
7328  const typeLabel = { 'il-network':'🇮🇱 רשת ישראלית', network:'🌐 רשת', direct:'🎯 ישיר', il:'🇮🇱 ישראלי' };
7329
7330  tbody.innerHTML = rows.map(p => {
7331    const st = _affGetStatus(p.id);
7332    const stl = _AFF_STATUS_LABELS[st] || _AFF_STATUS_LABELS[''];
7333    const storesHtml = p.stores.length
7334      ? p.stores.map(s => `<span style="display:inline-block;background:var(--gray3);border-radius:4px;padding:1px 6px;font-size:10px;color:var(--gray6);margin:1px 2px">${s}</span>`).join('')
7335      : '<span style="color:var(--gray4);font-size:11px">כללי</span>';
7336    return `<tr data-aff-id="${p.id}" style="border-bottom:1px solid var(--gray2);transition:background .15s" onmouseover="this.style.background='var(--gray2)'" onmouseout="this.style.background=''">
7337      <td style="padding:12px 12px">
7338        <div style="display:flex;align-items:center;gap:8px">
7339          <span style="font-size:20px">${p.icon}</span>
7340          <div>
7341            <div style="font-size:13px;font-weight:600;color:var(--white)">${p.name}</div>
7342            <div style="font-size:11px;color:var(--gray5);margin-top:2px">${p.desc}</div>
7343            <div style="margin-top:3px"><span style="font-size:10px;color:var(--gray4);background:var(--gray2);border-radius:4px;padding:1px 5px">${typeLabel[p.type]||p.type}</span></div>
7344          </div>
7345        </div>
7346      </td>
7347      <td style="padding:12px 12px;max-width:220px">${storesHtml}</td>
7348      <td style="padding:12px 12px;text-align:center;font-size:12px;color:var(--accent2);font-weight:600;white-space:nowrap">${p.commission}</td>
7349      <td style="padding:12px 12px;text-align:center">
7350        <button class="aff-status-btn" onclick="_affCycleStatus('${p.id}')" style="background:${stl.bg};color:${stl.color};border:1px solid ${stl.color}33;border-radius:20px;padding:4px 12px;font-size:11px;cursor:pointer;white-space:nowrap" title="לחץ להחלפת סטטוס">${stl.icon} ${stl.text}</button>
7351      </td>
7352      <td style="padding:12px 12px;text-align:center">
7353        <a href="${p.url}" target="_blank" style="font-size:11px;color:var(--accent);text-decoration:none;background:rgba(0,232,122,0.08);border:1px solid rgba(0,232,122,0.2);border-radius:6px;padding:4px 10px;white-space:nowrap">הירשם →</a>
7354      </td>
7355    </tr>`;
7356  }).join('');
7357
7358  // update sidebar badge
7359  const el = document.getElementById('src-aff-count');
7360  if (el) el.textContent = reg || '—';
7361}
7362
7363// ── Newsletter scraper ───────────────────────────────────────────────────────
7364
7365let _nlStores = [];
7366let _nlActiveFilter = 'all';
7367let _nlSortKey = 'status';
7368let _nlSortDir = 1; // 1 = asc, -1 = desc
7369
7370async function loadNewsletterStores(force = false) {
7371  const tbody = document.getElementById('nl-stores-tbody');
7372  const statsBar = document.getElementById('nl-stats-bar');
7373  if (!tbody) return;
7374  _nlSyncInterval();
7375  if (!force && _nlStores.length) { _nlRender(); return; }
7376  tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:40px;color:var(--gray5)">⏳ בודק תיבת דואר ו-DB...</td></tr>';
7377  if (statsBar) statsBar.textContent = '';
7378  try {
7379    const d = await fetch('/api/admin/newsletter-stores').then(r => r.json());
7380    if (!d.ok) { tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:40px;color:#ff6b6b">שגיאה: ${d.error||'?'}</td></tr>`; return; }
7381    _nlStores = d.stores || [];
7382    NL.page = 0;
7383    _nlRender();
7384  } catch(e) {
7385    tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:40px;color:#ff6b6b">שגיאת חיבור</td></tr>';
7386  }
7387}
7388
7389function _nlSetFilter(f, btn) {
7390  _nlActiveFilter = f;
7391  NL.page = 0;
7392  document.querySelectorAll('#nl-filter-all,#nl-filter-il,#nl-filter-intl,#nl-filter-reg,#nl-filter-unreg').forEach(b => b.classList.remove('on'));
7393  btn.classList.add('on');
7394  _nlRender();
7395}
7396
7397function _nlSort(key) {
7398  if (_nlSortKey === key) { _nlSortDir *= -1; } else { _nlSortKey = key; _nlSortDir = 1; }
7399  NL.page = 0;
7400  const sel = document.getElementById('nl-sort-sel');
7401  if (sel) sel.value = key;
7402  const dirBtn = document.getElementById('nl-sort-dir-btn');
7403  if (dirBtn) dirBtn.textContent = _nlSortDir === 1 ? '↑' : '↓';
7404  _nlRender();
7405}
7406function _nlSetSortKey(key) {
7407  _nlSortKey = key; _nlSortDir = 1; NL.page = 0;
7408  const dirBtn = document.getElementById('nl-sort-dir-btn');
7409  if (dirBtn) dirBtn.textContent = '↑';
7410  _nlRender();
7411}
7412function _nlToggleDir() {
7413  _nlSortDir *= -1; NL.page = 0;
7414  const dirBtn = document.getElementById('nl-sort-dir-btn');
7415  if (dirBtn) dirBtn.textContent = _nlSortDir === 1 ? '↑' : '↓';
7416  _nlRender();
7417}
7418
7419function _nlRender() {
7420  const tbody = document.getElementById('nl-stores-tbody');
7421  const statsBar = document.getElementById('nl-stats-bar');
7422  if (!tbody) return;
7423
7424  let rows = _nlStores.slice();
7425
7426  // filter
7427  const _isNlIL = s => s.domain.endsWith('.co.il') || s.domain.endsWith('.il') ||
7428    ['10bis','shufersal','ksp','castro','golf','bug','ivory','isrotel','fattal','issta',
7429     'terminalx','delta','vardinon','shilav','renuar','adika','superpharm','super-pharm','ace'].some(k => s.domain.includes(k));
7430  if (_nlActiveFilter === 'il')    rows = rows.filter(s => _isNlIL(s));
7431  if (_nlActiveFilter === 'intl')  rows = rows.filter(s => !_isNlIL(s));
7432  if (_nlActiveFilter === 'reg')   rows = rows.filter(s => s.registered);
7433  if (_nlActiveFilter === 'unreg') rows = rows.filter(s => !s.registered);
7434
7435  // sort
7436  rows.sort((a, b) => {
7437    let va, vb;
7438    if (_nlSortKey === 'name')    { va = a.name.toLowerCase(); vb = b.name.toLowerCase(); }
7439    else if (_nlSortKey === 'status')  { va = a.active ? 0 : a.registered ? 1 : 2; vb = b.active ? 0 : b.registered ? 1 : 2; }
7440    else if (_nlSortKey === 'emails')  { va = a.email_count; vb = b.email_count; }
7441    else if (_nlSortKey === 'coupons') { va = a.coupon_count; vb = b.coupon_count; }
7442    else if (_nlSortKey === 'date')    { va = a.last_email_ts; vb = b.last_email_ts; }
7443    if (va < vb) return -1 * _nlSortDir;
7444    if (va > vb) return  1 * _nlSortDir;
7445    return 0;
7446  });
7447
7448  // stats bar
7449  const total = _nlStores.length;
7450  const regCount = _nlStores.filter(s => s.registered).length;
7451  const activeCount = _nlStores.filter(s => s.active).length;
7452  const newCount = _nlStores.filter(s => s.newsletter_new).length;
7453  const totalCoupons = _nlStores.reduce((s, x) => s + x.coupon_count, 0);
7454  if (statsBar) statsBar.innerHTML =
7455    `<span style="color:var(--accent)">${activeCount}</span> פעילות · ` +
7456    `<span style="color:var(--gray6)">${regCount}</span> רשומות · ` +
7457    `<span style="color:var(--gray5)">${total - regCount}</span> לא רשומות · ` +
7458    `<span style="color:var(--accent2)">${totalCoupons}</span> קופונים סה"כ מניוזלטרים` +
7459    (newCount ? ` · <span style="color:#ffb347;font-weight:600">⚡ ${newCount} חדשות</span>` : '');
7460  const nlBadge = document.getElementById('src-nl-count');
7461  if (nlBadge) nlBadge.textContent = activeCount || '0';
7462
7463  if (!rows.length) {
7464    tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;padding:40px;color:var(--gray5)">אין תוצאות</td></tr>';
7465    document.getElementById('nl-pagination').innerHTML = '';
7466    return;
7467  }
7468
7469  // pagination
7470  const nlPages = Math.max(1, Math.ceil(rows.length / NL.perPage));
7471  if (NL.page >= nlPages) NL.page = nlPages - 1;
7472  const nlSlice = rows.slice(NL.page * NL.perPage, (NL.page + 1) * NL.perPage);
7473
7474  const pgDiv = document.getElementById('nl-pagination');
7475  if (pgDiv) {
7476    if (nlPages <= 1) { pgDiv.innerHTML = ''; } else {
7477      const nums = _pageNums(NL.page, nlPages);
7478      const numBtns = nums.map(n => n === '…'
7479        ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
7480        : `<button class="td-btn${n-1===NL.page?' page-active':''}" onclick="nlGoPage(${n-1})" style="min-width:32px">${n}</button>`
7481      ).join('');
7482      pgDiv.innerHTML = `
7483        <div style="display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 18px 14px;border-top:1px solid var(--gray3);flex-wrap:wrap">
7484          <button class="td-btn" onclick="nlGoPage(${NL.page-1})" ${NL.page===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
7485          ${numBtns}
7486          <button class="td-btn" onclick="nlGoPage(${NL.page+1})" ${NL.page>=nlPages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
7487        </div>`;
7488    }
7489  }
7490
7491  tbody.innerHTML = nlSlice.map((s, idx) => {
7492    const rowNum = NL.page * NL.perPage + idx + 1;
7493    const statusDot = s.active
7494      ? `<span style="display:inline-flex;align-items:center;gap:5px;background:rgba(0,232,122,0.12);border:1px solid rgba(0,232,122,0.3);border-radius:20px;padding:2px 8px;font-size:11px;color:var(--accent)"><span style="width:6px;height:6px;border-radius:50%;background:var(--accent);display:inline-block;animation:pulse 2s infinite"></span>פעיל</span>`
7495      : s.registered
7496        ? `<span style="display:inline-flex;align-items:center;gap:5px;background:rgba(255,228,94,0.1);border:1px solid rgba(255,228,94,0.3);border-radius:20px;padding:2px 8px;font-size:11px;color:var(--accent2)">⚠ רשום / לא פעיל</span>`
7497        : `<span style="display:inline-flex;align-items:center;gap:5px;background:rgba(255,80,80,0.08);border:1px solid rgba(255,80,80,0.2);border-radius:20px;padding:2px 8px;font-size:11px;color:#ff6b6b">✕ לא רשום</span>`;
7498
7499    const lastDate = s.last_email_ts
7500      ? new Date(s.last_email_ts * 1000).toLocaleDateString('he-IL', {day:'2-digit',month:'2-digit',year:'2-digit'})
7501      : '—';
7502
7503    const _NL_SITE_OVERRIDE = {
7504      'golf.co.il':          'https://www.golf-il.co.il',
7505      'adika.co.il':         'https://www.adikastyle.com',
7506      'americanexpress.co.il': 'https://www.americanexpress.co.il',
7507      'americanexpress.com':   'https://www.americanexpress.co.il',
7508      'aexp.com':              'https://www.americanexpress.co.il',
7509    };
7510    const storeUrl = _NL_SITE_OVERRIDE[s.domain] || `https://www.${s.domain}`;
7511    const couponBadge = s.coupon_count > 0
7512      ? `<span style="background:rgba(0,232,122,0.15);color:var(--accent);border-radius:12px;padding:2px 8px;font-size:12px;font-weight:600">${s.coupon_count}</span>`
7513      : `<span style="color:var(--gray4);font-size:12px">0</span>`;
7514
7515    // Affiliate column
7516    const affIds = _STORE_AFF_MAP[s.name] || [];
7517    let affCell = '<span style="color:var(--gray4);font-size:11px">—</span>';
7518    if (affIds.length) {
7519      const affStatus = _affGetStatus(affIds[0]);
7520      const stl = _AFF_STATUS_LABELS[affStatus] || _AFF_STATUS_LABELS[''];
7521      const prog = _AFF_LIST.find(p => p.id === affIds[0]);
7522      const label = prog ? prog.name.split(' ')[0] : affIds[0];
7523      affCell = `<button onclick="_affCycleStatus('${affIds[0]}');_nlRender()" title="${prog?prog.name:affIds[0]}" style="background:${stl.bg};color:${stl.color};border:1px solid ${stl.color}33;border-radius:12px;padding:2px 9px;font-size:10px;cursor:pointer;white-space:nowrap">${stl.icon} ${label}</button>`;
7524    }
7525
7526    // Actions column:
7527    // • Active (got emails)      → green "✓ רשום" info badge, no need to re-register
7528    // • Manually registered      → green "✓ רשום" toggle (click = unmark)
7529    // • Not registered at all    → yellow "הירשם →" link only (clean, no gray clutter)
7530    const manualBtn = s.manual_registered
7531      ? `<button onclick="_nlToggleReg('${s.domain}',this)" title="בטל רישום ידני" style="font-size:10px;padding:2px 7px;border-radius:6px;border:1px solid rgba(0,232,122,0.4);background:rgba(0,232,122,0.1);color:var(--accent);cursor:pointer">✓ רשום</button>`
7532      : s.active
7533        ? `<span style="font-size:10px;padding:2px 7px;border-radius:6px;border:1px solid rgba(0,232,122,0.3);background:rgba(0,232,122,0.08);color:var(--accent)">✓ רשום</span>`
7534        : '';  // unregistered: no gray toggle — just הירשם below
7535    const signupLink = s.registered
7536      ? ''
7537      : `<a href="${storeUrl}" target="_blank" style="font-size:11px;color:var(--accent2);text-decoration:none;background:rgba(255,228,94,0.08);border:1px solid rgba(255,228,94,0.2);border-radius:6px;padding:3px 8px" title="פתח אתר להרשמה לניוזלטר">הירשם →</a>`;
7538    return `<tr style="border-bottom:1px solid var(--gray2);transition:background .15s" onmouseover="this.style.background='var(--gray2)'" onmouseout="this.style.background=''">
7539      <td style="padding:10px 8px;text-align:center;font-size:11px;color:var(--gray4)">${rowNum}</td>
7540      <td style="padding:10px 12px;font-size:13px;font-weight:500;color:var(--white)">${s.newsletter_new ? `<span style="display:inline-block;background:rgba(255,165,0,0.18);color:#ffb347;border:1px solid rgba(255,165,0,0.4);border-radius:6px;padding:1px 7px;font-size:10px;font-weight:700;margin-bottom:3px;letter-spacing:.5px">NEW</span><br>` : ''}${s.name}<div style="font-size:11px;color:var(--gray4);direction:ltr">${s.domain}</div></td>
7541      <td style="padding:10px 12px;text-align:center">${statusDot}</td>
7542      <td style="padding:10px 12px;text-align:center;font-size:13px;color:${s.email_count>0?'var(--white)':'var(--gray4)'}">${s.email_count||'—'}</td>
7543      <td style="padding:10px 12px;text-align:center">${couponBadge}</td>
7544      <td style="padding:10px 12px;text-align:center;font-size:12px;color:var(--gray5)">${lastDate}</td>
7545      <td style="padding:10px 12px;text-align:center">${affCell}</td>
7546      <td style="padding:10px 12px;text-align:center">
7547        <div style="display:flex;gap:5px;justify-content:center;align-items:center;flex-wrap:wrap">${manualBtn}${signupLink}${s.newsletter_new ? `<button onclick="_nlDismissNew('${s.name.replace(/'/g,"\\'")}',this)" title="סמן כבדוק" style="font-size:10px;padding:2px 7px;border-radius:6px;border:1px solid rgba(255,165,0,0.4);background:rgba(255,165,0,0.12);color:#ffb347;cursor:pointer">✓ בדקתי</button>` : ''}</div>
7548      </td>
7549    </tr>`;
7550  }).join('');
7551}
7552
7553// Override filter buttons to use _nlSetFilter (static HTML uses onclick)
7554function _nlFilter(f, btn) { _nlSetFilter(f, btn); }
7555
7556async function _nlDismissNew(name, btn) {
7557  btn.disabled = true;
7558  try {
7559    const res = await fetch('/api/admin/newsletter-stores/dismiss-new', {
7560      method: 'POST',
7561      headers: {'Content-Type':'application/json'},
7562      body: JSON.stringify({name})
7563    }).then(r => r.json());
7564    if (res.ok) {
7565      const s = _nlStores.find(x => x.name === name);
7566      if (s) s.newsletter_new = false;
7567      _nlRender();
7568    }
7569  } catch(e) { btn.disabled = false; }
7570}
7571
7572async function _nlToggleReg(domain, btn) {
7573  btn.disabled = true;
7574  try {
7575    const res = await fetch('/api/admin/newsletter-stores/toggle-registered', {
7576      method: 'POST',
7577      headers: {'Content-Type': 'application/json'},
7578      body: JSON.stringify({domain})
7579    }).then(r => r.json());
7580    if (res.ok) {
7581      // Update local store data and re-render
7582      const s = _nlStores.find(x => x.domain === domain);
7583      if (s) {
7584        s.manual_registered = res.registered;
7585        s.registered = s.email_count > 0 || res.registered;
7586        s.active = s.registered;
7587      }
7588      _nlRender();
7589    }
7590  } finally {
7591    btn.disabled = false;
7592  }
7593}
7594
7595async function autoSignupNewsletters(btn) {
7596  btn.disabled = true;
7597  const orig = btn.innerHTML;
7598  btn.innerHTML = '⏳ מתחבר לחנויות...';
7599
7600  try {
7601    const res = await fetch('/api/admin/newsletter-signup/auto', {method:'POST'}).then(r=>r.json());
7602
7603    // Build detailed results modal
7604    const ok      = res.results.filter(r => r.status === 'ok');
7605    const skipped = res.results.filter(r => r.status === 'skipped');
7606    const failed  = res.results.filter(r => r.status === 'failed');
7607
7608    const methodIcon = m => m === 'klaviyo' ? '🎹' : m === 'mailchimp' ? '🐒' : m === 'form' ? '📝' : '?';
7609
7610    const rows = (list, color, icon) => list.map(r =>
7611      `<tr style="border-bottom:1px solid rgba(255,255,255,0.05)">
7612        <td style="padding:6px 10px;color:${color};font-size:13px">${icon}</td>
7613        <td style="padding:6px 10px;font-size:13px;font-weight:500">${r.store}</td>
7614        <td style="padding:6px 10px;font-size:11px;color:var(--gray5);direction:ltr">${r.domain}</td>
7615        <td style="padding:6px 10px;font-size:11px;color:var(--gray4)">${r.method ? methodIcon(r.method)+' '+r.method : r.reason || '—'}</td>
7616      </tr>`
7617    ).join('');
7618
7619    const modal = document.createElement('div');
7620    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px';
7621    modal.innerHTML = `
7622      <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;max-width:720px;width:100%;max-height:80vh;overflow:hidden;display:flex;flex-direction:column">
7623        <div style="padding:20px 24px;border-bottom:1px solid var(--gray3);display:flex;justify-content:space-between;align-items:center">
7624          <div>
7625            <div style="font-size:17px;font-weight:700">תוצאות רישום אוטומטי לניוזלטרים</div>
7626            <div style="font-size:12px;color:var(--gray5);margin-top:4px">
7627              <span style="color:#00e87a">✅ ${ok.length} הצליחו</span> &nbsp;·&nbsp;
7628              <span style="color:var(--gray4)">⏭ ${skipped.length} כבר רשומים</span> &nbsp;·&nbsp;
7629              <span style="color:#ff6b6b">❌ ${failed.length} נכשלו</span>
7630            </div>
7631          </div>
7632          <button onclick="this.closest('div[style*=fixed]').remove()" style="background:var(--gray3);border:none;color:var(--gray5);width:32px;height:32px;border-radius:50%;cursor:pointer;font-size:16px">✕</button>
7633        </div>
7634        <div style="overflow-y:auto;flex:1">
7635          ${ok.length ? `
7636          <div style="padding:12px 24px 4px;font-size:11px;font-weight:700;color:#00e87a;letter-spacing:1px">✅ הצליחו (${ok.length})</div>
7637          <table style="width:100%;border-collapse:collapse">${rows(ok,'#00e87a','✅')}</table>` : ''}
7638          ${failed.length ? `
7639          <div style="padding:12px 24px 4px;font-size:11px;font-weight:700;color:#ff6b6b;letter-spacing:1px">❌ נכשלו (${failed.length})</div>
7640          <table style="width:100%;border-collapse:collapse">${rows(failed,'#ff6b6b','❌')}</table>` : ''}
7641          ${skipped.length ? `
7642          <div style="padding:12px 24px 4px;font-size:11px;font-weight:700;color:var(--gray4);letter-spacing:1px">⏭ כבר רשומים (${skipped.length})</div>
7643          <table style="width:100%;border-collapse:collapse">${rows(skipped,'var(--gray4)','⏭')}</table>` : ''}
7644        </div>
7645        <div style="padding:14px 24px;border-top:1px solid var(--gray3);text-align:center">
7646          <button onclick="this.closest('div[style*=fixed]').remove()" style="background:var(--accent);color:#000;border:none;border-radius:8px;padding:8px 28px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif">סגור</button>
7647        </div>
7648      </div>`;
7649    document.body.appendChild(modal);
7650
7651    btn.innerHTML = `✅ ${ok.length} הצליחו · ${skipped.length} כבר רשומים · ${failed.length} נכשלו`;
7652    _nlLoad();
7653  } catch(e) {
7654    btn.innerHTML = '❌ שגיאה: ' + e.message;
7655  } finally {
7656    setTimeout(() => { btn.innerHTML = orig; btn.disabled = false; }, 6000);
7657  }
7658}
7659
7660async function scanNewsletters(btn, days) {
7661  btn.disabled = true;
7662  const orig = btn.textContent;
7663  btn.textContent = '⏳ סורק מיילים...';
7664  const resultEl = document.getElementById('nl-result');
7665  const resultText = document.getElementById('nl-result-text');
7666  if (resultEl) resultEl.style.display = 'none';
7667  try {
7668    const res = await fetch(`/api/admin/scan-newsletters?days=${days}`, { method: 'POST' });
7669    const d = await res.json();
7670    if (!d.ok) {
7671      showToast(`❌ שגיאה: ${d.error}`);
7672    } else {
7673      if (resultEl && resultText) {
7674        resultText.innerHTML = `✅ נסרקו <strong>${d.emails_scanned}</strong> מיילים · נמצאו <strong>${d.coupons_found}</strong> קופונים · נשמרו <strong>${d.coupons_saved}</strong> חדשים`;
7675        resultEl.style.display = 'block';
7676      }
7677      if (d.coupons_saved > 0) { loadCoupons(); loadStats(); }
7678    }
7679  } catch(e) {
7680    showToast('❌ שגיאה בסריקת ניוזלטרים');
7681  } finally {
7682    btn.disabled = false;
7683    btn.textContent = orig;
7684  }
7685}
7686
7687async function setNlInterval(select) {
7688  const seconds = parseInt(select.value);
7689  try {
7690    const d = await fetch('/api/admin/newsletter-watcher/set-interval', {
7691      method: 'POST',
7692      headers: {'Content-Type': 'application/json'},
7693      body: JSON.stringify({seconds})
7694    }).then(r => r.json());
7695    if (d.ok) showToast('✅ מרווח הסריקה עודכן');
7696    else showToast('❌ שגיאה: ' + d.error);
7697  } catch(e) { showToast('❌ שגיאת רשת'); }
7698}
7699
7700async function _nlSyncInterval() {
7701  try {
7702    const d = await fetch('/api/admin/newsletter-watcher/status').then(r => r.json());
7703    const sel = document.getElementById('nl-interval-select');
7704    if (sel && d.poll_interval_seconds) {
7705      const opt = [...sel.options].find(o => parseInt(o.value) === d.poll_interval_seconds);
7706      if (opt) sel.value = opt.value;
7707    }
7708  } catch(e) {}
7709}
7710
7711// ── Feature 5: Scrape full promocode.co.il ───────────────────────────────────
7712
7713let _promoPoller = null;
7714
7715async function scrapePromocodeAll(btn) {
7716  // If already running, just open the progress modal
7717  const st = await fetch('/api/admin/scrape-promocode/status').then(r=>r.json()).catch(()=>null);
7718  if (st && st.running) { _showPromoProgress(st); _startPromoPoller(); return; }
7719
7720  btn.disabled = true;
7721  btn.textContent = '⏳ מתחיל...';
7722  try {
7723    const res = await fetch('/api/admin/scrape-promocode', { method: 'POST' });
7724    const d = await res.json();
7725    if (!d.ok && d.msg) { showToast('⚠️ ' + d.msg); btn.disabled=false; btn.textContent='🔄 סרוק את כל promocode.co.il'; return; }
7726    _showPromoProgress(d.status);
7727    _startPromoPoller();
7728  } catch(e) {
7729    showToast('❌ שגיאה בהתחלת הסריקה');
7730    btn.disabled = false;
7731    btn.textContent = '🔄 סרוק את כל promocode.co.il';
7732  }
7733}
7734
7735function _showPromoProgress(st) {
7736  let modal = document.getElementById('promo-progress-modal');
7737  if (!modal) {
7738    modal = document.createElement('div');
7739    modal.id = 'promo-progress-modal';
7740    modal.style.cssText = 'position:fixed;bottom:24px;left:24px;background:var(--gray2);border:1px solid var(--gray3);border-radius:12px;padding:18px 22px;min-width:300px;z-index:9999;box-shadow:0 8px 32px #0008;direction:rtl';
7741    document.body.appendChild(modal);
7742  }
7743  const pagesDone  = st.pages_done  ?? 0;
7744  const found      = st.found       ?? 0;
7745  const saved      = st.saved       ?? 0;
7746  const pagesKnown = st.pages_known ?? false;
7747  const pagesTotal = pagesKnown ? (st.pages_total ?? pagesDone) : null;
7748  const pct        = pagesTotal ? Math.round(pagesDone / pagesTotal * 100) : 0;
7749  const pagesLabel = pagesTotal ? `${pagesDone} / ${pagesTotal}` : `${pagesDone}+`;
7750  const done = st.finished_at && !st.running;
7751  modal.innerHTML = `
7752    <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
7753      <span style="font-weight:600;font-size:14px">${done ? '✅ סריקה הושלמה' : '🔄 סורק promocode.co.il...'}</span>
7754      ${done ? `<button onclick="document.getElementById('promo-progress-modal').remove()" style="background:none;border:none;color:var(--gray5);font-size:16px;cursor:pointer">✕</button>` : ''}
7755    </div>
7756    <div style="background:var(--gray3);border-radius:6px;height:8px;margin-bottom:10px;overflow:hidden">
7757      <div style="background:var(--accent);height:100%;width:${pct}%;transition:width .3s;border-radius:6px"></div>
7758    </div>
7759    <div style="font-size:13px;color:var(--gray6);display:flex;gap:16px;flex-wrap:wrap">
7760      <span>📄 ${pagesLabel} דפים</span>
7761      <span>🎫 ${found} נמצאו</span>
7762      <span>💾 ${saved} נשמרו</span>
7763    </div>
7764    ${st.error ? `<div style="color:#ff6b6b;font-size:12px;margin-top:8px">שגיאה: ${st.error}</div>` : ''}
7765  `;
7766}
7767
7768function _startPromoPoller() {
7769  if (_promoPoller) return;
7770  const btn = document.querySelector('[onclick*="scrapePromocodeAll"]');
7771  _promoPoller = setInterval(async () => {
7772    try {
7773      const st = await fetch('/api/admin/scrape-promocode/status').then(r=>r.json());
7774      _showPromoProgress(st);
7775      if (!st.running) {
7776        clearInterval(_promoPoller); _promoPoller = null;
7777        if (btn) { btn.disabled=false; btn.textContent='🔄 סרוק את כל promocode.co.il'; }
7778        loadCoupons(); loadStats();
7779      }
7780    } catch(e) { clearInterval(_promoPoller); _promoPoller = null; }
7781  }, 3000);
7782}
7783
7784// On page load, resume progress display if a scrape is already running
7785(async () => {
7786  try {
7787    const st = await fetch('/api/admin/scrape-promocode/status').then(r=>r.json());
7788    if (st.running) { _showPromoProgress(st); _startPromoPoller(); }
7789  } catch(e) {}
7790})();
7791
7792async function runBackfill(btn) {
7793  btn.disabled = true;
7794  const orig = btn.textContent;
7795  btn.textContent = '⏳ סורק...';
7796  try {
7797    const res = await fetch('/api/admin/backfill-pending', { method: 'POST' });
7798    const d = await res.json();
7799    showToast(`✅ זוהו ${d.moved_to_pending} קופונים ממקורות לא מוכרים → הועברו לבדיקה`);
7800    loadPendingView();
7801  } catch(e) {
7802    showToast('❌ שגיאה בזיהוי מקורות');
7803  } finally {
7804    btn.disabled = false;
7805    btn.textContent = orig;
7806  }
7807}
7808
7809async function fixRecords(btn) {
7810  btn.disabled = true;
7811  btn.textContent = '🔧 מתקן...';
7812  try {
7813    const res = await fetch('/api/admin/fix-records', { method: 'POST' });
7814    const d   = await res.json();
7815    const t   = document.getElementById('toast');
7816    t.textContent = `✅ תוקנו: ${d.fixed_codes} קודים, ${d.fixed_notes} הערות תורגמו, ${d.deleted_bad} רשומות נמחקו`;
7817    t.classList.add('show');
7818    setTimeout(() => t.classList.remove('show'), 4000);
7819    loadCoupons(); loadStats();
7820  } catch(e) {
7821    alert('שגיאה בתיקון רשומות');
7822  } finally {
7823    btn.disabled = false;
7824    btn.textContent = '🔧 תקן רשומות';
7825  }
7826}
7827
7828async function runDedup(btn) {
7829  btn.disabled = true;
7830  btn.textContent = '🧹 מנקה...';
7831  try {
7832    const res = await fetch('/api/admin/dedup', { method: 'POST' });
7833    const d   = await res.json();
7834    const t   = document.getElementById('toast');
7835    t.textContent = `✅ כפילויות: ${d.duplicates_deleted} נמחקו מ-${d.duplicate_groups_merged} קבוצות · ${d.store_names_normalised} שמות חנויות תוקנו`;
7836    t.classList.add('show');
7837    setTimeout(() => t.classList.remove('show'), 5000);
7838    loadCoupons(); loadStats();
7839  } catch(e) {
7840    alert('שגיאה בניקוי כפילויות');
7841  } finally {
7842    btn.disabled = false;
7843    btn.textContent = '🧹 נקה כפילויות';
7844  }
7845}
7846
7847async function runFixPublishedDates(btn) {
7848  btn.disabled = true;
7849  const orig = btn.textContent;
7850  btn.textContent = '📅 סורק תאריכים...';
7851  try {
7852    const res = await fetch('/api/admin/fix-published-dates', { method: 'POST' });
7853    const d   = await res.json();
7854    showToast(
7855      `✅ עודכנו ${d.coupons_updated} קופונים עם תאריך · ` +
7856      `${d.pages_with_date_found}/${d.unique_source_pages} דפים עם תאריך · ` +
7857      `${d.coupons_no_date_found} ללא תאריך`
7858    );
7859  } catch(e) {
7860    showToast('❌ שגיאה בתיקון תאריכי פרסום');
7861  } finally {
7862    btn.disabled = false;
7863    btn.textContent = orig;
7864  }
7865}
7866
7867async function runScanInstagram(btn) {
7868  btn.disabled = true;
7869  const orig = btn.textContent;
7870  btn.textContent = '📸 סורק אינסטגרם...';
7871  try {
7872    const res = await fetch('/api/admin/scan-instagram', { method: 'POST' });
7873    const d   = await res.json();
7874    if (d.error) {
7875      showToast(`⚠️ ${d.error}`);
7876    } else {
7877      showToast(`✅ נמצאו ${d.found} קופונים · ${d.posts_fetched} פוסטים מ־${d.profiles_scraped} פרופילים`);
7878      if (d.found > 0) loadAdminData();
7879    }
7880  } catch(e) {
7881    showToast('❌ שגיאה בסריקת אינסטגרם');
7882  } finally {
7883    btn.disabled = false;
7884    btn.textContent = orig;
7885  }
7886}
7887
7888
7889// ── Sources & Web admin views (live data) ─────────────────────────────────────
7890let _srcData = null;
7891
7892function _relTime(iso) {
7893  if (!iso) return '—';
7894  const diff = Date.now() - new Date(iso).getTime();
7895  const min  = Math.floor(diff / 60000);
7896  if (min < 2)   return 'עכשיו';
7897  if (min < 60)  return `לפני ${min} דק׳`;
7898  const hr = Math.floor(min / 60);
7899  if (hr < 24)   return `לפני ${hr} שע׳`;
7900  const days = Math.floor(hr / 24);
7901  if (days < 30) return `לפני ${days} ימים`;
7902  return new Date(iso).toLocaleDateString('he-IL');
7903}
7904
7905async function _fetchSourceData(force = false) {
7906  if (_srcData && !force) return _srcData;
7907  const r = await fetch('/api/admin/sources');
7908  _srcData = await r.json();
7909  const tgEl  = document.getElementById('src-tg-count');
7910  const webEl = document.getElementById('src-web-count');
7911  if (tgEl)  tgEl.textContent  = _srcData.telegram.length;
7912  if (webEl) webEl.textContent = _srcData.web.length;
7913  return _srcData;
7914}
7915
7916function _renderTelegramTable(data) {
7917  if (!data) return;
7918  const tbody = document.getElementById('tg-sources-tbody');
7919  if (!tbody) return;
7920  if (!data.telegram.length) {
7921    tbody.innerHTML = `<tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:24px">אין ערוצי טלגרם — לחץ "+ הוסף ערוץ" כדי להתחיל</td></tr>`;
7922    return;
7923  }
7924  const sortVal = document.getElementById('tg-sort')?.value || 'count';
7925  const sorted = [...data.telegram].sort((a,b) => {
7926    if (sortVal === 'count')  return b.count - a.count;
7927    if (sortVal === 'recent') return (b.last_scan||'').localeCompare(a.last_scan||'');
7928    if (sortVal === 'name')   return a.name.localeCompare(b.name, 'he');
7929    return 0;
7930  });
7931  tbody.innerHTML = sorted.map((ch, idx) => {
7932    const hasUrl   = ch.url && ch.url.startsWith('http');
7933    const safeUrl  = (ch.url || '').replace(/'/g, "\\'");
7934    const safeName = ch.name.replace(/'/g, "\\'");
7935    const tgIcon   = `<svg width="11" height="11" viewBox="0 0 24 24" fill="#29b6f6" style="vertical-align:-1px;margin-left:4px"><path d="M11.944 0A12 12 0 1 0 24 12 12 12 0 0 0 11.944 0zm5.992 8.287-2.021 9.525c-.148.658-.55.818-1.113.508l-3-2.21-1.448 1.393c-.16.16-.295.295-.605.295l.213-3.052 5.56-5.023c.242-.214-.054-.333-.373-.12l-6.871 4.326-2.962-.924c-.644-.204-.657-.644.136-.953l11.574-4.462c.537-.194 1.006.131.91.697z"/></svg>`;
7936    const nameHtml = hasUrl
7937      ? `<a href="${ch.url}" target="_blank" style="color:#29b6f6;text-decoration:none">${tgIcon}${ch.name}</a>`
7938      : `<span style="color:#29b6f6">${tgIcon}${ch.name}</span>`;
7939    const editBtn = `<button class="td-btn" onclick="openEditSourceModal('${safeName}','${safeUrl}')">ערוך</button>`;
7940    const actions = hasUrl
7941      ? `${editBtn}<button class="td-btn" onclick="scanSource('${safeUrl}',this)">סרוק</button>
7942         <button class="td-btn danger" onclick="removeSource('${safeUrl}',this)">הסר</button>`
7943      : `${editBtn}<button class="td-btn danger" onclick="removeTgByName('${safeName}',this)">הסר</button>`;
7944    const newBadge = (ch.new_7d > 0)
7945      ? `<span style="color:#ffe45e;font-weight:600">+${ch.new_7d}</span>`
7946      : `<span style="color:var(--gray5)">0</span>`;
7947    return `<tr>
7948      <td style="color:var(--gray5);font-size:11px;text-align:center">${idx+1}</td>
7949      <td class="td-store">${nameHtml}</td>
7950      <td style="text-align:center">${ch.count}</td>
7951      <td style="text-align:center">${newBadge}</td>
7952      <td>${_relTime(ch.last_scan)}</td>
7953      <td><span style="color:var(--accent)">פעיל</span></td>
7954      <td><div class="td-actions">${actions}</div></td>
7955    </tr>`;
7956  }).join('');
7957}
7958
7959// ── Feature 2: IL/International geo filter ────────────────────────────────────
7960let _webGeoFilter = 'all'; // 'all' | 'il' | 'intl'
7961
7962const _KNOWN_IL_DOMAINS = new Set([
7963  'couponet.co.il','icoupons.co.il','jemix.co.il','groo.co.il','buyme.co.il',
7964  'influ.codes','deals.co.il','secretcoupon.co.il','israelicoupons.co.il',
7965  'israelcoupon.co.il','promocode.co.il','aliclub.co.il','pepper.co.il',
7966  'dealtas.com','dealhunter.co.il',
7967  // Israeli stores
7968  'castro.com','ksp.co.il','bug.co.il','golf.co.il','terminalx.com',
7969  'renuar.co.il','adika.co.il','fox.co.il','shufersal.co.il','delta.co.il',
7970  'super-pharm.co.il','10bis.co.il','wolt.com','rami-levy.co.il',
7971  'homecenter.co.il','ace.co.il','hamashbir.co.il','buy2.co.il',
7972  'steimatzky.co.il','isrotel.co.il','fattal.co.il','issta.co.il',
7973  'newpharm.co.il','ivory.co.il','payngo.co.il','zer4u.co.il',
7974  'vardinon.co.il','zaga.co.il','optikana.co.il','intima.co.il',
7975  'urban.co.il','kitan.co.il','beitely.co.il','hashmalneto.co.il',
7976  'tami4.co.il','discount.co.il','pandazzz.co.il','shamhi.co.il',
7977]);
7978
7979function _isIsraeliSource(s) {
7980  const url = (s.url || s.name || '').toLowerCase();
7981  // Check .co.il or .il TLD
7982  if (url.includes('.co.il') || url.match(/\.il(\/|$)/)) return true;
7983  // Check known Israeli domains set
7984  try {
7985    const host = new URL(url.startsWith('http') ? url : 'https://' + url).hostname.replace(/^www\./, '');
7986    if (_KNOWN_IL_DOMAINS.has(host)) return true;
7987    // Telegram channels are Israeli by default (they're in TELEGRAM_CHANNELS)
7988    if (host === 't.me') return true;
7989  } catch(e) {}
7990  return false;
7991}
7992
7993function _setWebGeo(geo, btn) {
7994  _webGeoFilter = geo;
7995  document.querySelectorAll('#web-geo-all,#web-geo-il,#web-geo-intl').forEach(b => b.classList.remove('on'));
7996  btn.classList.add('on');
7997  WEB.page = 0;
7998  _renderWebTable(_srcData);
7999}
8000
8001function _renderWebTable(data) {
8002  if (!data) return;
8003  const tbody = document.getElementById('web-sources-tbody');
8004  if (!tbody) return;
8005  if (!data.web.length) {
8006    tbody.innerHTML = `<tr><td colspan="7" style="color:var(--gray5);text-align:center;padding:24px">אין אתרים במעקב — לחץ "+ הוסף אתר" כדי להתחיל</td></tr>`;
8007    document.getElementById('web-pagination').innerHTML = '';
8008    return;
8009  }
8010  const sortVal = document.getElementById('web-sort')?.value || 'count';
8011  // Apply IL/International geo filter
8012  let webFiltered = data.web;
8013  if (_webGeoFilter === 'il')   webFiltered = data.web.filter(s =>  _isIsraeliSource(s));
8014  if (_webGeoFilter === 'intl') webFiltered = data.web.filter(s => !_isIsraeliSource(s));
8015  const sorted = [...webFiltered].sort((a,b) => {
8016    if (sortVal === 'new') {
8017      // unreviewed first, then newest added_at first
8018      const revDiff = (a.reviewed ? 1 : 0) - (b.reviewed ? 1 : 0);
8019      if (revDiff !== 0) return revDiff;
8020      return (b.added_at||'').localeCompare(a.added_at||'');
8021    }
8022    if (sortVal === 'count')  return b.count - a.count;
8023    if (sortVal === 'recent') return (b.last_scan||'').localeCompare(a.last_scan||'');
8024    if (sortVal === 'name')   return a.name.localeCompare(b.name);
8025    return 0;
8026  });
8027  const pages = Math.max(1, Math.ceil(sorted.length / WEB.perPage));
8028  WEB.page = Math.min(WEB.page, pages - 1);
8029  const slice = sorted.slice(WEB.page * WEB.perPage, (WEB.page + 1) * WEB.perPage);
8030  tbody.innerHTML = slice.map((s, idx) => {
8031    const safeUrl = s.url.replace(/'/g, "\\'");
8032    const rowNum = WEB.page * WEB.perPage + idx + 1;
8033    const addedDate = s.added_at ? new Date(s.added_at).toLocaleDateString('he-IL',{day:'2-digit',month:'2-digit',year:'2-digit'}) : '—';
8034    const reviewedCell = s.reviewed
8035      ? `<button class="td-btn" style="color:var(--accent);min-width:32px;padding:3px 8px" title="נבדק — לחץ לביטול" onclick="toggleSourceReviewed('${safeUrl}',${s.reviewed},this)">✓</button>`
8036      : `<button class="td-btn" style="color:var(--gray5);min-width:32px;padding:3px 8px;border:1px dashed var(--gray4)" title="סמן כנבדק" onclick="toggleSourceReviewed('${safeUrl}',${s.reviewed},this)">✓</button>`;
8037    return `<tr${s.reviewed ? '' : ' style="background:rgba(255,200,0,0.04)"'}>
8038      <td style="color:var(--gray5);font-size:11px;text-align:center">${rowNum}</td>
8039      <td class="td-store"><a href="${s.url}" target="_blank" style="color:var(--accent);text-decoration:none">${s.name}</a>${s.reviewed ? '' : ' <span style="font-size:10px;background:rgba(255,200,0,0.15);color:#ffcc00;border-radius:4px;padding:1px 5px">חדש</span>'}</td>
8040      <td>${s.count}</td>
8041      <td>${_relTime(s.last_scan)}</td>
8042      <td style="font-size:12px;color:var(--gray5)">${addedDate}</td>
8043      <td style="text-align:center">${reviewedCell}</td>
8044      <td><div class="td-actions">
8045        <button class="td-btn" onclick="scanSource('${safeUrl}',this)">סרוק</button>
8046        <button class="td-btn danger" onclick="removeSource('${safeUrl}',this)">הסר</button>
8047      </div></td>
8048    </tr>`;
8049  }).join('');
8050  // Pagination bar
8051  const pgDiv = document.getElementById('web-pagination');
8052  if (!pgDiv) return;
8053  if (pages <= 1) { pgDiv.innerHTML = ''; return; }
8054  const nums = _pageNums(WEB.page, pages);
8055  const numBtns = nums.map(n => n === '…'
8056      ? `<span style="color:var(--gray5);padding:0 4px;font-size:13px">…</span>`
8057      : `<button class="td-btn${n-1===WEB.page?' page-active':''}" onclick="webGoPage(${n-1})" style="min-width:32px">${n}</button>`
8058  ).join('');
8059  pgDiv.innerHTML = `
8060    <div style="font-size:11px;color:var(--gray5);text-align:center;padding:8px 0 2px">${sorted.length} אתרים</div>
8061    <div style="display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 18px 14px;border-top:1px solid var(--gray3);flex-wrap:wrap">
8062      <button class="td-btn" onclick="webGoPage(${WEB.page-1})" ${WEB.page===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>
8063      ${numBtns}
8064      <button class="td-btn" onclick="webGoPage(${WEB.page+1})" ${WEB.page>=pages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>
8065    </div>`;
8066}
8067function webGoPage(n) {
8068  WEB.page = Math.max(0, n);
8069  _renderWebTable(_srcData);
8070}
8071function nlGoPage(n) {
8072  NL.page = Math.max(0, n);
8073  _nlRender();
8074}
8075
8076// ── Instagram view ────────────────────────────────────────────────────────────
8077let _igData = null;
8078
8079function _renderInstagramTable() {
8080  const tbody = document.getElementById('ig-sources-tbody');
8081  if (!tbody || !_igData) return;
8082  const profiles = _igData.profiles || [];
8083  const titleEl  = document.getElementById('ig-table-title');
8084  if (titleEl) titleEl.textContent = `${profiles.length} פרופילים · ${profiles.reduce((s,p)=>s+p.count,0)} קופונים`;
8085  const countEl = document.getElementById('src-ig-count');
8086  if (countEl) countEl.textContent = profiles.length;
8087
8088  const sortVal = document.getElementById('ig-sort')?.value || 'count';
8089  const sorted = [...profiles].sort((a,b) => {
8090    if (sortVal === 'count')  return b.count - a.count;
8091    if (sortVal === 'name')   return (a.name||'').localeCompare(b.name||'', 'he');
8092    if (sortVal === 'recent') return (b.last_scan||'').localeCompare(a.last_scan||'');
8093    return 0;
8094  });
8095
8096  tbody.innerHTML = sorted.map((p, idx) => {
8097    const igIcon = `<svg width="12" height="12" viewBox="0 0 24 24" fill="url(#ig-grad)" style="vertical-align:-2px;margin-left:5px"><defs><linearGradient id="ig-grad" x1="0%" y1="100%" x2="100%" y2="0%"><stop offset="0%" style="stop-color:#f09433"/><stop offset="25%" style="stop-color:#e6683c"/><stop offset="50%" style="stop-color:#dc2743"/><stop offset="75%" style="stop-color:#cc2366"/><stop offset="100%" style="stop-color:#bc1888"/></linearGradient></defs><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>`;
8098    const safeHandle = (p.handle||'').replace(/'/g,"\\'");
8099    const nameHtml = `<a href="${p.url}" target="_blank" style="color:#e1306c;text-decoration:none;font-weight:500">${igIcon}${p.name}</a>
8100      <span style="color:var(--gray5);font-size:11px;margin-right:6px">@${p.handle}</span>`;
8101    const status = p.count > 0
8102      ? `<span style="color:var(--accent)">פעיל</span>`
8103      : `<span style="color:var(--gray5)">ממתין</span>`;
8104    return `<tr>
8105      <td style="color:var(--gray5);font-size:11px;text-align:center">${idx+1}</td>
8106      <td class="td-store">${nameHtml}</td>
8107      <td>${p.count}</td>
8108      <td>${_relTime(p.last_scan)}</td>
8109      <td>${status}</td>
8110      <td><div class="td-actions">
8111        <button class="td-btn" onclick="igScanOne('${safeHandle}',this)">סרוק</button>
8112        <a href="${p.url}" target="_blank" class="td-btn" style="text-decoration:none">פרופיל ↗</a>
8113      </div></td>
8114    </tr>`;
8115  }).join('');
8116}
8117
8118async function initInstagramView() {
8119  const tbody = document.getElementById('ig-sources-tbody');
8120  if (tbody) tbody.innerHTML = `<tr><td colspan="6" style="color:var(--gray5);text-align:center;padding:24px">טוען פרופילים...</td></tr>`;
8121  try {
8122    const res = await fetch('/api/admin/instagram/profiles', { cache: 'no-store' });
8123    _igData = await res.json();
8124    _renderInstagramTable();
8125  } catch(e) {
8126    if (tbody) tbody.innerHTML = `<tr><td colspan="6" style="color:#ff6b6b;text-align:center;padding:24px">שגיאה בטעינת פרופילים</td></tr>`;
8127  }
8128}
8129
8130async function igTestLogin(btn) {
8131  btn.disabled = true;
8132  const orig = btn.textContent;
8133  btn.textContent = '🔌 בודק...';
8134  try {
8135    const res = await fetch('/api/admin/instagram/test');
8136    const d   = await res.json();
8137    if (d.ok) {
8138      showToast(`✅ מחובר @${d.username} · ${d.test || ''} [${d._v||''}]`);
8139    } else {
8140      showToast(`❌ [${d._v||'old'}] ${d.error || 'חיבור נכשל'}`);
8141      if (d.trace) console.error('Instagram test trace:', d.trace);
8142    }
8143  } catch(e) {
8144    showToast('❌ שגיאה: ' + e.message);
8145  } finally {
8146    btn.disabled = false;
8147    btn.textContent = orig;
8148  }
8149}
8150
8151function _goInstagram() {
8152  const btn = document.querySelector('.sidebar-item[onclick*="instagram"]');
8153  if (btn) setAdminView(btn, 'instagram');
8154}
8155
8156async function igScanAll(btn) {
8157  btn.disabled = true;
8158  const orig = btn.textContent;
8159  btn.textContent = '⏳ סורק...';
8160  try {
8161    const res = await fetch('/api/admin/scan-instagram', { method: 'POST' });
8162    const d   = await res.json();
8163    if (d.error) {
8164      showToast(`⚠️ ${d.error}`);
8165    } else {
8166      showToast(`✅ נמצאו ${d.found} קופונים מ־${d.profiles_scraped} פרופילים (${d.posts_fetched} פוסטים)`);
8167      await initInstagramView();
8168      if (d.found > 0) loadAdminData();
8169    }
8170  } catch(e) {
8171    showToast('❌ שגיאה בסריקה: ' + e.message);
8172  } finally {
8173    btn.disabled = false;
8174    btn.textContent = orig;
8175  }
8176}
8177
8178async function igScanOne(handle, btn) {
8179  btn.disabled = true;
8180  const orig = btn.textContent;
8181  btn.textContent = '⏳...';
8182  try {
8183    const res = await fetch(`/api/admin/instagram/scan/${encodeURIComponent(handle)}`, { method: 'POST' });
8184    const d   = await res.json();
8185    if (d.error) {
8186      showToast(`❌ @${handle}: ${d.error}`);
8187    } else {
8188      showToast(`✅ @${handle}: ${d.posts} פוסטים · ${d.found} קופונים חדשים`);
8189      await initInstagramView();
8190      if (d.found > 0) loadAdminData();
8191    }
8192  } catch(e) {
8193    showToast('❌ שגיאה: ' + e.message);
8194  } finally {
8195    btn.disabled = false;
8196    btn.textContent = orig;
8197  }
8198}
8199
8200async function initSourcesView() {
8201  const tbody = document.getElementById('tg-sources-tbody');
8202  if (tbody) tbody.innerHTML = `<tr><td colspan="5" style="color:var(--gray5);text-align:center;padding:20px">טוען...</td></tr>`;
8203  try {
8204    const data = await _fetchSourceData();
8205    _renderTelegramTable(data);
8206  } catch(e) {
8207    if (tbody) tbody.innerHTML = `<tr><td colspan="5" style="color:#fc8181;text-align:center;padding:20px">שגיאה בטעינת מקורות</td></tr>`;
8208  }
8209}
8210
8211async function _autoResolveTgNames(data) {
8212  const nameOnly = (data.telegram || []).filter(ch => !ch.url || !ch.url.startsWith('http'));
8213  if (!nameOnly.length) return;
8214  for (const ch of nameOnly) {
8215    try {
8216      const r = await fetch('/api/admin/sources/resolve-tg', {
8217        method: 'POST',
8218        headers: { 'Content-Type': 'application/json' },
8219        body: JSON.stringify({ name: ch.name })
8220      });
8221      if (!r.ok) continue;
8222      const { url } = await r.json();
8223      if (url) {
8224        // Update DB records and refresh table silently
8225        await fetch('/api/admin/sources/relink', {
8226          method: 'POST',
8227          headers: { 'Content-Type': 'application/json' },
8228          body: JSON.stringify({ old_pattern: `Telegram: ${ch.name}`, new_source: url })
8229        });
8230        _srcData = null;
8231        const fresh = await _fetchSourceData(true);
8232        _renderTelegramTable(fresh);
8233      }
8234    } catch(e) {}
8235  }
8236}
8237
8238async function initWebView() {
8239  const tbody = document.getElementById('web-sources-tbody');
8240  if (tbody) tbody.innerHTML = `<tr><td colspan="5" style="color:var(--gray5);text-align:center;padding:20px">טוען...</td></tr>`;
8241  try {
8242    const data = await _fetchSourceData();
8243    _renderWebTable(data);
8244  } catch(e) {
8245    if (tbody) tbody.innerHTML = `<tr><td colspan="5" style="color:#fc8181;text-align:center;padding:20px">שגיאה בטעינת מקורות</td></tr>`;
8246  }
8247}
8248
8249async function toggleSourceReviewed(url, currentReviewed, btn) {
8250  const newVal = !currentReviewed;
8251  btn.disabled = true;
8252  try {
8253    await fetch('/api/admin/sources/reviewed', {
8254      method: 'PATCH',
8255      headers: { 'Content-Type': 'application/json' },
8256      body: JSON.stringify({ url, reviewed: newVal })
8257    });
8258    const entry = (_srcData?.web || []).find(s => s.url === url);
8259    if (entry) entry.reviewed = newVal;
8260    _renderWebTable(_srcData);
8261  } catch(e) {
8262    btn.disabled = false;
8263  }
8264}
8265
8266async function scanSource(url, btn) {
8267  const orig = btn.textContent;
8268  btn.disabled = true;
8269  btn.textContent = '⏳';
8270  try {
8271    await fetch('/api/admin/sources/scan', {
8272      method: 'POST',
8273      headers: { 'Content-Type': 'application/json' },
8274      body: JSON.stringify({ url })
8275    });
8276    btn.textContent = '✅';
8277    _srcData = null;
8278    setTimeout(async () => {
8279      btn.textContent = orig; btn.disabled = false;
8280      const d = await _fetchSourceData(true);
8281      _renderTelegramTable(d); _renderWebTable(d);
8282    }, 1500);
8283  } catch(e) {
8284    btn.textContent = '❌';
8285    setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 1500);
8286  }
8287}
8288
8289async function removeSource(url, btn) {
8290  if (!confirm(`למחוק את כל הקופונים ממקור זה?\n${url}`)) return;
8291  btn.disabled = true;
8292  const pattern = url.replace(/^https?:\/\//, '');
8293  try {
8294    const r = await fetch('/api/admin/sources/remove', {
8295      method: 'DELETE',
8296      headers: { 'Content-Type': 'application/json' },
8297      body: JSON.stringify({ pattern })
8298    });
8299    const result = await r.json();
8300    _srcData = null;
8301    const d = await _fetchSourceData(true);
8302    _renderTelegramTable(d); _renderWebTable(d);
8303    const t = document.getElementById('toast');
8304    if (t) { t.textContent = `🗑️ נמחקו ${result.deleted} קופונים`; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3000); }
8305  } catch(e) {
8306    alert('שגיאה במחיקה'); btn.disabled = false;
8307  }
8308}
8309
8310async function removeTgByName(name, btn) {
8311  if (!confirm(`למחוק את כל הקופונים מהערוץ "${name}"?`)) return;
8312  btn.disabled = true;
8313  try {
8314    const r = await fetch('/api/admin/sources/remove', {
8315      method: 'DELETE',
8316      headers: { 'Content-Type': 'application/json' },
8317      body: JSON.stringify({ pattern: `Telegram: ${name}` })
8318    });
8319    const result = await r.json();
8320    _srcData = null;
8321    const d = await _fetchSourceData(true);
8322    _renderTelegramTable(d); _renderWebTable(d);
8323    const t = document.getElementById('toast');
8324    if (t) { t.textContent = `🗑️ נמחקו ${result.deleted} קופונים`; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3000); }
8325  } catch(e) {
8326    alert('שגיאה במחיקה'); btn.disabled = false;
8327  }
8328}
8329
8330function openAddCouponModal() {
8331  ['ac-store','ac-code','ac-discount','ac-min','ac-notes'].forEach(id => { const el=document.getElementById(id); if(el) el.value=''; });
8332  const expiry = document.getElementById('ac-expiry'); if(expiry) expiry.value='';
8333  const err = document.getElementById('ac-error'); if(err) err.style.display='none';
8334  document.getElementById('add-coupon-overlay').style.display = 'flex';
8335}
8336function closeAddCouponModal() {
8337  document.getElementById('add-coupon-overlay').style.display = 'none';
8338}
8339async function submitAddCoupon(btn) {
8340  const store    = document.getElementById('ac-store').value.trim();
8341  const code     = document.getElementById('ac-code').value.trim().toUpperCase() || 'NO_CODE';
8342  const discount = document.getElementById('ac-discount').value.trim();
8343  const cat      = document.getElementById('ac-cat').value;
8344  const expiry   = document.getElementById('ac-expiry').value || null;
8345  const minOrder = document.getElementById('ac-min').value.trim() || null;
8346  const notes    = document.getElementById('ac-notes').value.trim() || null;
8347  const errEl    = document.getElementById('ac-error');
8348
8349  if (!store || !discount) {
8350    errEl.textContent = 'חנות והנחה הם שדות חובה'; errEl.style.display='block'; return;
8351  }
8352  btn.disabled = true; btn.textContent = '⏳ שומר...';
8353  try {
8354    const r = await fetch('/api/admin/coupons', {
8355      method: 'POST',
8356      headers: { 'Content-Type': 'application/json' },
8357      body: JSON.stringify({ store, code, discount, categories: cat ? [cat] : [], expiry, min_order: minOrder, notes, source: 'ידני', status: 'unknown', votes: 0 })
8358    });
8359    if (!r.ok) throw new Error(await r.text());
8360    closeAddCouponModal();
8361    const t = document.getElementById('toast');
8362    if (t) { t.textContent = `✅ קופון נוסף בהצלחה`; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3000); }
8363    loadAdminTable();
8364  } catch(e) {
8365    errEl.textContent = 'שגיאה בשמירה — נסה שוב'; errEl.style.display='block';
8366    btn.disabled=false; btn.textContent='שמור קופון';
8367  }
8368}
8369
8370async function linkTgChannel(name, btn) {
8371  const url = prompt(`הכנס קישור t.me לערוץ "${name}":\nלדוגמא: https://t.me/shufersal`);
8372  if (!url) return;
8373  if (!url.startsWith('https://t.me/') && !url.startsWith('http://t.me/')) {
8374    alert('הקישור חייב להתחיל ב-https://t.me/'); return;
8375  }
8376  btn.disabled = true;
8377  btn.textContent = '⏳';
8378  try {
8379    // Re-label all existing coupons from "Telegram: name" → the t.me URL
8380    const r = await fetch('/api/admin/sources/relink', {
8381      method: 'POST',
8382      headers: { 'Content-Type': 'application/json' },
8383      body: JSON.stringify({ old_pattern: `Telegram: ${name}`, new_source: url })
8384    });
8385    if (!r.ok) throw new Error();
8386    // Now scan the channel for new coupons
8387    await fetch('/api/admin/sources/scan', {
8388      method: 'POST',
8389      headers: { 'Content-Type': 'application/json' },
8390      body: JSON.stringify({ url })
8391    });
8392    _srcData = null;
8393    const d = await _fetchSourceData(true);
8394    _renderTelegramTable(d); _renderWebTable(d);
8395    const t = document.getElementById('toast');
8396    if (t) { t.textContent = `✅ הערוץ קושר וסרוק בהצלחה`; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3000); }
8397  } catch(e) {
8398    alert('שגיאה בקישור הערוץ'); btn.disabled = false; btn.textContent = '🔗 קשר ערוץ';
8399  }
8400}
8401
8402let _addSourceType = 'telegram';
8403function addSourcePrompt(type) {
8404  _addSourceType = type;
8405  const inp = document.getElementById('as-url-input');
8406  if (inp) inp.value = '';
8407  const err = document.getElementById('as-error');
8408  if (err) err.style.display = 'none';
8409  const title = document.getElementById('as-modal-title');
8410  if (title) title.textContent = type === 'telegram' ? '+ הוסף ערוץ טלגרם' : '+ הוסף אתר לסריקה';
8411  const hint = document.getElementById('as-url-hint');
8412  if (hint) hint.textContent = type === 'telegram' ? 'לדוגמא: t.me/channelname' : 'לדוגמא: https://example.co.il';
8413  if (inp) inp.placeholder = type === 'telegram' ? 't.me/channelname' : 'https://example.co.il';
8414  document.getElementById('add-source-overlay').style.display = 'flex';
8415  setTimeout(() => inp && inp.focus(), 50);
8416}
8417function closeAddSourceModal() {
8418  document.getElementById('add-source-overlay').style.display = 'none';
8419}
8420
8421// ── Edit Source Modal ────────────────────────────────────────────────────────
8422let _editSourceOldName = '';
8423let _editSourceOldUrl  = '';
8424
8425
8426function _esUrlChanged(val) {
8427  val = val.trim();
8428  if (!val) return;
8429  // Extract channel handle from t.me URL
8430  const m = val.match(/t\.me\/(?:s\/)?([^/?#|\s]+)/);
8431  if (!m) return;
8432  const handle = m[1];
8433  const nameInp = document.getElementById('es-name-input');
8434  // Only auto-fill if the name field is empty or still holds the previous auto-value
8435  const current = (nameInp.value || '').trim();
8436  const prevAuto = _editSourceOldUrl
8437    ? (() => { const pm = _editSourceOldUrl.match(/t\.me\/(?:s\/)?([^/?#|\s]+)/); return pm ? '@' + pm[1] : ''; })()
8438    : '';
8439  if (!current || current === prevAuto || current === _editSourceOldName) {
8440    nameInp.value = '@' + handle;
8441  }
8442}
8443
8444function openEditSourceModal(name, url) {
8445  // url may contain "|Custom Name" — strip it for the URL field
8446  const bareUrl = url ? url.split('|')[0] : '';
8447  _editSourceOldName = name;
8448  _editSourceOldUrl  = bareUrl;
8449  document.getElementById('es-url-input').value  = bareUrl;
8450  document.getElementById('es-name-input').value = name;
8451  document.getElementById('es-error').style.display = 'none';
8452  document.getElementById('edit-source-overlay').style.display = 'flex';
8453  document.getElementById('es-name-input').focus();
8454}
8455function closeEditSourceModal() {
8456  document.getElementById('edit-source-overlay').style.display = 'none';
8457}
8458async function submitEditSource() {
8459  const newName = document.getElementById('es-name-input').value.trim();
8460  let   newUrl  = document.getElementById('es-url-input').value.trim();
8461  const errEl   = document.getElementById('es-error');
8462  errEl.style.display = 'none';
8463
8464  if (!newName) { errEl.textContent = 'שם הערוץ לא יכול להיות ריק'; errEl.style.display='block'; return; }
8465
8466  // Normalise URL if provided
8467  if (newUrl) {
8468    if (!newUrl.startsWith('http')) newUrl = 'https://' + newUrl;
8469    if (!newUrl.includes('t.me')) {
8470      errEl.textContent = 'הקישור חייב להיות של טלגרם (t.me/...)';
8471      errEl.style.display = 'block';
8472      return;
8473    }
8474  }
8475
8476  const btn = document.getElementById('es-submit-btn');
8477  btn.disabled = true; btn.textContent = '...שומר';
8478
8479  try {
8480    // Determine old pattern to find existing DB records
8481    // Strip any "|Custom Name" suffix from the stored URL to get the raw URL pattern
8482    const rawOldUrl = _editSourceOldUrl ? _editSourceOldUrl.split('|')[0] : '';
8483    const oldPattern = rawOldUrl.startsWith('http')
8484      ? rawOldUrl                          // match by URL (strips old custom name)
8485      : `Telegram: ${_editSourceOldName}`; // name-only format
8486
8487    // Determine new source value
8488    // If URL given: store as "url|Custom Name" when name differs from auto-derived @handle
8489    let newSource;
8490    if (newUrl) {
8491      const autoName = '@' + newUrl.replace(/.*t\.me\/(?:s\/)?/, '').replace(/[/?#].*/, '');
8492      newSource = (newName && newName !== autoName) ? `${newUrl}|${newName}` : newUrl;
8493    } else {
8494      newSource = `Telegram: ${newName}`;
8495    }
8496
8497    await fetch('/api/admin/sources/relink', {
8498      method: 'POST',
8499      headers: {'Content-Type':'application/json'},
8500      body: JSON.stringify({ old_pattern: oldPattern, new_source: newSource })
8501    });
8502
8503    closeEditSourceModal();
8504    _srcData = null;
8505    const d = await _fetchSourceData(true);
8506    _renderTelegramTable(d); _renderWebTable(d);
8507    const t = document.getElementById('toast');
8508    if (t) { t.textContent = '✅ המקור עודכן בהצלחה'; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),2500); }
8509  } catch(e) {
8510    errEl.textContent = 'שגיאה בשמירה — נסה שוב';
8511    errEl.style.display = 'block';
8512  } finally {
8513    btn.disabled = false; btn.textContent = 'שמור';
8514  }
8515}
8516function _fixSourceUrl(raw, type) {
8517  let url = raw.trim();
8518  // Auto-fix: strip @ prefix (Telegram handles)
8519  if (url.startsWith('@')) url = 't.me/' + url.slice(1);
8520  // Auto-fix: add https:// if missing
8521  if (!url.startsWith('http')) url = 'https://' + url;
8522  return url;
8523}
8524async function submitAddSource() {
8525  const inp  = document.getElementById('as-url-input');
8526  const errEl = document.getElementById('as-error');
8527  const btn  = document.getElementById('as-submit-btn');
8528  const url  = _fixSourceUrl(inp.value, _addSourceType);
8529
8530  // Validate t.me for telegram type
8531  if (_addSourceType === 'telegram' && !url.includes('t.me')) {
8532    errEl.textContent = 'הקישור חייב להיות ערוץ טלגרם (t.me/...)'; errEl.style.display = 'block'; return;
8533  }
8534  if (!url.startsWith('http')) {
8535    errEl.textContent = 'כתובת לא תקינה'; errEl.style.display = 'block'; return;
8536  }
8537
8538  // Register source (placeholder record) — no auto-scan
8539  btn.disabled = true; btn.textContent = '⏳ רושם...';
8540  try {
8541    await fetch('/api/admin/sources/register', {
8542      method: 'POST',
8543      headers: { 'Content-Type': 'application/json' },
8544      body: JSON.stringify({ url })
8545    });
8546    closeAddSourceModal();
8547    _srcData = null;
8548    const d = await _fetchSourceData(true);
8549    _renderTelegramTable(d); _renderWebTable(d);
8550    const t = document.getElementById('toast');
8551    if (t) { t.textContent = `✅ המקור נרשם — לחץ סרוק כשתהיה מוכן`; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3500); }
8552  } catch(e) {
8553    errEl.textContent = 'שגיאה ברישום המקור'; errEl.style.display = 'block';
8554  } finally {
8555    btn.disabled = false; btn.textContent = 'רשום מקור';
8556  }
8557}
8558
8559let _logActiveDid  = '';   // '' = all devices
8560let _logActiveUser = '';   // '' = all users
8561
8562async function loadLogs(did, user, page) {
8563  if (did  !== undefined) { _logActiveDid  = did;  LOG.page = 0; }
8564  if (user !== undefined) { _logActiveUser = user; LOG.page = 0; }
8565  if (page !== undefined)   LOG.page = page;
8566  const el      = document.getElementById('log-lines');
8567  const summary = document.getElementById('log-summary');
8568  if (!el) return;
8569  el.textContent = 'טוען לוגים...';
8570
8571  try {
8572    // ── Sync local events to server first (so admin's own events show up) ─────
8573    await _syncAnalyticsToServer(false).catch(() => {});
8574
8575    // ── Fetch devices, admin users list, and log rows in parallel ─────────────
8576    const devicesUrl = '/api/admin/logs/devices';
8577    const [allRows, devices, adminUsers] = await Promise.all([
8578      fetch(`/api/admin/logs?limit=500`, { cache: 'no-store' }).then(r => r.json()),
8579      fetch(devicesUrl, { cache: 'no-store' }).then(r => r.json()).catch(() => []),
8580      fetch('/api/admin/users', { cache: 'no-store' }).then(r => r.json()).catch(() => []),
8581    ]);
8582
8583    // ── Build known-users set: from admin_users table + current logged-in user ─
8584    const _getU = (data) => {
8585      if (!data) return '';
8586      if (typeof data === 'object') return data._u || '';
8587      return '';
8588    };
8589    const knownAdminUsers = new Set((adminUsers || []).map(u => u.username).filter(Boolean));
8590    const currentUser = _currentUsername();
8591    if (currentUser) knownAdminUsers.add(currentUser);
8592    // Also add any users found in event data (even if not in admin_users table)
8593    allRows.forEach(r => { const u = _getU(r.data); if (u) knownAdminUsers.add(u); });
8594    const knownUsers = [...knownAdminUsers].sort();
8595
8596    // ── Filter rows by active user / device ───────────────────────────────────
8597    // For user filter: find all device IDs that have ANY event tagged _u===user
8598    // then show ALL events from those DIDs (so sessions etc. appear too)
8599    let rows;
8600    if (_logActiveUser) {
8601      const userDids = new Set(
8602        allRows
8603          .filter(r => r.data && typeof r.data === 'object' && r.data._u === _logActiveUser)
8604          .map(r => r.did).filter(Boolean)
8605      );
8606      rows = allRows.filter(r => {
8607        const u = r.data && typeof r.data === 'object' ? r.data._u : '';
8608        if (u === _logActiveUser) return true;                    // direct match
8609        if (r.did && userDids.has(r.did)) return true;           // same device
8610        return false;
8611      });
8612    } else if (_logActiveDid) {
8613      rows = allRows.filter(r => r.did === _logActiveDid);
8614    } else {
8615      rows = allRows;
8616    }
8617
8618    // ── Filter bar — by user then by device ───────────────────────────────────
8619    const filterBar = document.getElementById('log-device-filter');
8620    if (filterBar) {
8621      const _btn = (onclick, label, active) =>
8622        `<button onclick="${onclick}" style="padding:4px 14px;border:none;border-radius:20px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;background:${active?'var(--accent)':'var(--gray3)'};color:${active?'#000':'var(--gray6)'};transition:.15s">${label}</button>`;
8623
8624      let html = _btn(`loadLogs('','')`, 'הכל', !_logActiveDid && !_logActiveUser);
8625
8626      // User buttons — always shown if we know of any admin users
8627      if (knownUsers.length) {
8628        html += `<span style="color:var(--gray4);font-size:11px;margin:0 4px">|</span>`;
8629        html += knownUsers.map(u =>
8630          _btn(`loadLogs('','${u}')`, `👤 ${u}`, _logActiveUser === u)
8631        ).join('');
8632      }
8633
8634      // Device buttons — only show devices NOT already linked to a known admin user
8635      // A device is "linked" if any of its events has _u matching a known admin
8636      const linkedDids = new Set(
8637        allRows
8638          .filter(r => r.data && typeof r.data === 'object' && knownAdminUsers.has(r.data._u))
8639          .map(r => r.did).filter(Boolean)
8640      );
8641      const anonDevices = devices.filter(dev => !linkedDids.has(dev.did));
8642      if (anonDevices.length) {
8643        html += `<span style="color:var(--gray4);font-size:11px;margin:0 4px">|</span>`;
8644        let anonIdx = 0;
8645        html += anonDevices.map(dev => {
8646          anonIdx++;
8647          const short = dev.did.slice(-6).toUpperCase();
8648          // Find first-seen timestamp and IP for this device
8649          const devRows = allRows.filter(r => r.did === dev.did).sort((a,b) => (a.ts||0)-(b.ts||0));
8650          const firstRow = devRows[0];
8651          const firstDate = firstRow && firstRow.ts ? new Date(firstRow.ts).toLocaleDateString('he-IL',{day:'2-digit',month:'2-digit'}) : '';
8652          const firstIp = firstRow && firstRow.ip ? firstRow.ip : '';
8653          const tooltip = [firstDate, firstIp].filter(Boolean).join(' · ');
8654          return _btn(`loadLogs('${dev.did}','')`,
8655            `👁️ אנונימי ${anonIdx} <span style="opacity:.6;font-size:10px">${short}${tooltip?' · '+tooltip:''}</span>`,
8656            _logActiveDid === dev.did);
8657        }).join('');
8658      }
8659      filterBar.innerHTML = html;
8660    }
8661
8662    // ── Summary chips ─────────────────────────────────────────────────────────
8663    const counts = {};
8664    rows.forEach(r => { counts[r.type] = (counts[r.type] || 0) + 1; });
8665    const _chip = (label, n, color) =>
8666      `<div style="background:var(--gray3);border-radius:20px;padding:4px 12px;font-size:12px;font-family:'DM Sans',sans-serif;color:${color||'var(--gray6)'}">${label} <strong style="color:var(--white)">${n}</strong></div>`;
8667    if (summary) summary.innerHTML = [
8668      _chip('🔍 חיפושים',   counts.search              || 0, ''),
8669      _chip('📋 העתקות',    counts.copy                || 0, ''),
8670      _chip('👍 לייקים',    counts.thumb_up            || 0, 'var(--accent)'),
8671      _chip('👎 דיסלייקים', counts.thumb_down          || 0, '#ff6b6b'),
8672      _chip('🛍️ כניסות',   counts.show_site           || 0, ''),
8673      _chip('🖱️ לחיצות',   counts.coupon_field_click  || 0, ''),
8674      _chip('🎫 פופאפים',  counts.coupon_popup        || 0, '#a78bfa'),
8675      _chip('🔗 עוד קופ׳', counts.more_coupons        || 0, '#ffe45e'),
8676      _chip('📡 סה״כ',     rows.length,                    'var(--accent)'),
8677    ].join('');
8678
8679    // ── Event label map ───────────────────────────────────────────────────────
8680    // ── Event type config ─────────────────────────────────────────────────────
8681    const _EVT = {
8682      session:            { icon:'🌐', label:'כניסה לאתר',      color:'var(--gray5)' },
8683      search:             { icon:'🔍', label:'חיפוש',           color:'#63b3ed' },
8684      search_time:        { icon:'⏱️', label:'תוצאות חיפוש',   color:'#63b3ed' },
8685      search_dropped:     { icon:'↩️', label:'חיפוש בוטל',     color:'var(--gray5)' },
8686      search_failed:      { icon:'❌', label:'חיפוש נכשל',      color:'#ff6b6b' },
8687      copy:               { icon:'📋', label:'העתקת קוד',       color:'var(--accent)' },
8688      coupon_field_click: { icon:'👆', label:'לחיצה על קוד',   color:'var(--gray6)' },
8689      show_site:          { icon:'🛍️', label:'כניסה לחנות',    color:'var(--gray6)' },
8690      thumb_up:           { icon:'👍', label:'לייק',            color:'var(--accent)' },
8691      thumb_down:         { icon:'👎', label:'דיסלייק',         color:'#ff6b6b' },
8692      coupon_popup:       { icon:'🎫', label:'פתיחת פופאפ',     color:'#a78bfa' },
8693      modal_copy:         { icon:'📋', label:'העתקה מפופאפ',    color:'var(--accent)' },
8694      more_coupons:       { icon:'🔗', label:'עוד קופונים של',  color:'#ffe45e' },
8695      login:              { icon:'🔑', label:'כניסת מנהל',      color:'#e1a0ff' },
8696    };
8697
8698    const _detail = (type, data) => {
8699      const d = (data && typeof data === 'object') ? data : {};
8700      const v = d._v ?? data ?? '';
8701      switch (type) {
8702        case 'search':         return String(v);
8703        case 'search_dropped': return String(v);
8704        case 'search_failed':  return d.error || String(v);
8705        case 'copy':           return String(v);
8706        case 'search_time':    return `"${d.q||''}" · ${d.results||0} תוצאות · ${d.elapsed||'?'}s · ${d.sites||0} אתרים`;
8707        case 'coupon_popup':   return d.store || '';
8708        case 'modal_copy':     return d.store || '';
8709        case 'more_coupons':   return typeof data === 'string' ? data : (d.store || '');
8710        case 'login':          return d._v || '';
8711        default:               return '';
8712      }
8713    };
8714
8715    // ── Pagination ────────────────────────────────────────────────────────────
8716    const pgDiv   = document.getElementById('log-pagination');
8717    const totPages = Math.max(1, Math.ceil(rows.length / LOG.perPage));
8718    LOG.page = Math.min(LOG.page, totPages - 1);
8719    const slice   = rows.slice(LOG.page * LOG.perPage, (LOG.page + 1) * LOG.perPage);
8720
8721    // Render pagination buttons
8722    if (pgDiv) {
8723      if (totPages <= 1) {
8724        pgDiv.innerHTML = '';
8725      } else {
8726        const nums = _pageNums(LOG.page, totPages);
8727        pgDiv.innerHTML =
8728          `<button class="td-btn" onclick="loadLogs(undefined,undefined,${LOG.page-1})" ${LOG.page===0?'disabled':''} style="padding:4px 10px">‹ הקודם</button>` +
8729          nums.map(n => n==='…'
8730            ? `<span style="padding:4px 6px;color:var(--gray5)">…</span>`
8731            : `<button class="td-btn${n-1===LOG.page?' page-active':''}" onclick="loadLogs(undefined,undefined,${n-1})" style="min-width:32px">${n}</button>`
8732          ).join('') +
8733          `<button class="td-btn" onclick="loadLogs(undefined,undefined,${LOG.page+1})" ${LOG.page>=totPages-1?'disabled':''} style="padding:4px 10px">הבא ›</button>`;
8734      }
8735    }
8736
8737    // ── Render table ──────────────────────────────────────────────────────────
8738    if (!rows.length) {
8739      el.innerHTML = `<div style="padding:32px;text-align:center;color:var(--gray5)">אין פעילות רשומה עדיין.</div>`;
8740      if (pgDiv) pgDiv.innerHTML = '';
8741      return;
8742    }
8743
8744    const th = s => `<th style="padding:8px 14px;text-align:right;font-size:11px;color:var(--gray5);font-weight:500;white-space:nowrap;border-bottom:1px solid var(--gray3);background:var(--gray2)">${s}</th>`;
8745    const tdS = (content, extra='') => `<td style="padding:7px 14px;font-size:12px;border-bottom:1px solid rgba(255,255,255,0.04);white-space:nowrap;${extra}">${content}</td>`;
8746
8747    el.innerHTML = `<table style="width:100%;border-collapse:collapse;direction:rtl">
8748      <thead><tr>${th('תאריך')}${th('שעה')}${th('מי')}${th('פעולה')}${th('פרטים')}${th('IP')}</tr></thead>
8749      <tbody>
8750      ${slice.map(r => {
8751        const dt      = new Date(r.ts);
8752        const dateStr = isNaN(dt) ? '—' : dt.toLocaleDateString('he-IL', {day:'2-digit', month:'2-digit', year:'2-digit'});
8753        const timeStr = isNaN(dt) ? '—' : dt.toLocaleTimeString('he-IL', {hour:'2-digit', minute:'2-digit'});
8754        const data    = r.data;
8755        const rowUser = (data && typeof data === 'object') ? (data._u || '') : '';
8756        const evt     = _EVT[r.type] || { icon:'📌', label: r.type, color:'var(--gray5)' };
8757        const detail  = _detail(r.type, data);
8758        const ip      = (data && typeof data === 'object' && data._v && r.type === 'login')
8759                        ? (String(data._v).replace('login from ', ''))
8760                        : '';
8761        const whoBadge = rowUser
8762          ? `<span style="font-size:11px;padding:2px 8px;border-radius:10px;background:rgba(255,228,94,0.15);color:var(--accent2);font-weight:600">👤 ${rowUser}</span>`
8763          : `<span style="font-size:11px;color:var(--gray4)">אנונימי</span>`;
8764        return `<tr style="transition:background .1s" onmouseover="this.style.background='rgba(255,255,255,0.03)'" onmouseout="this.style.background=''">
8765          ${tdS(dateStr, 'color:var(--gray5)')}
8766          ${tdS(timeStr, 'color:var(--gray5);font-family:monospace')}
8767          ${tdS(whoBadge)}
8768          ${tdS(`<span style="color:${evt.color}">${evt.icon} ${evt.label}</span>`)}
8769          ${tdS(`<span style="color:var(--gray6);max-width:320px;overflow:hidden;text-overflow:ellipsis;display:inline-block">${detail}</span>`)}
8770          ${tdS(`<span style="font-family:monospace;font-size:11px;color:var(--gray4)">${ip || '—'}</span>`)}
8771        </tr>`;
8772      }).join('')}
8773      </tbody></table>`;
8774
8775  } catch(e) {
8776    el.textContent = 'לא ניתן לטעון לוגים: ' + e.message;
8777  }
8778}
8779function copyCode(code, store, src) {
8780  // Admin table copy — no new tab needed
8781  trackEvent('copy', code);
8782  _sendCouponEvent('copy', store, code, null);
8783  navigator.clipboard.writeText(code).catch(() => {});
8784  const t = document.getElementById('toast');
8785  t.textContent = 'הועתק: ' + code;
8786  t.classList.add('show');
8787  setTimeout(() => t.classList.remove('show'), 3000);
8788}
8789
8790// ── Reveal modal ──────────────────────────────────────────────────────────────
8791
8792// Reference to the store tab opened in the background
8793function ccReveal(el, code, store, src, id, storeHome) {
8794
8795  // On the filtered store page AND same store: show modal inline — don't open a new tab or navigate away
8796  if (_storeFilterMode && (store || '').toLowerCase() === _storeFilterName) {
8797    const card = el ? el.closest('.coupon-card') : null;
8798    const ds   = card ? card.dataset : {};
8799    const goUrl = '/go?' + new URLSearchParams({ store: store || '' }).toString();
8800    _storeDataReady.then(() =>
8801      showRevealModal(code, store, storeHome, goUrl,
8802        ds.discountRaw || '', id, ds.notes || '', ds.source || '',
8803        ds.ageLabel || '', ds.pubLabel || '', ds.votes || '0', ds.newsletter || '')
8804    );
8805    return;
8806  }
8807
8808  const goParams = new URLSearchParams({ store: store || '' });
8809  if (storeHome) goParams.set('url', storeHome);
8810  const goUrl = '/go?' + goParams.toString();
8811
8812  // Read extra data from card data attributes
8813  const card = el ? el.closest('.coupon-card') : null;
8814  const ds   = card ? card.dataset : {};
8815
8816  const revealParams = new URLSearchParams({
8817    showReveal: '1',
8818    rc:  code              || '',
8819    rs:  store             || '',
8820    rh:  storeHome         || '',
8821    ru:  goUrl,
8822    rd:  ds.discountRaw    || '',
8823    ri:  id                || '',
8824    rn:  ds.notes          || '',
8825    rsrc: ds.source        || '',
8826    ral: ds.ageLabel       || '',
8827    rpl: ds.pubLabel       || '',
8828    rv:  ds.votes          || '0',
8829    rns: ds.newsletter     || '',
8830  });
8831  const revealUrl = window.location.origin + '/?' + revealParams.toString();
8832
8833  window.name = 'couponai_store_tab';
8834  window.open(revealUrl, '_blank');
8835  window.location.href = goUrl;
8836}
8837
8838function ccCopy(code, store, src, id) { ccReveal(null, code, store, src, id, ''); }
8839function openBehind(url) { window.open(url, '_blank', 'noopener,noreferrer'); }
8840
8841// ── Modal state ───────────────────────────────────────────────────────────────
8842let _rmCode = '', _rmGoUrl = '', _rmId = '', _rmStore = '';
8843let _storeFilterMode = false;   // true when on filtered store page (after "עוד קופונים של")
8844let _storeFilterName = '';     // canonical store name that triggered the filter
8845
8846function showRevealModal(code, store, storeHome, goUrl, discount, id, notes, sourceSrc, ageLabel, pubLabel, votes, newsletter) {
8847  _rmCode  = code  || '';
8848  _rmGoUrl = goUrl || '';
8849  _rmId    = id    || '';
8850  _rmStore = store || '';
8851
8852  // Header
8853  document.getElementById('rm-header-title').textContent =
8854    discount ? `קופון ${discount} - ${_storeDisplayModal(store)}` : `קופון הנחה - ${_storeDisplayModal(store)}`;
8855
8856  // Logo — use stored logo from Supabase Storage, fall back to Google favicon, then initials
8857  const logoEl = document.getElementById('rm-logo');
8858  const _storeKey = (store || '').toLowerCase();
8859  const _storedLogo = STORE_LOGOS[_storeKey] || null;
8860  const _storeDomain = STORE_DOMAINS[_storeKey] || null;
8861
8862  const _setModalLogo = (primarySrc, fallbackSrc) => {
8863    const img = document.createElement('img');
8864    img.alt = store;
8865    img.onerror = fallbackSrc
8866      ? function() {
8867          img.onerror = () => { logoEl.innerHTML = _storeInitial(store, 64); };
8868          img.src = fallbackSrc;
8869        }
8870      : function() { logoEl.innerHTML = _storeInitial(store, 64); };
8871    img.src = primarySrc;
8872    logoEl.innerHTML = '';
8873    logoEl.appendChild(img);
8874  };
8875
8876  if (_storedLogo) {
8877    const apiFav = _storeDomain ? `/api/favicon?domain=${encodeURIComponent(_storeDomain)}` : null;
8878    _setModalLogo(_storedLogo, apiFav);
8879  } else if (_storeDomain) {
8880    _setModalLogo(`/api/favicon?domain=${encodeURIComponent(_storeDomain)}`, null);
8881  } else {
8882    logoEl.innerHTML = _storeInitial(store, 64);
8883  }
8884
8885  // Discount
8886  const discountEl = document.getElementById('rm-discount');
8887  discountEl.textContent = discount || '';
8888  discountEl.style.display = discount ? '' : 'none';
8889
8890  // Newsletter badge
8891  const nlEl = document.getElementById('rm-newsletter');
8892  nlEl.innerHTML = newsletter === '1' ? '<div class="cc-newsletter-badge">✉️ לקוחות חדשים בלבד — רישום לניוזלטר</div>' : '';
8893  nlEl.style.display = newsletter === '1' ? '' : 'none';
8894
8895  // Notes
8896  const notesEl = document.getElementById('rm-notes');
8897  notesEl.textContent = notes || '';
8898  notesEl.style.display = notes ? '' : 'none';
8899
8900  // Code
8901  document.getElementById('rm-code-val').textContent = code || '';
8902
8903  // Vote buttons
8904  const voteState = id ? getVoteState(id) : '';
8905  const upBtn  = document.getElementById('rm-vote-up');
8906  const dnBtn  = document.getElementById('rm-vote-down');
8907  const upvotes = Math.max(0, parseInt(votes) || 0);
8908  document.getElementById('rm-vote-up-count').textContent   = upvotes;
8909  document.getElementById('rm-vote-down-count').textContent = 0;
8910  upBtn.disabled = voteState === 'up';   upBtn.classList.toggle('voted', voteState === 'up');
8911  dnBtn.disabled = voteState === 'down'; dnBtn.classList.toggle('voted', voteState === 'down');
8912
8913  // Footer store link
8914  document.getElementById('rm-more-store').textContent = _storeDisplayCard(store);
8915  document.getElementById('rm-more-link').href = `/store/${_storeSlug(store || '')}`;
8916
8917  // Views — only count once per device; subsequent opens show cached count
8918  const viewsEl = document.getElementById('rm-views');
8919  viewsEl.textContent = '';
8920  if (id) {
8921    const viewedKey  = `viewed_${id}`;
8922    const cachedCount = localStorage.getItem(viewedKey);
8923    if (cachedCount !== null) {
8924      // Already counted — just display the cached value
8925      viewsEl.textContent = `👁 ${cachedCount} צפיות`;
8926    } else {
8927      // First time this device views this coupon — increment and cache
8928      fetch(`/api/coupons/${id}/view`, { method: 'POST' })
8929        .then(r => r.json())
8930        .then(d => {
8931          const count = d.views || 0;
8932          viewsEl.textContent = `👁 ${count} צפיות`;
8933          localStorage.setItem(viewedKey, String(count));
8934        })
8935        .catch(() => { viewsEl.textContent = ''; });
8936    }
8937  }
8938
8939  // Auto-copy
8940  navigator.clipboard.writeText(code || '').catch(() => {});
8941
8942  trackEvent('coupon_popup', { store: store || '', id: id || '' });
8943  _sendCouponEvent('modal_open', store, code, id);
8944  // Flush immediately — this fires in a new tab that closes quickly, can't wait for 5-min interval
8945  _syncAnalyticsToServer(true);
8946  document.getElementById('reveal-modal-overlay').classList.add('active');
8947  document.body.style.overflow = 'hidden';
8948}
8949
8950function closeRevealModal() {
8951  document.getElementById('reveal-modal-overlay').classList.remove('active');
8952  document.body.style.overflow = '';
8953  history.replaceState(null, '', window.location.pathname);
8954}
8955
8956function rmGoToStore() {
8957  if (_rmGoUrl) window.open(_rmGoUrl, '_blank');
8958}
8959
8960function rmShowToast() {
8961  const t = document.getElementById('rm-toast');
8962  if (!t) return;
8963  t.classList.add('visible');
8964  setTimeout(() => t.classList.remove('visible'), 2000);
8965}
8966
8967function rmCopyCode() {
8968  navigator.clipboard.writeText(_rmCode).catch(() => {});
8969  trackEvent('modal_copy', { store: _rmStore, id: _rmId });
8970  _sendCouponEvent('copy', _rmStore, _rmCode, _rmId);
8971  _syncAnalyticsToServer(true); // flush immediately
8972  rmShowToast();
8973}
8974
8975async function rmVote(dir) {
8976  if (!_rmId) return;
8977  // Snapshot the ID now — _rmId may change while awaiting if user opens another coupon
8978  const voteId  = _rmId;
8979  const existing = getVoteState(voteId);
8980  // Same direction — no-op
8981  if ((dir === 1 && existing === 'up') || (dir === -1 && existing === 'down')) return;
8982
8983  const upBtn = document.getElementById('rm-vote-up');
8984  const dnBtn = document.getElementById('rm-vote-down');
8985  const upCnt = document.getElementById('rm-vote-up-count');
8986  const dnCnt = document.getElementById('rm-vote-down-count');
8987
8988  try {
8989    const voteUrl = `/api/coupons/${voteId}/vote`;
8990    const downUrl = `/api/coupons/${voteId}/downvote`;
8991
8992    if (dir === 1) {
8993      const r = await fetch(voteUrl, { method: 'POST' });
8994      if (!r.ok) return;
8995      if (_rmId !== voteId) return;  // modal moved to a different coupon — bail
8996      if (existing === 'down') {
8997        // Cancel the previous downvote (net +2 total)
8998        await fetch(voteUrl, { method: 'POST' });
8999        if (_rmId !== voteId) return;
9000        dnCnt.textContent = Math.max(0, parseInt(dnCnt.textContent) - 1);
9001        dnBtn.disabled = false; dnBtn.classList.remove('voted');
9002      }
9003      upCnt.textContent = parseInt(upCnt.textContent) + 1;
9004      upBtn.disabled = true; upBtn.classList.add('voted');
9005    } else {
9006      const r = await fetch(downUrl, { method: 'POST' });
9007      if (!r.ok) return;
9008      if (_rmId !== voteId) return;  // modal moved to a different coupon — bail
9009      if (existing === 'up') {
9010        // Cancel the previous upvote (net -2 total)
9011        await fetch(downUrl, { method: 'POST' });
9012        if (_rmId !== voteId) return;
9013        upCnt.textContent = Math.max(0, parseInt(upCnt.textContent) - 1);
9014        upBtn.disabled = false; upBtn.classList.remove('voted');
9015      }
9016      dnCnt.textContent = parseInt(dnCnt.textContent) + 1;
9017      dnBtn.disabled = true; dnBtn.classList.add('voted');
9018    }
9019
9020    localStorage.setItem(`vote_${voteId}`, dir === 1 ? 'up' : 'down');
9021  } catch(_) {}
9022}
9023
9024// ── On load: auto-search when opened via "עוד קופונים של" link (?q=STORE) ────
9025(function checkSearchParam() {
9026  const p = new URLSearchParams(window.location.search);
9027  const q = p.get('q');
9028  if (!q || p.get('showReveal') === '1') return;
9029  const run = () => {
9030    const inp = document.getElementById('pub-search');
9031    if (inp) {
9032      inp.value = q;
9033      history.replaceState(null, '', window.location.pathname);
9034      _storeFilterMode = true;
9035      _storeFilterName = q.trim().toLowerCase();
9036      if (typeof aiSearch === 'function') aiSearch();
9037    }
9038  };
9039  // Use a small delay so the page's own init (COUPONS load, showPage) finishes first
9040  if (document.readyState === 'loading') {
9041    document.addEventListener('DOMContentLoaded', () => setTimeout(run, 150));
9042  } else {
9043    setTimeout(run, 150);
9044  }
9045})();
9046
9047
9048// ── On load: check if opened via reverse popup ────────────────────────────────
9049(function checkRevealParams() {
9050  const p = new URLSearchParams(window.location.search);
9051  if (p.get('showReveal') !== '1') return;
9052  const code      = p.get('rc')   || '';
9053  const store     = p.get('rs')   || '';
9054  const storeHome = p.get('rh')   || '';
9055  const goUrl     = p.get('ru')   || '';
9056  const discount  = p.get('rd')   || '';
9057  const id        = p.get('ri')   || '';
9058  const notes     = p.get('rn')   || '';
9059  const sourceSrc = p.get('rsrc') || '';
9060  const ageLabel  = p.get('ral')  || '';
9061  const pubLabel  = p.get('rpl')  || '';
9062  const votes     = p.get('rv')   || '0';
9063  if (!code) return;
9064  const newsletter = p.get('rns') || '';
9065  const run = () => _storeDataReady.then(() =>
9066    showRevealModal(code, store, storeHome, goUrl, discount, id, notes, sourceSrc, ageLabel, pubLabel, votes, newsletter)
9067  );
9068  if (document.readyState === 'loading') {
9069    document.addEventListener('DOMContentLoaded', run);
9070  } else {
9071    run();
9072  }
9073})();
9074
9075// ── Store Pages Demo ─────────────────────────────────────────────────────
9076let _sdCurrentLayout = 'a';
9077let _sdCurrentStore  = 'ksp';
9078
9079const _STORE_DEMO_DATA = {
9080  ksp: {
9081    name: 'KSP', nameHe: 'KSP', domain: 'ksp.co.il', origin: 'IL',
9082    categories: ['אלקטרוניקה', 'גיימינג', 'מחשבים'],
9083    desc: 'KSP היא אחת מרשתות האלקטרוניקה הגדולות בישראל, עם עשרות סניפים ברחבי הארץ וחנות אונליין ענפה. החנות מתמחה במחשבים, רכיבים, סמארטפונים ואביזרי גיימינג.',
9084    longDesc: `<p>KSP היא אחת מרשתות האלקטרוניקה הגדולות בישראל, עם עשרות סניפים ברחבי הארץ וחנות אונליין ענפה. החנות מתמחה במחשבים, רכיבים, סמארטפונים, אביזרי גיימינג וציוד היקפי — ומוכרת הן ללקוחות פרטיים והן לעסקים.</p><p>KSP ידועה בכך שמחיריה תחרותיים במיוחד, ולעיתים קרובות ניתן למצוא קוד קופון שמוסיף עוד כמה אחוזים על גבי המחיר הכבר מוזל. הקופונים הנפוצים ביותר הם הנחה באחוזים על קניה מעל סכום מסוים, משלוח חינם, או עסקאות bundle על ציוד גיימינג.</p>`,
9085    tip: 'כדאי לבדוק קופוני KSP לפני רכישת מחשב נייד, מסך, או כרטיס מסך — אלו הפריטים שעליהם ניתן לחסוך הכי הרבה.',
9086    faq: [
9087      { q: 'איך להשתמש בקוד קופון KSP?', a: 'בעת הקנייה באתר ksp.co.il, לחץ על "סל קניות" ובשדה "קוד הטבה" הכנס את הקוד. ההנחה תחושב אוטומטית.' },
9088      { q: 'כמה ניתן לחסוך עם קופון KSP?', a: 'ההנחות נעות בין 5% ל-20% בהתאם לקופון. על רכישות גדולות כמו מחשבים ניידים ניתן לחסוך מאות שקלים.' },
9089      { q: 'האם הקופונים של KSP פגים תוקף?', a: 'כן, רוב הקופונים תקפים לתקופה מוגבלת. כדאי לבדוק את תאריך התפוגה לפני השימוש.' },
9090    ],
9091    coupons: [
9092      { code: 'KSP15', discount: '15% הנחה על כל האתר', notes: 'מינימום קנייה 300₪', expiry: '30/06/2026', votes: 42 },
9093      { code: 'KSPSHIP', discount: 'משלוח חינם', notes: 'על הזמנה מעל 200₪', expiry: null, votes: 28 },
9094      { code: 'SCREEN20', discount: '20% על מסכים', notes: 'בקטגוריית מסכים בלבד', expiry: '15/07/2
9094026', votes: 19 },
9095      { code: 'LAPTOP100', discount: '100₪ הנחה', notes: 'על מחשבים ניידים מעל 3,000₪', expiry: '01/07/2026', votes: 15 },
9096      { code: 'STUDENT10', discount: '10% לסטודנטים', notes: 'עם תעודת סטודנט בלבד', expiry: null, votes: 11 },
9097      { code: 'GAMING25', discount: '25% ציוד גיימינג', notes: 'עכבר, מקלדת, אוזניות', expiry: '20/07/2026', votes: 8 },
9098    ]
9099  },
9100  superpharm: {
9101    name: 'סופר פארם', nameHe: 'סופר פארם', domain: 'super-pharm.co.il', origin: 'IL',
9102    categories: ['קוסמטיקה', 'בריאות', 'תרופות'],
9103    desc: 'סופר פארם היא רשת חנויות הפארמצבטיקה, הקוסמטיקה והבריאות המובילה בישראל עם מאות סניפים ברחבי הארץ.',
9104    longDesc: `<p>סופר פארם היא רשת חנויות הפארמצבטיקה, הקוסמטיקה והבריאות המובילה בישראל. עם מאות סניפים, היא מציעה מגוון עצום של מוצרים — ממוצרי טיפוח ובשמים, דרך תרופות ללא מרשם, ועד מוצרי תינוקות ותזונה.</p><p>אחת הדרכים הנפוצות לחסוך בסופר פארם היא שימוש בקופוני הנחה באפליקציה ובאתר, בשילוב עם כרטיס מועדון Dream Card.</p>`,
9105    tip: 'הקופונים של סופר פארם לרוב תקפים לזמן קצר — שווה לבדוק אותם בתחילת כל חודש.',
9106    faq: [
9107      { q: 'איך משתמשים בקופון סופר פארם אונליין?', a: 'בעמוד התשלום באתר super-pharm.co.il יש שדה "קוד קופון" — הזן את הקוד לפני השלמת הרכישה.' },
9108      { q: 'האם ניתן לשלב קופון עם Dream Card?', a: 'בדרך כלל לא — קופונים ומועדון Dream Card הם הטבות נפרדות. בדוק את תנאי הקופון.' },
9109    ],
9110    coupons: [
9111      { code: 'PHARM15', discount: '15% על קוסמטיקה', notes: 'תנאים חלים', expiry: '30/06/2026', votes: 55 },
9112      { code: 'BEAUTY20', discount: '20% בשמים ומוצרי יוקרה', notes: 'מינימום 200₪', expiry: '15/07/2026', votes: 33 },
9113      { code: 'HEALTH10', discount: '10% תוספי תזונה', notes: 'על הזמנה באתר', expiry: null, votes: 21 },
9114      { code: 'FREESHIP', discount: 'משלוח חינם', notes: 'ללא מינימום', expiry: '01/07/2026', votes: 18 },
9115      { code: 'BABY25', discount: '25% מוצרי תינוקות', notes: 'קטגוריה ייעודית', expiry: null, votes: 12 },
9116    ]
9117  },
9118  aliexpress: {
9119    name: 'AliExpress', nameHe: 'AliExpress', domain: 'aliexpress.com', origin: 'INTL',
9120    categories: ['אלקטרוניקה', 'אופנה', 'בית', 'גאדג\'טים'],
9121    desc: 'AliExpress היא פלטפורמת הקניות הבינלאומית של Alibaba עם מיליוני מוצרים במחירים הנמוכים ביותר בעולם.',
9122    longDesc: `<p>AliExpress היא פלטפורמת הקניות הבינלאומית של Alibaba, המאפשרת רכישה ישירה ממוכרים בסין ומרחבי העולם. האתר מציע מוצרים בקטגוריות אין-ספור — אלקטרוניקה, אופנה, ציוד לבית, תכשיטים, צעצועים ועוד.</p><p>הקופונים מגיעים בכמה צורות: קופוני מוכר ספציפי, קופוני אתר רחבים, ומבצעי עונה כמו 11.11 ו-Super Sale.</p>`,
9123    tip: 'כדאי לאסוף קופונים דרך סל הקניות לפני הרכישה ולשלב מספר קופונים יחד לחיסכון מקסימלי.',
9124    faq: [
9125      { q: 'איך משתמשים בקוד קופון AliExpress?', a: 'בסל הקניות לחץ על "הוסף קוד קופון". ניתן לשלב קופון מוכר וקופון אתר באותה עסקה.' },
9126      { q: 'כמה זמן לוקח המשלוח מ-AliExpress לישראל?', a: 'בדרך כלל 2–4 שבועות. עם AliExpress Standard Shipping ניתן לקבל תוך 7–14 ימים.' },
9127    ],
9128    coupons: [
9129      { code: 'NEW10', discount: '10$ הנחה להזמנה ראשונה', notes: 'מינימום 15$', expiry: null, votes: 88 },
9130      { code: 'ALI20', discount: '20% על אלקטרוניקה', notes: 'מעל 50$', expiry: '30/06/2026', votes: 44 },
9131      { code: 'SHIP0', discount: 'משלוח חינם', notes: 'ממוכרים נבחרים', expiry: null, votes: 37 },
9132      { code: 'SUMMER15', discount: '15$ על 100$', notes: 'קיץ 2026', expiry: '31/08/2026', votes: 29 },
9133    ]
9134  },
9135  asos: {
9136    name: 'ASOS', nameHe: 'ASOS', domain: 'asos.com', origin: 'INTL',
9137    categories: ['אופנה', 'נעליים', 'אביזרים'],
9138    desc: 'ASOS היא אחת מפלטפורמות האופנה הגדולות בעולם עם למעלה מ-850 מותגים וקולקציה עצומה של בגדים ונעליים.',
9139    longDesc: `<p>ASOS היא אחת מפלטפורמות האופנה הגדולות בעולם, עם למעלה מ-850 מותגים וקולקציה עצומה של בגדים, נעליים ואביזרים לגברים ולנשים. ASOS מאפשרת משלוח לישראל.</p><p>קופוני ASOS מופיעים בדרך כלל כהנחה באחוזים על כל ההזמנה, ולעיתים גם כמשלוח חינם. הנחות של 10%–20% נפוצות.</p>`,
9140    tip: 'ASOS מציעה משלוח חינם מעל סכום מינימלי — שווה לאגור כמה פריטים בסל ולהגיע לסף, ואז להוסיף קוד קופון.',
9141    faq: [
9142      { q: 'איך להשתמש בקופון ASOS?', a: 'בדף התשלום יש שדה "הזן קוד פרומו". הכנס את הקוד לפני השלמת הרכישה.' },
9143    ],
9144    coupons: [
9145      { code: 'ASOS20', discount: '20% על כל ההזמנה', notes: 'מינימום 150₪', expiry: '30/06/2026', votes: 61 },
9146      { code: 'ASOSSHIP', discount: 'משלוח חינם', notes: 'ללא מינימום', expiry: null, votes: 38 },
9147      { code: 'SALE15', discount: '15% סייל קיץ', notes: 'גם על פריטי סייל', expiry: '31/08/2026', votes: 22 },
9148      { code: 'NEW25', discount: '25% ללקוחות חדשים', notes: 'הזמנה ראשונה בלבד', expiry: null, votes: 17 },
9149    ]
9150  },
9151  castro: {
9152    name: 'Castro', nameHe: 'קסטרו', domain: 'castro.com', origin: 'IL',
9153    categories: ['אופנה', 'נשים', 'גברים', 'ילדים'],
9154    desc: 'קסטרו היא רשת אופנה ישראלית מובילה עם עשרות שנות ניסיון, המציעה קולקציות לנשים, גברים וילדים.',
9155    longDesc: `<p>קסטרו היא אחת מרשתות האופנה הוותיקות והמובילות בישראל, עם עשרות סניפים ברחבי הארץ וחנות אונליין. החברה מציעה קולקציות עונתיות לנשים, גברים וילדים תחת מספר מותגים.</p><p>קופוני קסטרו נפוצים מאוד ומגיעים דרך הניוזלטר, האפליקציה ורשתות חברתיות. לרוב מדובר בהנחות של 10%–30% על הרכישה.</p>`,
9156    tip: 'הירשם לניוזלטר של קסטרו לקבלת קופון קבלת פנים של 15% ועדכונים על מבצעים בלעדיים.',
9157    faq: [
9158      { q: 'איך להשתמש בקוד קופון קסטרו?', a: 'בעמוד התשלום באתר castro.com יש שדה "קוד הטבה". הכנס את הקוד לפני אישור ההזמנה.' },
9159      { q: 'כמה ניתן לחסוך עם קופון קסטרו?', a: 'בדרך כלל 10%–30% על הזמנה. בתקופות סייל ניתן לשלב ולהגיע לחיסכון גבוה יותר.' },
9160      { q: 'האם ניתן להשתמש בקופון קסטרו בסניפים?', a: 'רוב הקופונים המקוונים תקפים לאתר בלבד. קופוני סניפים מגיעים בנפרד דרך האפליקציה.' },
9161    ],
9162    coupons: [
9163      { code: 'CASTRO20', discount: '20% על כל הקולקציה', notes: 'לא על פריטי סייל', expiry: '15/07/2
9163026', votes: 74 },
9164      { code: 'WELCOME15', discount: '15% ללקוחות חדשים', notes: 'הרשמה לניוזלטר', expiry: null, votes: 51 },
9165      { code: 'SUMMER30', discount: '30% קולקציית קיץ', notes: 'פריטים נבחרים', expiry: '31/08/2026', votes: 39 },
9166      { code: 'SHIP0', discount: 'משלוח חינם', notes: 'מעל 250₪', expiry: null, votes: 27 },
9167      { code: 'KIDS25', discount: '25% מדור ילדים', notes: 'קטגוריה ייעודית', expiry: '30/06/2026', votes: 18 },
9168      { code: 'APP10', discount: '10% דרך האפליקציה', notes: 'הזמנה דרך אפליקציה בלבד', expiry: null, votes: 14 },
9169    ]
9170  }
9171};
9172
9173// ── Share SVG icon ────────────────────────────────────────────────────────
9174const _SD_SHARE_ICON = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>`;
9175
9176function _sdCouponCard(c, storeName) {
9177  const peek = (c.code || '').slice(-2);
9178  const shareUrl = `couponai.co.il/store/${_storeSlug(storeName)}#coupon-${encodeURIComponent(c.code)}`;
9179  return `
9180    <div style="background:var(--gray1);border:1px solid ${c.votes>30?'rgba(0,232,122,0.3)':'var(--gray3)'};border-radius:12px;padding:16px;position:relative;overflow:hidden">
9181      ${c.votes>30 ? `<div style="position:absolute;top:0;left:0;right:0;height:2px;background:var(--accent)"></div>` : ''}
9182      <div style="font-size:22px;font-weight:800;color:var(--white);margin-bottom:6px;letter-spacing:-0.5px">${c.discount}</div>
9183      ${c.notes ? `<div style="font-size:12px;color:var(--gray5);margin-bottom:12px">${c.notes}</div>` : ''}
9184      <div style="display:flex;align-items:center;gap:8px;direction:ltr">
9185        <div style="flex:1;background:#00e87a;border-radius:6px;padding:10px 12px;font-family:monospace;font-size:14px;font-weight:800;color:#000;letter-spacing:2px;cursor:pointer;text-align:center">
9186          <span style="opacity:0.4">••••</span>${peek}
9187        </div>
9188        <button onclick="navigator.clipboard.writeText('${shareUrl}')" title="שתף קופון זה" style="background:var(--gray3);border:1px solid var(--gray4);border-radius:6px;padding:10px;cursor:pointer;color:var(--gray5);display:flex;align-items:center">${_SD_SHARE_ICON}</button>
9189      </div>
9190      <div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px">
9191        ${c.expiry ? `<span style="font-size:11px;color:var(--gray5)">תוקף עד ${c.expiry}</span>` : `<span></span>`}
9192        <span style="font-size:11px;color:var(--gray5)">👍 ${c.votes}</span>
9193      </div>
9194    </div>`;
9195}
9196
9197function _sdCouponRow(c, storeName) {
9198  const shareUrl = `couponai.co.il/store/${_storeSlug(storeName)}#coupon-${encodeURIComponent(c.code)}`;
9199  return `
9200    <div style="display:flex;align-items:center;gap:16px;padding:14px 16px;border-bottom:1px solid var(--gray3);direction:rtl">
9201      <div style="flex:1;min-width:0">
9202        <div style="font-size:15px;font-weight:700;color:var(--white)">${c.discount}</div>
9203        ${c.notes ? `<div style="font-size:12px;color:var(--gray5);margin-top:2px">${c.notes}</div>` : ''}
9204      </div>
9205      <div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
9206        ${c.expiry ? `<span style="font-size:11px;color:var(--gray5)">${c.expiry}</span>` : ''}
9207        <button onclick="navigator.clipboard.writeText('${shareUrl}')" title="שתף" style="background:transparent;border:none;cursor:pointer;color:var(--gray5);padding:4px;display:flex">${_SD_SHARE_ICON}</button>
9208        <div style="background:#00e87a;border-radius:6px;padding:8px 14px;font-family:monospace;font-size:12px;font-weight:800;color:#000;letter-spacing:1px;cursor:pointer;white-space:nowrap">
9209          <span style="opacity:0.4">••</span>${(c.code||'').slice(-2)} גלה
9210        </div>
9211      </div>
9212    </div>`;
9213}
9214
9215// ── Layout A: Hero + Grid ─────────────────────────────────────────────────
9216function _sdLayoutA(store) {
9217  const fav = `https://www.google.com/s2/favicons?domain=${store.domain}&sz=64`;
9218  const cats = store.categories.map(c => `<span style="background:var(--gray3);border-radius:20px;padding:3px 10px;font-size:11px;color:var(--gray5)">${c}</span>`).join('');
9219  const cards = store.coupons.map(c => _sdCouponCard(c, store.name)).join('');
9220  return `
9221    <div style="background:var(--black);direction:rtl;font-family:'DM Sans',sans-serif">
9222      <!-- Nav -->
9223      <div style="background:var(--gray1);border-bottom:1px solid var(--gray3);padding:14px 28px;display:flex;align-items:center;justify-content:space-between">
9224        <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:800">קופון<span style="color:var(--accent)">AI</span></div>
9225        <div style="font-size:12px;color:var(--gray5)">ראשי &rsaquo; חנויות &rsaquo; ${store.nameHe}</div>
9226      </div>
9227      <!-- Hero -->
9228      <div style="background:linear-gradient(180deg,var(--gray1) 0%,var(--black) 100%);padding:36px 28px 28px">
9229        <div style="display:flex;align-items:center;gap:18px;margin-bottom:16px">
9230          <img src="${fav}" width="64" height="64" style="border-radius:14px;background:var(--gray2);padding:4px" onerror="this.style.display='none'"/>
9231          <div>
9232            <h1 style="font-family:'Syne',sans-serif;font-size:28px;font-weight:800;margin:0 0 4px">${store.nameHe} - קופונים והנחות</h1>
9233            <div style="font-size:12px;color:var(--gray5)">עודכן יוני 2026 &bull; ${store.coupons.length} קופונים פעילים</div>
9234          </div>
9235          <div style="margin-right:auto;display:flex;align-items:center;gap:8px">
9236            <button style="background:var(--accent);color:#000;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;display:flex;align-items:center;gap:6px">${_SD_SHARE_ICON} שתף דף חנות</button>
9237            <a href="#" style="background:var(--gray3);color:var(--white);border:none;border-radius:8px;padding:9px 16px;font-size:13px;cursor:pointer;text-decoration:none">כניסה לאתר ↗</a>
9238          </div>
9239        </div>
9240        <div style="font-size:13px;color:var(--gray5);line-height:1.7;max-width:640px;margin-bottom:16px">${store.desc}</div>
9241        <div style="display:flex;gap:6px;flex-wrap:wrap">${cats}</div>
9242        <!-- Stats -->
9243        <div style="display:flex;gap:24px;margin-top:20px;padding-top:20px;border-top:1px solid var(--gray3)">
9244          <div><div style="font-size:22px;font-weight:800;font-family:'Syne',sans-serif;color:var(--accent)">${store.coupons.length}</div><div style="font-size:11px;color:var(--gray5)">קופונים פעילים</div></div>
9245          <div><div style="font-size:22px;font-weight:800;font-family:'Syne',sans-serif">${store.coupons[0]?.discount?.includes('%') ? store.coupons[0].discount.match(/\d+/)?.[0]+'%' : 'עד 30%'}</div><div style="font-size:11px;color:var(--gray5)">הנחה מקסימלית</div></div>
9246          <div><div style="font-size:22px;font-weight:800;font-family:'Syne',sans-serif">${store.origin==='IL' ? '🇮🇱' : '🌍'}</div><div style="font-size:11px;color:var(--gray5)">${store.origin==='IL' ? 'חנות ישראלית' : 'חנות בינלאומית'}</div></div>
9247        </div>
9248      </div>
9249      <!-- Coupons grid -->
9250      <div style="padding:0 28px 36px">
9251        <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:16px">כל הקופונים של ${store.nameHe}</div>
9252        <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px">${cards}</div>
9253      </div>
9254      <!-- About -->
9255      <div style="padding:0 28px 36px;border-top:1px solid var(--gray3)">
9256        <div style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;margin-bottom:12px">אודות ${store.nameHe}</div>
9257        <div style="font-size:13px;color:var(--gray5);line-height:1.8">${store.longDesc}</div>
9258        <div style="margin-top:14px;background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:10px;padding:12px 16px;font-size:13px;color:var(--gray6)">
9259          💡 <strong style="color:var(--accent)">טיפ:</strong> ${store.tip}
9260        </div>
9261      </div>
9262    </div>`;
9263}
9264
9265// ── Layout B: Sidebar + List ──────────────────────────────────────────────
9266function _sdLayoutB(store) {
9267  const fav = `https://www.google.com/s2/favicons?domain=${store.domain}&sz=64`;
9268  const cats = store.categories.map(c => `<div style="padding:6px 0;border-bottom:1px solid var(--gray3);font-size:12px;color:var(--gray5)">${c}</div>`).join('');
9269  const rows = store.coupons.map(c => _sdCouponRow(c, store.name)).join('');
9270  return `
9271    <div style="background:var(--black);direction:rtl;font-family:'DM Sans',sans-serif">
9272      <!-- Nav -->
9273      <div style="background:var(--gray1);border-bottom:1px solid var(--gray3);padding:14px 28px;display:flex;align-items:center;justify-content:space-between">
9274        <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:800">קופון<span style="color:var(--accent)">AI</span></div>
9275        <div style="font-size:12px;color:var(--gray5)">ראשי &rsaquo; חנויות &rsaquo; ${store.nameHe}</div>
9276      </div>
9277      <div style="display:flex;gap:0;min-height:600px">
9278        <!-- Sidebar -->
9279        <div style="width:260px;flex-shrink:0;border-left:1px solid var(--gray3);padding:24px 20px;background:var(--gray1)">
9280          <div style="text-align:center;margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid var(--gray3)">
9281            <img src="${fav}" width="56" height="56" style="border-radius:12px;background:var(--gray2);padding:4px;margin-bottom:10px" onerror="this.style.display='none'"/>
9281
9282            <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700">${store.nameHe}</div>
9283            <div style="font-size:11px;color:var(--gray5);margin-top:2px">${store.domain}</div>
9284            <div style="margin-top:10px;background:rgba(0,232,122,0.1);border-radius:6px;padding:3px 10px;font-size:11px;color:var(--accent);display:inline-block">${store.origin==='IL' ? 'חנות ישראלית' : 'חנות בינלאומית'}</div>
9285          </div>
9286          <div style="margin-bottom:16px">
9287            <div style="font-size:11px;font-weight:600;color:var(--gray5);margin-bottom:8px;letter-spacing:0.5px">קטגוריות</div>
9288            ${cats}
9289          </div>
9290          <div style="margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--gray3)">
9291            <div style="font-size:11px;font-weight:600;color:var(--gray5);margin-bottom:8px;letter-spacing:0.5px">סטטיסטיקות</div>
9292            <div style="font-size:13px;color:var(--gray6);margin-bottom:4px">${store.coupons.length} קופונים פעילים</div>
9293            <div style="font-size:13px;color:var(--gray6)">עודכן: יוני 2026</div>
9294          </div>
9295          <a href="#" style="display:block;text-align:center;background:var(--accent);color:#000;border-radius:8px;padding:10px;font-size:13px;font-weight:700;text-decoration:none;margin-bottom:8px">כניסה לאתר ↗</a>
9296          <button onclick="navigator.clipboard.writeText('couponai.co.il/store/${_storeSlug(store.name)}')" style="display:block;width:100%;text-align:center;background:var(--gray3);color:var(--white);border:none;border-radius:8px;padding:10px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px">${_SD_SHARE_ICON} שתף דף חנות</button>
9297        </div>
9298        <!-- Main -->
9299        <div style="flex:1;min-width:0">
9300          <div style="padding:24px 28px;border-bottom:1px solid var(--gray3)">
9301            <h1 style="font-family:'Syne',sans-serif;font-size:22px;font-weight:800;margin:0 0 8px">קודי קופון ${store.nameHe} - עודכן יוני 2026</h1>
9302            <div style="font-size:13px;color:var(--gray5);line-height:1.7">${store.desc}</div>
9303            <div style="margin-top:14px;background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:8px;padding:10px 14px;font-size:12px;color:var(--gray6)">
9304              💡 ${store.tip}
9305            </div>
9306          </div>
9307          <div style="padding:20px 28px 8px;display:flex;align-items:center;justify-content:space-between">
9308            <div style="font-family:'Syne',sans-serif;font-size:15px;font-weight:700">${store.coupons.length} קופונים זמינים</div>
9309            <div style="font-size:11px;color:var(--gray5)">ממוין לפי הצבעות</div>
9310          </div>
9311          <div style="background:var(--gray1);border-radius:0">${rows}</div>
9312          <!-- About -->
9313          <div style="padding:24px 28px">
9314            <div style="font-family:'Syne',sans-serif;font-size:15px;font-weight:700;margin-bottom:10px">אודות ${store.nameHe}</div>
9315            <div style="font-size:13px;color:var(--gray5);line-height:1.8">${store.longDesc}</div>
9316          </div>
9317        </div>
9318      </div>
9319    </div>`;
9320}
9321
9322// ── Layout C: Minimal SEO ─────────────────────────────────────────────────
9323function _sdLayoutC(store) {
9324  const fav = `https://www.google.com/s2/favicons?domain=${store.domain}&sz=32`;
9325  const rows = store.coupons.map((c, i) => `
9326    <tr style="border-bottom:1px solid var(--gray3)">
9327      <td style="padding:14px 16px;font-size:14px;font-weight:700;color:var(--white)">${c.discount}</td>
9328      <td style="padding:14px 16px;font-family:monospace;font-size:13px">
9329        <span style="background:var(--gray3);border-radius:6px;padding:6px 12px;color:var(--gray5);letter-spacing:1px">••••${(c.code||'').slice(-2)}</span>
9330      </td>
9331      <td style="padding:14px 16px;font-size:12px;color:var(--gray5)">${c.notes||''}</td>
9332      <td style="padding:14px 16px;font-size:12px;color:var(--gray5);white-space:nowrap">${c.expiry||'ללא תוקף'}</td>
9333      <td style="padding:14px 16px">
9334        <div style="display:flex;align-items:center;gap:6px">
9335          <button style="background:#00e87a;border:none;border-radius:6px;padding:7px 12px;font-size:12px;font-weight:700;color:#000;cursor:pointer;white-space:nowrap">גלה קוד</button>
9336          <button onclick="navigator.clipboard.writeText('couponai.co.il/store/${_storeSlug(store.name)}#coupon-${encodeURIComponent(c.code)}')" title="שתף" style="background:var(--gray3);border:none;border-radius:6px;padding:7px;cursor:pointer;color:var(--gray5);display:flex">${_SD_SHARE_ICON}</button>
9337        </div>
9338      </td>
9339    </tr>`).join('');
9340  const faqHtml = store.faq.map(f => `
9341    <div style="border:1px solid var(--gray3);border-radius:10px;padding:16px 20px;margin-bottom:10px">
9342      <div style="font-size:14px;font-weight:700;color:var(--white);margin-bottom:6px">${f.q}</div>
9343      <div style="font-size:13px;color:var(--gray5);line-height:1.6">${f.a}</div>
9344    </div>`).join('');
9345  return `
9346    <div style="background:var(--black);direction:rtl;font-family:'DM Sans',sans-serif;max-width:860px;margin:0 auto;padding:28px">
9347      <!-- Breadcrumb -->
9348      <div style="font-size:12px;color:var(--gray5);margin-bottom:20px">
9349        <a href="#" style="color:var(--gray5);text-decoration:none">ראשי</a> &rsaquo;
9350        <a href="#" style="color:var(--gray5);text-decoration:none">חנויות</a> &rsaquo;
9351        <span style="color:var(--white)">${store.nameHe}</span>
9352      </div>
9353      <!-- Header -->
9354      <div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
9355        <img src="${fav}" width="32" height="32" style="border-radius:8px;background:var(--gray2);padding:2px" onerror="this.style.display='none'"/>
9356        <h1 style="font-family:'Syne',sans-serif;font-size:24px;font-weight:800;margin:0">קודי קופון ${store.nameHe} - ${store.coupons.length} קופונים שעובדים (עודכן יוני 2026)</h1>
9357      </div>
9358      <div style="display:flex;gap:16px;align-items:center;margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid var(--gray3)">
9359        <div style="font-size:12px;color:var(--gray5)">${store.origin==='IL' ? '🇮🇱 חנות ישראלית' : '🌍 חנות בינלאומית'} &bull; ${store.domain}</div>
9360        <div style="margin-right:auto;display:flex;gap:8px">
9361          <button onclick="navigator.clipboard.writeText('couponai.co.il/store/${_storeSlug(store.name)}')" style="background:var(--gray3);border:none;border-radius:6px;padding:7px 12px;font-size:12px;cursor:pointer;color:var(--gray5);display:flex;align-items:center;gap:5px">${_SD_SHARE_ICON} שתף דף</button>
9362          <a href="#" style="background:var(--accent);color:#000;border-radius:6px;padding:7px 12px;font-size:12px;font-weight:700;text-decoration:none">כניסה לאתר ↗</a>
9363        </div>
9364      </div>
9365      <!-- Intro text (rich content for Google) -->
9366      <div style="font-size:13px;color:var(--gray5);line-height:1.8;margin-bottom:24px">${store.longDesc}</div>
9367      <!-- Coupons table -->
9368      <div style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;margin-bottom:12px">${store.coupons.length} קופוני ${store.nameHe} - יוני 2026</div>
9369      <div style="background:var(--gray1);border:1px solid var(--gray3);border-radius:10px;overflow:hidden;margin-bottom:32px">
9370        <table style="width:100%;border-collapse:collapse;font-size:13px">
9371          <thead>
9372            <tr style="background:var(--gray2);border-bottom:1px solid var(--gray3)">
9373              <th style="padding:12px 16px;font-weight:600;color:var(--gray5);text-align:right">הנחה</th>
9374              <th style="padding:12px 16px;font-weight:600;color:var(--gray5);text-align:right">קוד</th>
9375              <th style="padding:12px 16px;font-weight:600;color:var(--gray5);text-align:right">תנאים</th>
9376              <th style="padding:12px 16px;font-weight:600;color:var(--gray5);text-align:right">תוקף</th>
9377              <th style="padding:12px 16px;font-weight:600;color:var(--gray5);text-align:right"></th>
9378            </tr>
9379          </thead>
9380          <tbody>${rows}</tbody>
9381        </table>
9382      </div>
9383      <!-- Tip -->
9384      <div style="background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:10px;padding:14px 18px;font-size:13px;color:var(--gray6);margin-bottom:32px">
9385        💡 <strong style="color:var(--accent)">טיפ:</strong> ${store.tip}
9386      </div>
9387      <!-- FAQ (great for Google) -->
9388      <div style="margin-bottom:32px">
9389        <div style="font-family:'Syne',sans-serif;font-size:16px;font-weight:700;margin-bottom:14px">שאלות נפוצות - ${store.nameHe}</div>
9390        ${faqHtml}
9391      </div>
9392      <!-- Footer note -->
9393      <div style="font-size:11px;color:var(--gray4);padding-top:16px;border-top:1px solid var(--gray3)">
9394        דף זה עודכן לאחרונה ביוני 2026. כל הקופונים נבדקים על ידי הקהילה. couponai.co.il אינו אחראי לשינויים בתנאי השימוש של ${store.nameHe}.
9395      </div>
9396    </div>`;
9397}
9398
9399// ── Layout D: Enhanced Sidebar ────────────────────────────────────────────
9400// ── Layout D helpers (named fns avoid quote-escaping in onclick) ──────────
9401let _sdDStore = null;
9402function _sdDReveal(i) {
9403  const row    = document.getElementById('dcp-row-' + i);
9404  const masked = document.getElementById('dcp-masked-' + i);
9405  const code   = document.getElementById('dcp-code-' + i);
9406  if (!row || !masked || !code) return;
9407  row.classList.add('revealed');
9408  masked.classList.add('hiding');
9409  code.classList.add('visible');
9410  if (_sdDStore) {
9411    navigator.clipboard.writeText(_sdDStore.coupons[i].code);
9412    setTimeout(() => showToast('✓ הקוד הועתק!'), 200);
9413  }
9414}
9415function _sdDCopy(i) {
9416  if (!_sdDStore) return;
9417  const code = _sdDStore.coupons[i].code;
9418  navigator.clipboard.writeText(code);
9419  showToast('✓ הקוד הועתק!');
9420  const btn = document.getElementById('dcp-copy-' + i);
9421  const CHECK = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
9422  const COPY  = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
9423  if (btn) { btn.innerHTML = CHECK; setTimeout(() => { btn.innerHTML = COPY; }, 1500); }
9424}
9425function _sdDShare(i) {
9426  if (!_sdDStore) return;
9427  const url = 'couponai.co.il/store/' + _storeSlug(_sdDStore.name) + '#coupon-' + encodeURIComponent(_sdDStore.coupons[i].code);
9428  navigator.clipboard.writeText(url);
9429  const btn = document.getElementById('dcp-share-' + i);
9430  if (btn) { btn.style.color = 'var(--accent)'; setTimeout(() => { btn.style.color = 'var(--gray5)'; }, 1500); }
9431}
9432function _sdDLogoErr(img) {
9433  img.style.display = 'none';
9434  if (_sdDStore) img.insertAdjacentHTML('afterend', _storeInitial(_sdDStore.nameHe, 80));
9435}
9436
9437function _sdDSharePage() {
9438  if (!_sdDStore) return;
9439  const btn = document.getElementById('dcp-share-page');
9440  navigator.clipboard.writeText('couponai.co.il/store/' + _storeSlug(_sdDStore.name));
9441  if (btn) { btn.textContent = '✓ הועתק!'; setTimeout(() => { btn.innerHTML = _SD_SHARE_ICON + ' שתף דף חנות זה'; }, 1500); }
9442}
9443
9444function _sdLayoutD(store) {
9445  _sdDStore = store;
9446  const fav = `https://www.google.com/s2/favicons?domain=${store.domain}&sz=128`;
9447  const bestDiscount = store.coupons.reduce((best, c) => {
9448    const n = parseInt((c.discount||'').match(/\d+/)?.[0]||0);
9449    return n > best ? n : best;
9450  }, 0);
9451  const totalVotes = store.coupons.reduce((s,c) => s+(c.votes||0), 0);
9452  const cats = store.categories.map(c =>
9453    `<span style="background:var(--gray3);border-radius:20px;padding:4px 12px;font-size:12px;color:var(--gray5);display:inline-block;margin:3px 3px 0 0">${c}</span>`
9454  ).join('');
9455
9456  const related = Object.entries(_STORE_DEMO_DATA)
9457    .filter(([k,v]) => k !== _sdCurrentStore)
9458    .slice(0,5)
9459    .map(([k,v]) => `
9460      <div onclick="_sdSetStore('${k}')" style="display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;cursor:pointer" onmouseover="this.style.background='var(--gray3)'" onmouseout="this.style.background='transparent'">
9461        <img src="https://www.google.com/s2/favicons?domain=${v.domain}&sz=32" width="24" height="24" style="border-radius:6px;background:#fff" onerror="this.style.display='none'"/>
9462        <span style="font-size:12px;color:var(--gray6)">${v.nameHe}</span>
9463        <span style="margin-right:auto;font-size:11px;color:var(--accent)">${v.coupons.length} קופונים</span>
9464      </div>`).join('');
9465
9466  const EYE_SVG = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`;
9467  const COPY_SVG = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>`;
9468
9469  const coupons = store.coupons.map((c,i) => {
9470    const peek = (c.code||'').slice(-2);
9471    const views = Math.floor(c.votes * 7 + 12);
9472    return `
9473      <div style="background:var(--gray1);border:1px solid rgba(0,232,122,0.25);border-radius:14px;padding:18px 22px 12px 18px;position:relative;overflow:hidden;direction:rtl">
9474        <div style="position:absolute;top:0;right:0;bottom:0;width:3px;background:var(--accent);border-radius:3px 0 0 3px"></div>
9475        <div style="display:flex;align-items:flex-start;gap:20px">
9476          <!-- Right column: discount + notes only -->
9477          <div style="flex:1;min-width:0;padding-right:6px">
9478            <div style="font-family:'Syne',sans-serif;font-size:26px;font-weight:800;color:var(--white);letter-spacing:-0.5px;margin-bottom:5px">${c.discount}</div>
9479            ${c.notes ? `<div style="font-size:14px;color:var(--gray5)">${c.notes}</div>` : ''}
9480          </div>
9481          <!-- Left column: direction:ltr — aligns everything to visual left -->
9482          <div style="display:flex;flex-direction:column;gap:8px;flex-shrink:0;direction:ltr;align-items:flex-start">
9483            <!-- Row 1: share | hidden code | copy -->
9484            <div id="dcp-row-${i}" class="cc-code-row" style="display:flex;align-items:center;gap:6px;direction:ltr">
9485              <button id="dcp-share-${i}" onclick="_sdDShare(${i})" title="שתף קופון" style="background:var(--gray2);border:1px solid var(--gray3);border-radius:7px;color:var(--gray5);cursor:pointer;width:38px;height:38px;display:flex;align-items:center;justify-content:center;flex-shrink:0">${_SD_SHARE_ICON}</button>
9486              <div class="cc-code" onclick="_sdDReveal(${i})" style="width:170px;height:38px;border-radius:7px;padding:0 14px;flex-shrink:0;font-size:15px">
9487                <div id="dcp-masked-${i}" class="cc-code-masked">
9488                  <span class="cc-code-peek">
9489                    <span class="cc-code-dots">••••••</span>
9490                    <span class="cc-code-end">${peek}</span>
9491                  </span>
9492                  <span class="cc-code-eye"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></span>
9493                </div>
9494                <div class="cc-code-revealed" id="dcp-code-${i}">${c.code}</div>
9495              </div>
9496              <button id="dcp-copy-${i}" onclick="_sdDCopy(${i})" title="העתק קוד" class="cc-copy" style="border-radius:7px;height:38px;padding:0 12px;flex-shrink:0;align-self:stretch">${COPY_SVG}</button>
9497            </div>
9498            <!-- Row 2: voting -->
9499            <div style="display:flex;align-items:center;gap:6px">
9500              <button class="vote-btn vote-up" style="padding:5px 10px;font-size:12px">${THUMB_UP} <span>${c.votes}</span></button>
9501              <button class="vote-btn vote-down" style="padding:5px 10px;font-size:12px">${THUMB_DOWN}</button>
9502            </div>
9503          </div>
9504        </div>
9505        <!-- Bottom row: views (left) + expiry (right) on same line -->
9506        <div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px;direction:ltr">
9507          <div style="font-size:12px;color:var(--gray5)">${views} צפיות</div>
9508          ${c.expiry ? `<div style="font-size:12px;color:var(--gray5)">תוקף עד ${c.expiry}</div>` : ''}
9509        </div>
9510      </div>`;
9511  }).join('');
9512
9513  const faqHtml = store.faq.map(f => `
9514    <div style="border:1px solid var(--gray3);border-radius:10px;padding:16px 20px;margin-bottom:10px">
9515      <div style="font-size:14px;font-weight:700;color:var(--white);margin-bottom:6px">${f.q}</div>
9516      <div style="font-size:13px;color:var(--gray5);line-height:1.6">${f.a}</div>
9517    </div>`).join('');
9518
9519  return `
9520    <div style="background:var(--black);direction:rtl;font-family:'DM Sans',sans-serif">
9521      <!-- Nav -->
9522      <div style="background:var(--gray1);border-bottom:1px solid var(--gray3);padding:14px 28px;display:flex;align-items:center;justify-content:space-between">
9523        <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:800">קופון<span style="color:var(--accent)">AI</span></div>
9524        <div style="font-size:12px;color:var(--gray5)">ראשי &rsaquo; חנויות &rsaquo; ${store.nameHe}</div>
9525      </div>
9526
9527      <div style="display:flex;min-height:600px">
9528        <!-- ── Sidebar ── -->
9529        <div style="width:280px;flex-shrink:0;border-left:1px solid var(--gray3);background:var(--gray1);display:flex;flex-direction:column;gap:0">
9530
9531          <!-- Logo + name: logo → favicon → initials -->
9532          <div style="padding:28px 24px 20px;text-align:center;border-bottom:1px solid var(--gray3)">
9533            <div id="sd-sidebar-logo" style="width:80px;height:80px;border-radius:18px;overflow:hidden;margin:0 auto 14px;background:#fff">
9534              <img src="https://www.google.com/s2/favicons?domain=${store.domain}&sz=128" width="80" height="80"
9535                style="border-radius:18px;display:block"
9536                onerror="_sdDLogoErr(this)"/>
9537            </div>
9538            <div style="font-family:'Syne',sans-serif;font-size:22px;font-weight:800;color:var(--white)">${store.nameHe}</div>
9539            <div style="font-size:12px;color:var(--gray5);margin-top:3px">${store.domain}</div>
9540          </div>
9541
9542          <!-- Categories -->
9543          <div style="padding:18px 24px;border-bottom:1px solid var(--gray3)">
9544            <div style="font-size:11px;font-weight:600;color:var(--gray5);margin-bottom:10px;letter-spacing:0.5px">קטגוריות</div>
9545            <div>${cats}</div>
9546          </div>
9547
9548          <!-- Best discount + stats -->
9549          <div style="padding:18px 24px;border-bottom:1px solid var(--gray3)">
9550            ${bestDiscount > 0 ? `
9551            <div style="background:rgba(0,232,122,0.08);border:1px solid rgba(0,232,122,0.25);border-radius:10px;padding:12px 16px;margin-bottom:12px;text-align:center">
9552              <div style="font-family:'Syne',sans-serif;font-size:28px;font-weight:800;color:var(--accent)">עד ${bestDiscount}%</div>
9553              <div style="font-size:11px;color:var(--gray5)">ההנחה הגבוהה ביותר כרגע</div>
9554            </div>` : ''}
9555            <div style="display:flex;justify-content:space-between;font-size:12px;color:var(--gray5);margin-bottom:6px">
9556              <span>קופונים פעילים</span><span style="color:var(--white);font-weight:600">${store.coupons.length}</span>
9557            </div>
9558            <div style="display:flex;justify-content:space-between;font-size:12px;color:var(--gray5);margin-bottom:6px">
9559              <span>סה"כ הצבעות</span><span style="color:var(--white);font-weight:600">${totalVotes}</span>
9560            </div>
9561            <div style="display:flex;justify-content:space-between;font-size:12px;color:var(--gray5)">
9562              <span>עודכן</span><span style="color:var(--white);font-weight:600">יוני 2026</span>
9563            </div>
9564          </div>
9565
9566          <!-- Related stores + share btn -->
9567          <div style="padding:18px 24px;border-bottom:1px solid var(--gray3)">
9568            <div style="font-size:11px;font-weight:600;color:var(--gray5);margin-bottom:8px;letter-spacing:0.5px">חנויות דומות</div>
9569            ${related}
9570            <button id="dcp-share-page" onclick="_sdDSharePage()"
9571              style="display:flex;align-items:center;justify-content:center;gap:8px;width:100%;background:var(--gray3);color:var(--white);border:none;border-radius:10px;padding:12px;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:500;margin-top:12px">
9572              ${_SD_SHARE_ICON} שתף דף חנות זה
9573            </button>
9574          </div>
9575        </div>
9576
9577        <!-- ── Main content ── -->
9578        <div style="flex:1;min-width:0;overflow:hidden">
9579
9580          <!-- Title -->
9581          <div style="padding:32px 36px 24px;border-bottom:1px solid var(--gray3)">
9582            <h1 style="font-family:'Syne',sans-serif;font-size:32px;font-weight:800;margin:0 0 10px;letter-spacing:-0.5px">${store.nameHe} - קופונים והנחות</h1>
9583            <div style="font-size:13px;color:var(--gray5);margin-bottom:0">עודכן יוני 2026 &bull; ${store.coupons.length} קופונים פעילים</div>
9584          </div>
9585
9586          <!-- Full store description -->
9587          <div style="padding:24px 36px;border-bottom:1px solid var(--gray3)">
9588            <div style="font-size:14px;color:var(--gray5);line-height:1.85">${store.longDesc}</div>
9589            <div style="margin-top:16px;background:rgba(0,232,122,0.07);border:1px solid rgba(0,232,122,0.2);border-radius:10px;padding:14px 18px;font-size:13px;color:var(--gray6)">
9590              💡 <strong style="color:var(--accent)">טיפ:</strong> ${store.tip}
9591            </div>
9592          </div>
9593
9594          <!-- Coupons -->
9595          <div style="padding:24px 36px;border-bottom:1px solid var(--gray3)">
9596            <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:18px">${store.coupons.length} קופונים של ${store.nameHe}</div>
9597            <div style="display:flex;flex-direction:column;gap:14px">${coupons}</div>
9598          </div>
9599
9600          <!-- FAQ -->
9601          <div style="padding:24px 36px;border-bottom:1px solid var(--gray3)">
9602            <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:14px">שאלות נפוצות - ${store.nameHe}</div>
9603            ${faqHtml}
9604          </div>
9605
9606          <!-- Disclaimer -->
9607          <div style="padding:16px 36px">
9608            <div style="font-size:12px;color:var(--gray6);line-height:1.7">
9609              דף זה עודכן לאחרונה ביוני 2026. כל הקופונים נבדקים על ידי הקהילה. couponai.co.il אינו אחראי לשינויים בתנאי השימוש של ${store.nameHe}. תנאי השימוש בקופונים נקבעים על ידי ${store.nameHe} בלבד.
9610            </div>
9611          </div>
9612
9613        </div>
9614      </div>
9615    </div>`;
9616}
9617
9618function _sdRender() {
9619  const store = _STORE_DEMO_DATA[_sdCurrentStore];
9620  if (!store) return;
9621  const el = document.getElementById('store-demo-content');
9622  if (_sdCurrentLayout === 'a') el.innerHTML = _sdLayoutA(store);
9623  else if (_sdCurrentLayout === 'b') el.innerHTML = _sdLayoutB(store);
9624  else if (_sdCurrentLayout === 'c') el.innerHTML = _sdLayoutC(store);
9625  else el.innerHTML = _sdLayoutD(store);
9626}
9627
9628function _sdSetLayout(l) {
9629  _sdCurrentLayout = l;
9630  ['a','b','c','d'].forEach(x => {
9631    const btn = document.getElementById(`sd-layout-${x}`);
9632    if (!btn) return;
9633    btn.style.background = x === l ? 'var(--accent)' : 'var(--gray3)';
9634    btn.style.color = x === l ? '#000' : 'var(--white)';
9635    btn.style.fontWeight = x === l ? '600' : '400';
9636  });
9637  _sdRender();
9638}
9639
9640function _sdSetStore(key) {
9641  _sdCurrentStore = key;
9642  document.querySelectorAll('#sd-store-btns button').forEach(b => {
9643    const isActive = b.getAttribute('onclick')?.includes(`'${key}'`);
9644    b.style.background = isActive ? 'var(--accent)' : 'var(--gray3)';
9645    b.style.color = isActive ? '#000' : 'var(--white)';
9646    b.style.fontWeight = isActive ? '600' : '400';
9647  });
9648  _sdRender();
9649}
9650
9651function _loadStoreDemo() {
9652  _sdSetLayout('a');
9653  _sdSetStore('ksp');
9654}
9655
9656function _storeDemoShow(key) { _sdSetStore(key); }
9657
9658// ── Homepage Demo ─────────────────────────────────────────────────────────
9659let _hpCurrentLayout = 'a';
9660
9661const _HP_STORES = [
9662  { name:'ksp',        nameHe:'KSP',          domain:'ksp.co.il',          cat:'tech',    coupons:73, best:'עד 15%',    votes:340 },
9663  { name:'bug',        nameHe:'באג',           domain:'bug.co.il',          cat:'tech',    coupons:19, best:'עד 10%',    votes:89  },
9664  { name:'ivory',      nameHe:'Ivory',         domain:'ivory.co.il',        cat:'tech',    coupons:14, best:'5%',        votes:55  },
9665  { name:'castro',     nameHe:'קסטרו',         domain:'castro.com',         cat:'fashion', coupons:28, best:'עד 30%',   votes:210 },
9666  { name:'adika',      nameHe:'עדיקה',         domain:'adika.com',          cat:'fashion', coupons:34, best:'20%',       votes:156 },
9667  { name:'terminalx',  nameHe:'טרמינל X',      domain:'terminalx.com',      cat:'fashion', coupons:22, best:'15%',       votes:134 },
9668  { name:'renuar',     nameHe:'רנואר',         domain:'renuar.co.il',       cat:'fashion', coupons:11, best:'20%',       votes:77  },
9669  { name:'shein',      nameHe:'Shein',         domain:'shein.com',          cat:'fashion', coupons:41, best:'30%',       votes:445, intl:true },
9670  { name:'zara',       nameHe:'Zara',          domain:'zara.com',           cat:'fashion', coupons:8,  best:'20%',       votes:178, intl:true },
9671  { name:'asos',       nameHe:'ASOS',          domain:'asos.com',           cat:'fashion', coupons:26, best:'15%',       votes:231, intl:true },
9672  { name:'superpharm', nameHe:'סופר פארם',     domain:'super-pharm.co.il',  cat:'pharma',  coupons:45, best:'50 ₪',     votes:290 },
9673  { name:'iherb',      nameHe:'iHerb',         domain:'iherb.com',          cat:'pharma',  coupons:15, best:'22%',       votes:98,  intl:true },
9674  { name:'hoodies',    nameHe:"הודיס",         domain:'hoodies.co.il',      cat:'food',    coupons:7,  best:'10%',       votes:44  },
9675  { name:'aliexpress', nameHe:'AliExpress',    domain:'aliexpress.com',     cat:'intl',    coupons:89, best:'$3 הנחה',   votes:567, intl:true },
9676  { name:'amazon',     nameHe:'Amazon',        domain:'amazon.com',         cat:'intl',    coupons:12, best:'20%',       votes:203, intl:true },
9677  { name:'temu',       nameHe:'Temu',          domain:'temu.com',           cat:'intl',    coupons:38, best:'40%',       votes:521, intl:true },
9678  { name:'nike',       nameHe:'Nike',          domain:'nike.com',           cat:'fashion', coupons:6,  best:'10%',       votes:312, intl:true },
9679  { name:'ikea',       nameHe:'IKEA',          domain:'ikea.com/il',        cat:'home',    coupons:9,  best:'15%',       votes:188, intl:true },
9680  { name:'hamashbir',  nameHe:'המשביר',        domain:'hamashbir.co.il',    cat:'fashion', coupons:17, best:'20%',       votes:102 },
9681  { name:'golf',       nameHe:'גולף',          domain:'golf-kids.co.il',    cat:'fashion', coupons:13, best:'15%',       votes:91  },
9682  { name:'fox',        nameHe:'פוקס',          domain:'foxfashion.co.il',   cat:'fashion', coupons:21, best:'25%',       votes:145 },
9683  { name:'laline',     nameHe:'לאלין',         domain:'laline.co.il',       cat:'pharma',  coupons:12, best:'15%',       votes:88  },
9684  { name:'factory54',  nameHe:'פקטורי 54',     domain:'factory54.co.il',    c
9684at:'fashion', coupons:9,  best:'20%',       votes:67  },
9685  { name:'mango',      nameHe:'מנגו',          domain:'mango.com',          cat:'fashion', coupons:14, best:'30%',       votes:119, intl:true },
9686  { name:'hm',         nameHe:'H&M',           domain:'hm.com',             cat:'fashion', coupons:18, best:'20%',       votes:203, intl:true },
9687  { name:'next',       nameHe:'Next',          domain:'next.co.il',         cat:'fashion', coupons:7,  best:'15%',       votes:54  },
9688  { name:'pullbear',   nameHe:'Pull&Bear',     domain:'pullandbear.com',    cat:'fashion', coupons:6,  best:'20%',       votes:71,  intl:true },
9689  { name:'stradivarius',nameHe:'Stradivarius', domain:'stradivarius.com',   cat:'fashion', coupons:5,  best:'25%',       votes:63,  intl:true },
9690  { name:'gap',         nameHe:'GAP',          domain:'gap.com',            cat:'fashion', coupons:6,  best:'25%',       votes:91,  intl:true },
9691  { name:'forever21',  nameHe:'Forever 21',   domain:'forever21.com',      cat:'fashion', coupons:8,  best:'30%',       votes:76,  intl:true },
9692  { name:'pandora',    nameHe:'Pandora',      domain:'pandora.net',        cat:'fashion', coupons:6,  best:'15%',       votes:88,  intl:true },
9693  { name:'sephora',    nameHe:'Sephora',      domain:'sephora.com',        cat:'pharma',  coupons:11, best:'20%',       votes:134, intl:true },
9694  { name:'nespresso',  nameHe:'Nespresso',    domain:'nespresso.com',      cat:'food',    coupons:5,  best:'15%',       votes:67,  intl:true },
9695  { name:'delta',      nameHe:'דלתא',         domain:'delta.
9695co.il',        cat:'fashion', coupons:9,  best:'20%',       votes:61  },
9696  { name:'ace',        nameHe:'ACE',          domain:'ace.co.il',          cat:'home',    coupons:5,  best:'10%',       votes:38  },
9697  { name:'urban',      nameHe:'Urban OF',     domain:'urbanoutfitters.com',cat:'fashion', coupons:7,  best:'20%',       votes:82,  intl:true },
9698  { name:'zara-home',  nameHe:'Zara Home',    domain:'zarahome.com',       cat:'home',    coupons:4,  best:'15%',       votes:55,  intl:true },
9699];
9700
9701const _HP_CATS = [
9702  { id:'all',     label:'הכל',          emoji:'🏷️' },
9703  { id:'fashion', label:'אופנה',        emoji:'👗' },
9704  { id:'tech',    label:'טכנולוגיה',    emoji:'💻' },
9705  { id:'pharma',  label:'בריאות ויופי', emoji:'💊' },
9706  { id:'food',    label:'אוכל',         emoji:'🍔' },
9707  { id:'intl',    label:'בינלאומי',     emoji:'🌍' },
9708  { id:'home',    label:'בית',          emoji:'🏠' },
9709];
9710
9711function _hpLogo(s, size=36) {
9712  const st = `width:${size}px;height:${size}px;object-fit:contain`;
9713  const stored = STORE_LOGOS[s.name] || null;
9714  const domain = STORE_DOMAINS[s.name] || s.domain || null;
9715  const apiFav = domain ? `/api/favicon?domain=${encodeURIComponent(domain)}` : null;
9716  if (stored && apiFav)
9717    return `<img src="${stored}" style="${st}" onerror="this.onerror=null;this.src='${apiFav}'"/>`;
9718  if (stored)
9719    return `<img src="${stored}" style="${st}" onerror="this.onerror=null"/>`;
9720  if (apiFav)
9721    return `<img src="${apiFav}" style="${st}" onerror="this.onerror=null"/>`;
9722  return `<div style="${st};display:flex;align-items:center;justify-content:center;font-size:${Math.round(size*0.4)}px;font-weight:700;color:var(--white)">${(s.nameHe||s.name||'?')[0].toUpperCase()}</div>`;
9723}
9724
9725function _hpStoreLink(s) { return `/store/${_storeSlug(s.name)}`; }
9726
9727// ── Hero block (shared, kept close to current) ──
9728const _HP_HERO = `
9729  <div style="padding:40px 40px 32px;text-align:center;position:relative;overflow:hidden;border-bottom:1px solid var(--gray3)">
9730    <div style="position:absolute;inset:0;background:radial-gradient(ellipse 60% 40% at 50% 0%,rgba(0,232,122,0.08),transparent 70%);pointer-events:none"></div>
9731    <div style="display:inline-flex;align-items:center;gap:6px;background:var(--gray2);border:1px solid var(--gray3);border-radius:20px;padding:5px 14px;font-size:12px;color:var(--gray6);margin-bottom:20px">
9732      <span style="width:6px;height:6px;background:var(--accent);border-radius:50%;display:inline-block"></span>
9733      מופעל על ידי AI &ndash; מתעדכן כל שעה
9734    </div>
9735    <h1 style="font-family:'Syne',sans-serif;font-size:48px;font-weight:800;line-height:1.05;letter-spacing:-2px;margin-bottom:14px">
9736      כל <em style="color:var(--accent);font-style:normal">הקופונים</em>.<br>נמצאים אוטומטית.
9737    </h1>
9738    <p style="font-size:16px;color:var(--gray5);max-width:440px;margin:0 auto 24px;line-height:1.6">חפשו לפי חנות, קטגוריה, או גלו את ההנחות הכי טובות עכשיו.</p>
9739    <div style="max-width:560px;margin:0 auto;display:flex;gap:10px;align-items:center">
9740      <div style="flex:1;display:flex;align-items:center;gap:10px;background:var(--gray2);border:1px solid var(--gray3);border-radius:14px;padding:14px 18px">
9741        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--gray5)" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
9742        <span style="color:var(--gray5);font-size:15px">חפשו חנות, מותג, או קטגוריה...</span>
9743      </div>
9744      <button style="background:var(--accent);color:#000;border:none;border-radius:12px;padding:14px 24px;font-size:15px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif;white-space:nowrap">AI חיפוש ✦</button>
9745    </div>
9746  </div>`;
9747
9748// ── Layout A: Store Grid with category tabs ──
9749function _hpLayoutA() {
9750  const cats = _HP_CATS.slice(0,6);
9751  const catTabs = cats.map(c =>
9752    `<button onclick="_hpAFilter('${c.id}',this)" id="hpa-cat-${c.id}" style="padding:7px 16px;border-radius:20px;border:1px solid ${c.id==='all'?'var(--accent)':'var(--gray3)'};background:${c.id==='all'?'rgba(0,232,122,0.1)':'transparent'};color:${c.id==='all'?'var(--accent)':'var(--gray5)'};font-size:13px;
9752cursor:pointer;font-family:'DM Sans',sans-serif;white-space:nowrap">${c.emoji} ${c.label}</button>`
9753  ).join('');
9754
9755  const storeCards = _HP_STORES.slice(0,30).map(s => `
9756    <a href="${_hpStoreLink(s)}" data-cat="${s.cat}" class="hpa-store-card" style="display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;text-decoration:none;transition:all 0.15s;text-align:center"
9757       onmouseover="this.style.borderColor='rgba(0,232,122,0.4)';this.style.transform='translateY(-2px)'" onmouseout="this.style.borderColor='var(--gray3)';this.style.transform=''">
9758      <div style="width:72px;height:72px;border-radius:18px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0">
9759        ${_hpLogo(s,72)}
9760      </div>
9761      <div style="font-size:15px;font-weight:700;color:var(--white)">${s.nameHe}</div>
9762      <div style="font-size:13px;color:var(--gray5)">${s.coupons} קופונים</div>
9763    </a>`).join('');
9764
9765  return `
9766    <div style="background:var(--black);color:var(--white);font-family:'DM Sans',sans-serif;direction:rtl">
9767      ${_HP_HERO}
9768      <div style="padding:28px 36px">
9769        <div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:24px;justify-content:center">${catTabs}</div>
9770        <div id="hpa-grid" style="display:grid;grid-template-columns:repeat(5,1fr);gap:14px">${storeCards}</div>
9771        <div style="text-align:center;margin-top:28px">
9772          <button style="padding:12px 32px;border:1px solid var(--gray3);border-radius:12px;background:transparent;color:var(--gray6);font-size:14px;cursor:pointer;font-family:'DM Sans',sans-serif">הצג את כל 401 החנויות ↓</button>
9773        </div>
9774      </div>
9775    </div>`;
9776}
9777
9778// ── Layout B: Category rows (horizontal scroll per category) ──
9779function _hpLayoutB() {
9780  const catSections = _HP_CATS.slice(1,6).map(cat => {
9781    const stores = _HP_STORES.filter(s => s.cat === cat.id).slice(0,6);
9782    if (!stores.length) return '';
9783    const cards = stores.map(s => `
9784      <a href="${_hpStoreLink(s)}" style="flex-shrink:0;width:160px;background:var(--gray1);border:1px solid var(--gray3);border-radius:12px;padding:16px;text-decoration:none;display:flex;flex-direction:column;gap:10px;transition:border-color 0.15s"
9785         onmouseover="this.style.borderColor='rgba(0,232,122,0.4)'" onmouseout="this.style.borderColor='var(--gray3)'">
9786        <div style="display:flex;align-items:center;gap:10px">
9787          <div style="width:36px;height:36px;border-radius:9px;background:#fff;overflow:hidden;flex-shrink:0">${_hpLogo(s,36)}</div>
9788          <span style="font-size:13px;font-weight:600;color:var(--white)">${s.nameHe}</span>
9789        </div>
9790        <div style="font-size:11px;color:var(--gray5)">${s.coupons} קופונים פעילים</div>
9791        <div style="font-size:13px;font-weight:700;color:var(--accent)">${s.best}</div>
9792      </a>`).join('');
9793    return `
9794      <div style="margin-bottom:32px">
9795        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
9796          <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700">${cat.emoji} ${cat.label}</div>
9797          <a href="#" style="font-size:13px;color:var(--accent);text-decoration:none">כל החנויות ←</a>
9798        </div>
9799        <div style="display:flex;gap:10px;overflow-x:auto;padding-bottom:8px;scrollbar-width:none">${cards}</div>
9800      </div>`;
9801  }).join('');
9802
9803  return `
9804    <div style="background:var(--black);color:var(--white);font-family:'DM Sans',sans-serif;direction:rtl">
9805      ${_HP_HERO}
9806      <div style="padding:28px 36px">${catSections}</div>
9807    </div>`;
9808}
9809
9810// ── Layout C: Editorial featured + full grid ──
9811function _hpLayoutC() {
9812  const featured = _HP_STORES.filter(s => s.votes > 200).slice(0,3);
9813  const featuredCards = featured.map((s,i) => {
9814    const hues = [160,200,140];
9815    return `
9816      <a href="${_hpStoreLink(s)}" style="flex:1;min-width:0;background:linear-gradient(135deg,hsl(${hues[i]},30%,10%),var(--gray1));border:1px solid rgba(0,232,122,0.2);border-radius:18px;padding:28px 24px;text-decoration:none;display:flex;flex-direction:column;gap:12px;position:relative;overflow:hidden;transition:transform 0.15s"
9817         onmouseover="this.style.transform='translateY(-3px)'" onmouseout="this.style.transform=''">
9818        <div style="position:absolute;top:-20px;left:-20px;width:100px;height:100px;background:rgba(0,232,122,0.05);border-radius:50%"></div>
9819        <div style="display:flex;align-items:center;gap:12px">
9820          <div style="width:48px;height:48px;border-radius:12px;background:#fff;overflow:hidden">${_hpLogo(s,48)}</div>
9821          <div>
9822            <div style="font-size:11px;color:var(--accent);font-weight:600;letter-spacing:0.5px;margin-bottom:2px">הבחירה שלנו</div>
9823            <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:800;color:var(--white)">${s.nameHe}</div>
9824          </div>
9825        </div>
9826        <div style="font-size:13px;color:var(--gray5);line-height:1.6">${s.coupons} קופונים פעילים · ${s.votes} הצבעות קהילה</div>
9827        <div style="font-family:'Syne',sans-serif;font-size:28px;font-weight:800;color:var(--accent)">${s.best}</div>
9828        <div style="font-size:13px;color:var(--accent);font-weight:600;margin-top:4px">לחנות המלאה →</div>
9829      </a>`;
9830  }).join('');
9831
9832  const gridCards = _HP_STORES.map(s => `
9833    <a href="${_hpStoreLink(s)}" style="display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--gray1);border:1px solid var(--gray3);border-radius:10px;text-decoration:none;transition:border-color 0.15s"
9834       onmouseover="this.style.borderColor='rgba(0,232,122,0.35)'" onmouseout="this.style.borderColor='var(--gray3)'">
9835      <div style="width:32px;height:32px;border-radius:8px;background:#fff;overflow:hidden;flex-shrink:0">${_hpLogo(s,32)}</div>
9836      <div style="flex:1;min-width:0">
9837        <div style="font-size:13px;font-weight:600;color:var(--white);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${s.nameHe}</div>
9838        <div style="font-size:11px;color:var(--gray5)">${s.coupons} קופונים</div>
9839      </div>
9840      <div style="font-size:12px;color:var(--accent);font-weight:700;flex-shrink:0">${s.best}</div>
9841    </a>`).join('');
9842
9843  return `
9844    <div style="background:var(--black);color:var(--white);font-family:'DM Sans',sans-serif;direction:rtl">
9845      ${_HP_HERO}
9846      <div style="padding:32px 36px">
9847        <div style="font-family:'Syne',sans-serif;font-size:20px;font-weight:700;margin-bottom:16px">⭐ הבחירות שלנו השבוע</div>
9848        <div style="display:flex;gap:14px;margin-bottom:36px">${featuredCards}</div>
9849        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
9850          <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700">כל החנויות</div>
9851          <span style="font-size:13px;color:var(--gray5)">401 חנויות</span>
9852        </div>
9853        <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px">${gridCards}</div>
9854      </div>
9855    </div>`;
9856}
9857
9858// ── Layout D: Logo cloud (minimal) + best deals ──
9859function _hpLayoutD() {
9860  const logoCloud = _HP_STORES.map(s => `
9861    <a href="${_hpStoreLink(s)}" title="${s.nameHe}" style="display:flex;flex-direction:column;align-items:center;gap:6px;padding:12px 10px;border-radius:12px;text-decoration:none;transition:background 0.15s"
9862       onmouseover="this.style.background='var(--gray2)'" onmouseout="this.style.background='transparent'">
9863      <div style="width:44px;height:44px;border-radius:12px;background:#fff;overflow:hidden;border:1px solid var(--gray3)">${_hpLogo(s,44)}</div>
9864      <span style="font-size:11px;color:var(--gray5);text-align:center;max-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${s.nameHe}</span>
9865    </a>`).join('');
9866
9867  const hotDeals = _HP_STORES.filter(s=>s.votes>200).slice(0,4).map(s => `
9868    <a href="${_hpStoreLink(s)}" style="flex:1;min-width:180px;display:flex;align-items:center;gap:14px;padding:18px;background:var(--gray1);border:1px solid rgba(0,232,122,0.2);border-radius:14px;text-decoration:none;transition:border-color 0.15s"
9869       onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='rgba(0,232,122,0.2)'">
9870      <div style="width:40px;height:40px;border-radius:10px;background:#fff;overflow:hidden;flex-shrink:0">${_hpLogo(s,40)}</div>
9871      <div>
9872        <div style="font-size:13px;color:var(--gray5);margin-bottom:3px">${s.nameHe}</div>
9873        <div style="font-family:'Syne',sans-serif;font-size:22px;font-weight:800;color:var(--accent)">${s.best}</div>
9874      </div>
9875    </a>`).join('');
9876
9877  return `
9878    <div style="background:var(--black);color:var(--white);font-family:'DM Sans',sans-serif;direction:rtl">
9879      ${_HP_HERO}
9880      <div style="padding:32px 36px">
9881        <div style="font-size:12px;font-weight:600;color:var(--gray5);letter-spacing:0.5px;margin-bottom:16px;text-align:center">401 חנויות — לחצו לכניסה ישירה</div>
9882        <div style="display:flex;flex-wrap:wrap;justify-content:center;gap:4px;margin-bottom:36px">${logoCloud}</div>
9883        <div style="height:1px;background:var(--gray3);margin-bottom:28px"></div>
9884        <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:14px">🔥 הנחות הכי טובות עכשיו</div>
9885        <div style="display:flex;flex-wrap:wrap;gap:10px">${hotDeals}</div>
9886      </div>
9887    </div>`;
9888}
9889
9890// ── Layout E: Category-first discovery ──
9891function _hpLayoutE() {
9892  const catCards = _HP_CATS.slice(1).map(cat => {
9893    const stores = _HP_STORES.filter(s => s.cat === cat.id).slice(0,3);
9894    const logos = stores.map(s =>
9895      `<div style="width:28px;height:28px;border-radius:7px;background:#fff;overflow:hidden;border:1px solid var(--gray3);flex-shrink:0">${_hpLogo(s,28)}</div>`
9896    ).join('');
9897    const total = _HP_STORES.filter(s => s.cat === cat.id).length;
9898    const bgs = {fashion:'rgba(255,100,150,0.07)',tech:'rgba(100,150,255,0.07)',pharma:'rgba(100,220,150,0.07)',food:'rgba(255,180,50,0.07)',intl:'rgba(100,200,255,0.07)',home:'rgba(200,150,100,0.07)'};
9899    const borders = {fashion:'rgba(255,100,150,0.2)',tech:'rgba(100,150,255,0.2)',pharma:'rgba(100,220,150,0.2)',food:'rgba(255,180,50,0.2)',intl:'rgba(100,200,255,0.2)',home:'rgba(200,150,100,0.2)'};
9900    return `
9901      <div style="background:${bgs[cat.id]||'var(--gray1)'};border:1px solid ${borders[cat.id]||'var(--gray3)'};border-radius:16px;padding:22px 20px;cursor:pointer;transition:transform 0.15s"
9902           onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform=''">
9903        <div style="font-size:28px;margin-bottom:10px">${cat.emoji}</div>
9904        <div style="font-family:'Syne',sans-serif;font-size:17px;font-weight:700;color:var(--white);margin-bottom:6px">${cat.label}</div>
9905        <div style="font-size:12px;color:var(--gray5);margin-bottom:14px">${total} חנויות · ${_HP_STORES.filter(s=>s.cat===cat.id).reduce((a,s)=>a+s.coupons,0)} קופונים</div>
9906        <div style="display:flex;gap:5px">${logos}</div>
9907      </div>`;
9908  }).join('');
9909
9910  const popularStores = [..._HP_STORES].sort((a,b)=>b.votes-a.votes).slice(0,8).map(s => `
9911    <a href="${_hpStoreLink(s)}" style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--gray1);border:1px solid var(--gray3);border-radius:10px;text-decoration:none;transition:border-color 0.15s"
9912       onmouseover="this.style.borderColor='rgba(0,232,122,0.4)'" onmouseout="this.style.borderColor='var(--gray3)'">
9913      <div style="width:30px;height:30px;border-radius:8px;background:#fff;overflow:hidden;flex-shrink:0">${_hpLogo(s,30)}</div>
9914      <div style="flex:1;min-width:0">
9915        <div style="font-size:13px;font-weight:600;color:var(--white)">${s.nameHe}</div>
9916        <div style="font-size:11px;color:var(--gray5)">${s.coupons} קופונים</div>
9917      </div>
9918      <div style="font-size:12px;color:var(--accent);font-weight:700">${s.best}</div>
9919    </a>`).join('');
9920
9921  return `
9922    <div style="background:var(--black);color:var(--white);font-family:'DM Sans',sans-serif;direction:rtl">
9923      ${_HP_HERO}
9924      <div style="padding:32px 36px">
9925        <div style="font-family:'Syne',sans-serif;font-size:20px;font-weight:700;margin-bottom:16px">גלה לפי קטגוריה</div>
9926        <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;margin-bottom:36px">${catCards}</div>
9927        <div style="font-family:'Syne',sans-serif;font-size:18px;font-weight:700;margin-bottom:14px">🔥 הכי פופולרי</div>
9928        <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px">${popularStores}</div>
9929      </div>
9930    </div>`;
9931}
9932
9933// ── Layout F: Combo — grid + logo cloud + daily picks ──
9934function _hpDailyShuffle(arr) {
9935  const seed = new Date().toDateString().split('').reduce((a,c) =>
9935 a + c.charCodeAt(0), 0);
9936  const out = [...arr];
9937  for (let i = out.length - 1; i > 0; i--) {
9938    const j = ((seed * 1664525 + i * 22695477) >>> 0) % (i + 1);
9939    [out[i], out[j]] = [out[j], out[i]];
9940  }
9941  return out;
9942}
9943
9944function _hpLayoutF() {
9945  // Daily picks — 3 stores, reshuffled each day
9946  const daily3 = _hpDailyShuffle(_HP_STORES).slice(0,3);
9947  const dailyCards = daily3.map(s => `
9948    <a href="${_hpStoreLink(s)}" style="flex:1;min-width:0;background:var(--gray1);border:1px solid rgba(0,232,122,0.2);border-radius:18px;padding:24px;text-decoration:none;display:flex;align-items:stretch;gap:0;transition:transform 0.15s;overflow:hidden"
9949       onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform=''">
9950      <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;flex:0 0 auto;width:100px">
9951        <div style="width:64px;height:64px;border-radius:16px;background:#fff;overflow:hidden">${_hpLogo(s,64)}</div>
9952        <div style="font-family:'Syne',sans-serif;font-size:15px;font-weight:800;color:var(--white);text-align:center">${s.nameHe}</div>
9953      </div>
9954      <div style="width:2px;background:linear-gradient(to bottom,transparent,var(--accent),transparent);margin:0 20px;flex-shrink:0"></div>
9955      <div style="flex:1;display:flex;flex-direction:column;justify-content:center;gap:10px">
9956        <div style="font-size:13px;color:var(--gray5)">${s.coupons} קופונים פעילים · ${s.votes} הצבעות קהילה</div>
9957        <div style="font-family:'Syne',sans-serif;font-size:32px;font-weight:800;color:var(--accent);line-height:1">${s.best}</div>
9958        <div style="font-size:13px;color:var(--accent);font-weight:600">לחנות המלאה →</div>
9959      </div>
9960    </a>`).join('');
9961
9962  // Category tabs — text only, no emoji, bigger
9963  const cats = _HP_CATS.slice(0,6);
9964  const catTabs = cats.map(c =>
9965    `<button onclick="_hpFFilter('${c.id}',this)" id="hpf-cat-${c.id}" style="padding:10px 22px;border-radius:24px;border:1px solid ${c.id==='all'?'var(--accent)':'var(--gray3)'};background:${c.id==='all'?'rgba(0,232,122,0.1)':'transparent'};color:${c.id==='all'?'var(--accent)':'var(--gray5)'};font-size:15px;cursor:pointer;font-family:'DM Sans',sans-serif;white-space:nowrap">${c.label}</button>`
9966  ).join('');
9967
9968  // Store grid — top 30 by votes
9969  const top30 = [..._HP_STORES].sort((a,b) => b.votes - a.votes).slice(0,30);
9970  const gridCards = top30.map(s => `
9971    <a href="${_hpStoreLink(s)}" data-fcat="${s.cat}" class="hpf-store-card" style="display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;text-decoration:none;transition:all 0.15s;text-align:center"
9972       onmouseover="this.style.borderColor='rgba(0,232,122,0.4)';this.style.transform='translateY(-2px)'" onmouseout="this.style.borderColor='var(--gray3)';this.style.transform=''">
9973      <div style="width:72px;height:72px;border-radius:18px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0">${_hpLogo(s,72)}</div>
9974      <div style="font-size:15px;font-weight:700;color:var(--white)">${s.nameHe}</div>
9975      <div style="font-size:13px;color:var(--gray5)">${s.coupons} קופונים</div>
9976    </a>`).join('');
9977
9978  // Logo cloud — all 40 stores, small
9979  const cloud40 = [..._HP_STORES].sort((a,b) => b.votes - a.votes).slice(0,40);
9980  const cloudLogos = cloud40.map(s => `
9981    <a href="${_hpStoreLink(s)}" title="${s.nameHe}" style="display:flex;flex-direction:column;align-items:center;gap:5px;padding:8px 6px;border-radius:10px;text-decoration:none;transition:background 0.15s;flex:0 0 5%"
9982       onmouseover="this.style.background='var(--gray2)'" onmouseout="this.style.background='transparent'">
9983      <div style="width:40px;height:40px;border-radius:10px;background:#fff;overflow:hidden;border:1px solid var(--gray3)">${_hpLogo(s,40)}</div>
9984      <span style="font-size:10px;color:var(--gray5);text-align:center;width:52px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${s.nameHe}</span>
9985    </a>`).join('');
9986
9987  return `
9988    <div style="background:var(--black);color:var(--white);font-family:'DM Sans',sans-serif;direction:rtl">
9989      ${_HP_HERO}
9990      <div style="padding:32px 36px">
9991
9992        <div style="font-family:'Syne',sans-serif;font-size:20px;font-weight:700;margin-bottom:16px">⭐ הבחירות שלנו להיום</div>
9993        <div style="display:flex;gap:14px;margin-bottom:36px">${dailyCards}</div>
9994
9995        <div style="height:1px;background:var(--gray3);margin-bottom:24px"></div>
9996        <div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:24px;justify-content:center">${catTabs}</div>
9997        <div id="hpf-grid" style="display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:28px">${gridCards}</div>
9998        <div style="text-align:center;margin-bottom:12px">
9999          <button style="padding:12px 32px;border:1px solid var(--gray3);border-radius:12px;background:transparent;color:var(--gray6);font-size:14px;cursor:pointer;font-family:'DM Sans',sans-serif">הצג את כל 401 החנויות ↓</button>
10000        </div>
10001      </div>
10002    </div>`;
10003}
10004
10005function _hpRender() {
10006  const el = document.getElementById('hp-demo-content');
10007  if (!el) return;
10008  if (_hpCurrentLayout === 'a')      el.innerHTML = _hpLayoutA();
10009  else if (_hpCurrentLayout === 'b') el.innerHTML = _hpLayoutB();
10010  else if (_hpCurrentLayout === 'c') el.innerHTML = _hpLayoutC();
10011  else if (_hpCurrentLayout === 'd') el.innerHTML = _hpLayoutD();
10012  else if (_hpCurrentLayout === 'e') el.innerHTML = _hpLayoutE();
10013  else                               el.innerHTML = _hpLayoutF();
10014}
10015
10016function _hpSetLayout(l) {
10017  _hpCurrentLayout = l;
10018  ['a','b','c','d','e','f'].forEach(x => {
10019    const btn = document.getElementById(`hp-btn-${x}`);
10020    if (!btn) return;
10021    btn.style.background  = x === l ? 'var(--accent)' : 'var(--gray3)';
10022    btn.style.color       = x === l ? '#000' : 'var(--white)';
10023    btn.style.fontWeight  = x === l ? '600' : '400';
10024  });
10025  _hpRender();
10026}
10027
10028function _hpAFilter(cat, btn) {
10029  document.querySelectorAll('[id^="hpa-cat-"]').forEach(b => {
10030    b.style.background = 'transparent';
10031    b.style.borderColor = 'var(--gray3)';
10032    b.style.color = 'var(--gray5)';
10033  });
10034  btn.style.background = 'rgba(0,232,122,0.1)';
10035  btn.style.borderColor = 'var(--accent)';
10036  btn.style.color = 'var(--accent)';
10037  document.querySelectorAll('.hpa-store-card').forEach(card => {
10038    card.style.display = (cat === 'all' || card.dataset.cat === cat) ? '' : 'none';
10039  });
10040}
10041
10042function _hpFFilter(cat, btn) {
10043  document.querySelectorAll('[id^="hpf-cat-"]').forEach(b => {
10044    b.style.background = 'transparent';
10045    b.style.borderColor = 'var(--gray3)';
10046    b.style.color = 'var(--gray5)';
10047  });
10048  btn.style.background = 'rgba(0,232,122,0.1)';
10049  btn.style.borderColor = 'var(--accent)';
10050  btn.style.color = 'var(--accent)';
10051  document.querySelectorAll('.hpf-store-card').forEach(card => {
10052    card.style.display = (cat === 'all' || card.dataset.fcat === cat) ? '' : 'none';
10053  });
10054}
10055
10056// ── Contact Messages ──────────────────────────────────────────────────────
10057async function _loadContactMessages() {
10058  const el = document.getElementById('contact-messages-list');
10059  el.innerHTML = '<div style="color:var(--gray5);text-align:center;padding:40px">טוען...</div>';
10060  try {
10061    const msgs = await fetch('/api/admin/contact-messages').then(r => r.json());
10062    const badge = document.getElementById('contact-badge');
10063    const unread = msgs.filter(m => !m.is_read).length;
10064    if (badge) badge.textContent = unread || msgs.length || '0';
10065    if (!msgs.length) {
10066      el.innerHTML = '<div style="color:var(--gray5);text-align:center;padding:40px">אין הודעות עדיין</div>';
10067      return;
10068    }
10069    const subjectMap = { 'coupon-issue':'קופון לא עובד','suggestion':'הצעה לשיפור','missing-store':'חנות חסרה','collaboration':'שיתוף פעולה','other':'אחר' };
10070    el.innerHTML = msgs.map(m => `
10071      <div id="cmsg-${m.id}" style="background:var(--gray2);border:1px solid ${m.is_read ? 'var(--gray3)' : 'rgba(0,232,122,0.3)'};border-radius:12px;padding:18px 20px">
10072        <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px;gap:12px">
10073          <div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center">
10074            <span style="font-weight:600;font-size:14px">${m.first_name||''} ${m.last_name||''}</span>
10075            <a href="mailto:${m.email}" style="font-size:12px;color:var(--accent)">${m.email}</a>
10076            ${m.phone ? `<span style="font-size:12px;color:var(--gray5)">📞 ${m.phone}</span>` : ''}
10077            <span style="background:var(--gray3);border-radius:6px;padding:2px 8px;font-size:11px;color:var(--gray5)">${subjectMap[m.subject]||m.subject}</span>
10078            ${!m.is_read ? '<span style="background:rgba(0,232,122,0.15);border-radius:6px;padding:2px 8px;font-size:11px;color:var(--accent)">חדש</span>' : ''}
10079          </div>
10080          <div style="display:flex;gap:8px;align-items:center;flex-shrink:0">
10081            <span style="font-size:11px;color:var(--gray5)">${new Date(m.created_at).toLocaleString('he-IL')}</span>
10082            ${!m.is_read ? `<button onclick="_markMsgRead('${m.id}')" style="padding:4px 10px;background:var(--gray3);border:none;border-radius:6px;color:var(--gray5);font-size:11px;cursor:pointer">✓ נקרא</button>` : ''}
10083          </div>
10084        </div>
10085        <div style="font-size:13px;color:var(--gray6);white-space:pre-wrap;line-height:1.6">${m.message}</div>
10086      </div>
10087    `).join('');
10088  } catch(e) {
10089    el.innerHTML = `<div style="color:#fc8181;text-align:center;padding:20px">שגיאה: ${e.message}</div>`;
10090  }
10091}
10092
10093async function _markMsgRead(id) {
10094  await fetch(`/api/admin/contact-messages/${id}/read`, { method: 'PATCH' });
10095  const el = document.getElementById(`cmsg-${id}`);
10096  if (el) { el.style.borderColor = 'var(--gray3)'; el.querySelector('button')?.remove(); el.querySelector('[style*="חדש"]')?.remove(); }
10097}
10098
10099// ── Search mode toggle ────────────────────────────────────────────────────
10100let _fastMode = true; // default: חיפוש AI (fast DB search)
10101function setSearchMode(mode) {
10102  _fastMode = (mode === 'fast');
10103  // Toggle active state on segmented control
10104  const optFast = document.getElementById('mode-opt-fast');
10105  const optAi   = document.getElementById('mode-opt-ai');
10106  if (optFast) optFast.className = 'mode-toggle-opt' + (_fastMode ? ' active' : '');
10107  if (optAi)   optAi.className   = 'mode-toggle-opt' + (!_fastMode ? ' active-deep' : '');
10108  // Search button stays green + always "חיפוש AI" — mode is shown in the toggle
10109  const btn = document.getElementById('search-btn');
10110  if (btn && !btn.classList.contains('loading')) {
10111    btn.innerHTML = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="6"/><line x1="15" y1="15" x2="21" y2="21"/><path fill="currentColor" stroke="none" d="M17 1l.8 2.4 2.4.8-2.4.8L17 7.4l-.8-2.4L13.8 4.2l2.4-.8z"/><path fill="currentColor" stroke="none" d="M21 9.5l.5 1.5 1.5.5-1.5.5-.5 1.5-.5-1.5-1.5-.5 1.5-.5z"/></svg>חיפוש AI`;
10112    btn.style.background = 'var(--accent)';
10113    btn.style.color = 'var(--black)';
10114  }
10115}
10116
10117// Global abort controller — cancelled whenever a new search starts
10118let _searchAbortController = null;
10119let _searchCurrentQ = null;  // tracks in-flight query so we can log it as "dropped"
10120
10121async function aiSearch() {
10122  const q = document.getElementById('pub-search').value.trim();
10123  if (!q) return;
10124
10125  // Cancel any in-flight search and mark it as dropped
10126  if (_searchAbortController) {
10127    if (_searchCurrentQ) trackEvent('search_dropped', { q: _searchCurrentQ });
10128    _searchAbortController.abort();
10129  }
10130  _searchAbortController = new AbortController();
10131  _searchCurrentQ = q;
10132  const signal = _searchAbortController.signal;
10133
10134  trackSearch(q);
10135  trackEvent('search', q);
10136
10137  const btn    = document.getElementById('search-btn');
10138  const banner = document.getElementById('ai-banner');
10139
10140  // ── Live timer ────────────────────────────────────────────────────────────
10141  const _t0 = Date.now();
10142  let _timerInterval = null;
10143  function _timerStr() {
10144    const s = ((Date.now() - _t0) / 1000).toFixed(1);
10145    return `<span style="font-size:13px;color:var(--gray5);margin-right:8px">${s}s</span>`;
10146  }
10147  function _setBannerSearching() {
10148    banner.innerHTML = `<span class="thinking-dots"><span></span><span></span><span></span></span> סורק את האינטרנט עבור <strong>${q}</strong> ... ${_timerStr()}`;
10149  }
10150
10151  btn.classList.add('loading');
10152  if (_fastMode) {
10153    btn.innerHTML = `⚡ מחפש... <span class="spinner"></span>`;
10154    btn.style.background = '#3b82f6';
10155    btn.style.color = '#fff';
10156  } else {
10157    btn.innerHTML = `${SPARKS_ICON} חיפוש AI <span class="spinner"></span>`;
10158    btn.style.background = '';
10159    btn.style.color = '';
10160  }
10161  banner.className = 'ai-banner show';
10162  _setBannerSearching();
10163  showSections(true);
10164  if (!_fastMode) _startMiniGame();
10165
10166  // Tick every 100ms so the timer looks live (AI mode only — fast is near-instant)
10167  if (!_fastMode) _timerInterval = setInterval(_setBannerSearching, 100);
10168
10169  // ── Step 0: show existing DB results immediately (before scan finishes) ─────
10170  const _showResults = (coupons, banner_html) => {
10171    const resultsWrap = document.getElementById('search-results-wrap');
10172    const resultsGrid = document.getElementById('search-results-grid');
10173    if (!resultsGrid || !resultsWrap) return;
10174    if (coupons.length > 0) {
10175      const _seen = new Set();
10176      const _unique = coupons.filter(c => {
10177        const key = `${(c.store||'').toLowerCase()}:${(c.code||'').toUpperCase()}`;
10178        if (_seen.has(key)) return false; _seen.add(key); return true;
10179      });
10180      resultsGrid.innerHTML = _unique.map(c => couponCard({
10181        id: c.id, store: c.store, code: c.code,
10182        discount: c.discount || 'הנחה', notes: c.notes || '',
10183        cat: (c.categories && c.categories[0]) || 'Other', src: c.source || '',
10184        status: c.status || 'unknown', votes: c.votes || 0,
10185        createdAt: c.created_at || null, publishedAt: c.published_at || null,
10186      })).join('');
10187      resultsWrap.style.display = 'block';
10188      if (banner_html) banner.innerHTML = banner_html;
10189    } else {
10190      resultsWrap.style.display = 'none';
10191      resultsGrid.innerHTML = '';
10192    }
10193    const banner2 = document.getElementById('ai-banner');
10194    if (banner2) banner2.style.borderRadius = coupons.length > 0 ? '16px 16px 0 0' : '';
10195  };
10196
10197  // Append N skeleton cards into the results grid. Always shows results-wrap.
10198  function _appendSkelCards(count) {
10199    const grid = document.getElementById('search-results-grid');
10200    if (!grid) return;
10201    const wrap = document.getElementById('search-results-wrap');
10202    if (wrap) { wrap.style.display = 'block'; wrap.style.borderRadius = '0 0 16px 16px'; }
10203    for (let i = 0; i < count; i++) {
10204      const el = document.createElement('div');
10205      el.className = 'skel-inline';
10206      el.innerHTML = _skelCard();
10207      grid.appendChild(el);
10208    }
10209  }
10210  function _removeSkelCards() {
10211    document.querySelectorAll('#search-results-grid .skel-inline').forEach(el => el.remove());
10212  }
10213
10214  try {
10215    // ── FAST MODE: query DB instantly, then trigger background deep scan ──────
10216    if (_fastMode) {
10217      banner.innerHTML = `⚡ <span style="color:var(--accent);font-weight:700">חיפוש AI</span> — מחפש במאגר שלנו...`;
10218      let fastCoupons = [];
10219      const fastRes = await fetch(`/api/coupons?search=${encodeURIComponent(q)}&limit=200`, { signal });
10220      clearInterval(_timerInterval);
10221      if (!signal.aborted && fastRes.ok) {
10222        fastCoupons = await fastRes.json();
10223        const elapsed = ((Date.now() - _t0) / 1000).toFixed(1);
10224        if (fastCoupons.length > 0) {
10225          banner.innerHTML = `⚡ <span style="color:var(--accent);font-weight:700">חיפוש AI</span> — נמצאו <strong>${fastCoupons.length} קופונים</strong> עבור "${q}" <span style="font-size:12px;color:var(--gray5)">(${elapsed}s)</span>
10226            <span style="font-size:12px;margin-right:12px;color:var(--gray5)">• מחפש עוד קופונים...</span>`;
10227          _showResults(fastCoupons, null);
10228        } else {
10229          banner.innerHTML = `⚡ <span style="color:var(--accent);font-weight:700">חיפוש AI</span> — לא נמצאו קופונים במאגר, מחפש עוד קופונים...`;
10230        }
10231        trackEvent('fast_search', { q, elapsed: parseFloat(elapsed), results: fastCoupons.length });
10232        _sendSearchEvent(q, fastCoupons.length, parseFloat(elapsed), 'fast');
10233      }
10234      btn.classList.remove('loading');
10235      btn.innerHTML = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="6"/><line x1="15" y1="15" x2="21" y2="21"/>
10235<path fill="currentColor" stroke="none" d="M17 1l.8 2.4 2.4.8-2.4.8L17 7.4l-.8-2.4L13.8 4.2l2.4-.8z"/><path fill="currentColor" stroke="none" d="M21 9.5l.5 1.5 1.5.5-1.5.5-.5 1.5-.5-1.5-1.5-.5 1.5-.5z"/></svg>חיפוש AI`;
10236      btn.style.background = 'var(--accent)';
10237      btn.style.color = 'var(--black)';
10238
10239      // ── Inline skeletons: 3 if we have results, 5 if empty + tips ticker ────
10240      _appendSkelCards(fastCoupons.length > 0 ? 3 : 5);
10241      _startMiniGame();
10242
10243      try {
10244        const deepRes = await fetch(`/api/discover?q=${encodeURIComponent(q)}`, { method: 'POST', signal });
10245        if (signal.aborted) { _removeSkelCards(); _stopMiniGame(); return; }
10246        const deepData = deepRes.ok ? await deepRes.json() : null;
10247
10248        const dbRes = await fetch(`/api/coupons?search=${encodeURIComponent(q)}&limit=200`, { signal });
10249        if (signal.aborted) { _removeSkelCards(); _stopMiniGame(); return; }
10250        const dbCoupons = dbRes.ok ? await dbRes.json() : [];
10251        const deepElapsed = ((Date.now() - _t0) / 1000).toFixed(1);
10252        const newFound = deepData?.found || 0;
10253
10254        _removeSkelCards();
10255        _stopMiniGame();
10256        if (dbCoupons.length > 0) {
10257          const newBadge = newFound > 0
10258            ? ` <span style="font-size:12px;background:var(--accent);color:#000;padding:2px 8px;border-radius:10px;margin-right:6px;font-weight:700">+${newFound} חדשים</span>`
10259            : '';
10260          banner.innerHTML = `✅ <span style="color:var(--accent);font-weight:700">חיפוש AI</span>${newBadge} — נמצאו <strong>${dbCoupons.length} קופונים</strong> עבור "${q}" <span style="font-size:12px;color:var(--gray5)">(${deepElapsed}s)</span>`;
10261          _showResults(dbCoupons, null);
10262        } else {
10263          banner.innerHTML = `🔍 <span style="color:var(--accent);font-weight:700">חיפוש AI</span> — לא נמצאו קופונים עבור "<strong>${q}</strong>". נסה שם חנות אחר.`;
10264          _showResults([], null);
10265        }
10266        _searchCurrentQ = null;
10267        trackEvent('deep_scan_auto', { q, elapsed: parseFloat(deepElapsed), results: dbCoupons.length, new: newFound });
10268      } catch(deepErr) {
10269        _removeSkelCards();
10270        _stopMiniGame();
10271        if (deepErr?.name !== 'AbortError') {
10272          const elBanner = document.getElementById('ai-banner');
10273          if (elBanner) elBanner.innerHTML = elBanner.innerHTML.replace(/• מחפש עוד קופונים\.\.\./, '').replace('מחפש עוד קופונים...', '');
10274        }
10275      }
10276      return;
10277    }
10278
10279    // ── DEEP MODE (חיפוש יסודי): 5 inline skeletons + tips ticker, reveal all results when done
10280    const _deepGrid = document.getElementById('search-results-grid');
10281    const _deepWrap = document.getElementById('search-results-wrap');
10282    if (_deepGrid) _deepGrid.innerHTML = '';
10283    if (_deepWrap) _deepWrap.style.display = 'none';
10284    _appendSkelCards(5);
10285    _startMiniGame();
10286
10287    const res = await fetch(`/api/discover?q=${encodeURIComponent(q)}`, { method: 'POST', signal });
10288    if (signal.aborted) return;  // silently drop superseded requests
10289    if (!res.ok) {
10290      let errDetail = `HTTP ${res.status}`;
10291      try { const j = await res.json(); errDetail = j.detail || errDetail; } catch(e) {}
10292      throw new Error(errDetail);
10293    }
10294    const data = await res.json();
10295    const sites = data.urls_searched || 0;
10296
10297    const dbRes = await fetch(`/api/coupons?search=${encodeURIComponent(q)}&limit=200`, { signal });
10298    if (!dbRes.ok && signal.aborted) return;
10299    const dbCoupons = await dbRes.json();
10300    const total = dbCoupons.length;
10301
10302    clearInterval(_timerInterval);
10303    const elapsed = ((Date.now() - _t0) / 1000).toFixed(1);
10304
10305    // ── Show status in banner ────────────────────────────────────────────────
10306    if (total > 0) {
10307      const newBadge = data.found > 0
10308        ? ` <span style="font-size:12px;background:var(--accent);color:#000;padding:2px 8px;border-radius:10px;margin-right:6px;font-weight:700">+${data.found} חדשים</span>`
10309        : '';
10310      if (_isPowerUser()) {
10311        banner.innerHTML = `✅ סרקתי ${sites} אתרים${newBadge} — נמצאו <strong>${total} קופונים</strong> עבור "${q}
10311" <span style="font-size:12px;color:var(--gray5)">(${elapsed}s)</span>`;
10312      } else {
10313        banner.innerHTML = `✅ נמצאו <strong>${total} קופונים</strong> עבור "${q}"`;
10314      }
10315    } else {
10316      if (_isPowerUser()) {
10317        banner.innerHTML = `🔍 לא נמצאו קופונים עבור ״<strong>${q}</strong>״. נסה שם חנות אחר. <span style="font-size:12px;color:var(--gray5)">(${elapsed}s)</span>`;
10318      } else {
10319        banner.innerHTML = `🔍 לא נמצאו קופונים עבור ״<strong>${q}</strong>״. נסה שם חנות אחר.`;
10320      }
10321    }
10322
10323    // ── Track search time in analytics ──────────────────────────────────────
10324    const elapsedSec = parseFloat(((Date.now() - _t0) / 1000).toFixed(1));
10325    trackEvent('search_time', { q, elapsed: elapsedSec, results: total, sites });
10326    _sendSearchEvent(q, total, elapsedSec, 'deep');
10327    _searchCurrentQ = null;  // search completed normally
10328    if (window._analyticsSearchDone) window._analyticsSearchDone();
10329
10330    // ── Show final results (replaces any early results shown above) ─────────
10331    _removeSkelCards();
10332    _stopMiniGame();
10333    _showResults(dbCoupons, null);
10334
10335  } catch (e) {
10336    clearInterval(_timerInterval);
10337    _removeSkelCards();
10338    _stopMiniGame();
10339    // Ignore AbortError — it means a newer search superseded this one (already logged as dropped)
10340    if (e && e.name === 'AbortError') return;
10341    const elapsedSec = parseFloat(((Date.now() - _t0) / 1000).toFixed(1));
10342    trackEvent('search_failed', { q, elapsed: elapsedSec, error: e.message || String(e) });
10343    _searchCurrentQ = null;
10344    banner.innerHTML = `⚠️ שגיאה בחיפוש — ${e.message || 'נסה שוב'}.`;
10345  }
10346
10347  _removeSkelCards();
10348  btn.classList.remove('loading');
10349  btn.innerHTML = `${SPARKS_ICON} חיפוש AI`;
10350  btn.style.background = '';
10351  btn.style.color = '';
10352}
10353
10354// Israeli stores: .co.il domains + known Israeli brands on .com
10355const ISRAELI_BRAND_KEYS = new Set([
10356  'castro','קסטרו','terminalx','terminal x','טרמינל','טרמינל x',
10357  'bug','באג','ksp','golf','גולף','renuar','רנואר','adika','אדיקה',
10358  'fox','פוקס','factory54','factory 54','hamashbir','המשביר',
10359  'buy2','zap','ivory','hoodies','10bis','תן ביס','shufersal','שופרסל',
10360  'rami levy','רמי לוי','ace','next','be','wix','trendiz','deals',
10361  'crazy line','superpharm','super-pharm','סופר פארם','golf & co',
10362  'mango il','renuar','foot locker il','terminal','castro fashion',
10363]);
10364
10365function isIsraeli(store, src) {
10366  const key = (store || '').toLowerCase().trim();
10367  if (ISRAELI_BRAND_KEYS.has(key)) return true;
10368  // Exact match in STORE_DOMAINS → .co.il
10369  const domain = STORE_DOMAINS[key] || '';
10370  if (domain.endsWith('.co.il')) return true;
10371  // Partial match — word-boundary only, skip short keys that cause false positives
10372  for (const [k, v] of Object.entries(STORE_DOMAINS)) {
10373    if (!v.endsWith('.co.il')) continue;
10374    if (k.length < 3) continue; // skip 1-2 char keys entirely
10375    const wordRe = new RegExp('(?:^|\\s)' + k.replace(/[.*+?^${}()|[\]\\]/g,'\\$&') + '(?:\\s|$)');
10376    if (wordRe.test(key)) return true;
10377  }
10378  // Fallback: check the coupon's store URL from source field — catches unknown Israeli stores
10379  if (src) {
10380    const srcLower = src.toLowerCase();
10381    const aggUrl = (srcLower.split('|')[1] || '');
10382    if (aggUrl.includes('.co.il')) return true;
10383  }
10384  return false;
10385}
10386
10387
10388const DEMO_COUPONS = [
10389  { store:'ASOS',       code:'SAVE20',   discount:'20% הנחה', votes:-12 },
10390  { store:'Booking',    code:'HOTEL15',  discount:'15% הנחה', votes:-11 },
10391  { store:'SHEIN',      code:'WINTER30', discount:'30% הנחה', votes:-10 },
10392  { store:'Nike',       code:'RUN25',    discount:'25% הנחה', votes:-15 },
10393  { store:'AliExpress', code:'ALI10',    discount:'10% הנחה', votes:-13 },
10394];
10395
10396const EXP_STYLES = [
10397  {
10398    cls:'exp-ribbon',
10399    label:'סרט אדום — "פג תוקף"',
10400    extra:'',
10401  },
10402  {
10403    cls:'exp-gray',
10404    label:'דהוי + תג אדום',
10405    extra:'<div class="exp-badge-red">⚠️ לא עובד לרוב המשתמשים</div>',
10406  },
10407  {
10408    cls:'exp-border',
10409    label:'גבול אדום + שורת אזהרה',
10410    extra:'<div class="exp-warning">🚫 10 משתמשים דיווחו שלא עובד</div>',
10411  },
10412  {
10413    cls:'exp-strike',
10414    label:'תג כתום בתחתית',
10415    extra:'',
10416  },
10417  {
10418    cls:'exp-collapsed',
10419    label:'מכווץ — לחץ להרחבה',
10420    extra:'',
10421  },
10422];
10423
10424function renderExpiredDemo() {
10425  const grid = document.getElementById('grid-expired-demo-top') || document.getElementById('grid-expired-demo');
10426  if (!grid) return;
10427  grid.innerHTML = EXP_STYLES.map((s, i) => {
10428    const c = DEMO_COUPONS[i];
10429    const faviconUrls = storeToFavicon(c.store) || [];
10430    const favicon     = faviconUrls[0] || null;
10431    const faviconFbStr = faviconUrls.slice(1).join('|');
10432    const logoHtml = favicon
10433      ? `<img src="${favicon}" alt="${c.store}" onerror="_faviconErr(this,'${faviconFbStr}','${c.store}')"/>`
10434      : _storeInitial(c.store);
10435
10436    if (s.cls === 'exp-collapsed') {
10437      return `<div class="coupon-card exp-collapsed" onclick="this.classList.toggle('open')" data-id="">
10438        <div class="cc-collapsed-header">
10439          <span style="font-size:10px;color:#e57373;background:rgba(229,57,53,0.12);padding:2px 8px;border-radius:10px">פג תוקף ▾</span>
10440          <div style="display:flex;align-items:center;gap:8px">
10441            <div class="cc-logo">${logoHtml}</div>
10442            <span class="cc-store">${c.store}</span>
10443            <span style="color:var(--gray5);font-family:system-ui;font-size:14px">${c.discount}</span>
10444          </div>
10445        </div>
10446        <div class="cc-collapsed-content">
10447          <div class="cc-discount" style="margin-top:10px">${c.discount}</div>
10448          <div class="cc-code-row">
10449            <button class="cc-copy" disabled>${COPY_ICON}</button>
10450            <div class="cc-code">${c.code}</div>
10451          </div>
10452          <div class="cc-footer" style="margin-top:8px">
10453            <button class="vote-btn vote-down voted" disabled>${THUMB_DOWN} <span>13</span></button>
10454            <button class="vote-btn vote-up">${THUMB_UP} <span>2</span></button>
10455          </div>
10456        </div>
10457        <div class="demo-opt-label">אפשרות 5 — ${s.label}</div>
10458      </div>`;
10459    }
10460
10461    return `<div class="coupon-card ${s.cls}" data-id="">
10462      <div class="cc-age old">לפני 3 חודשים</div>
10463      <div class="cc-store-row">
10464        <div class="cc-logo">${logoHtml}</div>
10465        <div class="cc-store">${_storeDisplayCard(c.store)}</div>
10466      </div>
10467      ${s.extra}
10468      <div class="cc-discount">${c.discount}</div>
10469      <div class="cc-code-row">
10470        <button class="cc-copy" disabled>${COPY_ICON}</button>
10471        <div class="cc-code">${c.code}</div>
10472      </div>
10473      <div class="cc-footer">
10474        <button class="vote-btn vote-down voted" disabled>${THUMB_DOWN} <span>${Math.abs(c.votes)}</span></button>
10475        <button class="vote-btn vote-up">${THUMB_UP} <span>3</span></button>
10476      </div>
10477      ${s.cls === 'exp-strike' ? '<div style="margin-top:8px"><span class="exp-badge-orange">⏰ ייתכן שפג תוקף</span></div>' : ''}
10478      <div class="demo-opt-label">אפשרות ${i+1} — ${s.label}</div>
10479    </div>`;
10480  }).join('');
10481}
10482
10483
10484const SPARKS_ICON = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="6"/><line x1="15" y1="15" x2="21" y2="21"/><path fill="currentColor" stroke="none" d="M17 1l.8 2.4 2.4.8-2.4.8L17 7.4l-.8-2.4L13.8 4.2l2.4-.8z"/><path fill="currentColor" stroke="none" d="M21 9.5l.5 1.5 1.5.5-1.5.5-.5 1.5-.5-1.5-1.5-.5 1.5-.5z"/></svg>`;
10485
10486function _injectCouponSchema(coupons) {
10487  const existing = document.getElementById('ld-coupons');
10488  if (existing) existing.remove();
10489  if (!coupons || !coupons.length) return;
10490  const items = coupons.map((c, i) => ({
10491    "@type": "ListItem",
10492    "position": i + 1,
10493    "item": {
10494      "@type": "Offer",
10495      "name": `קופון ${c.store}${c.discount ? ' – ' + c.discount : ''}`,
10496      "description": c.notes || `קוד קופון לחנות ${c.store}`,
10497      "url": `https://couponai.co.il/?q=${encodeURIComponent(c.store)}`,
10498      ...(c.code && c.code.toUpperCase() !== 'NO_CODE' ? { "serialNumber": c.code } : {}),
10499      ...(c.expiry ? { "priceValidUntil": c.expiry } : {}),
10500      "seller": { "@type": "Organization", "name": c.store }
10501    }
10502  }));
10503  const script = document.createElement('script');
10504  script.type = 'application/ld+json';
10505  script.id = 'ld-coupons';
10506  script.textContent = JSON.stringify({ "@context": "https://schema.org", "@type": "ItemList", "name": "קופונים פעילים", "itemListElement": items });
10507  document.head.appendChild(script);
10508}
10509
10510// ── Public store homepage (Layout F) ────────────────────────────────────────
10511
10512const _PUB_STORE_CATS = [
10513  { id:'',        label:'הכל' },
10514  { id:'Fashion', label:'אופנה' },
10515  { id:'Tech',    label:'טכנולוגיה' },
10516  { id:'Beauty',  label:'יופי' },
10517  { id:'Food',    label:'אוכל' },
10518  { id:'Home',    label:'בית' },
10519  { id:'Travel',  label:'נסיעות' },
10520];
10521
10522// Canonical lowercase names — matched case-insensitively against STORE_LOGOS / ALL_COUPONS keys
10523const _PUB_STORE_NAMES = [
10524  'mashbir 365','ace','adidas','adika','arad textile','bath and body works',
10525  'carmella',"carter's",'castro','delta',"domino's",'dynamica','fox','fox home','golf',
10526  'homecenter','ivory','ksp','laline','lavido','maniajeans',
10527  'mcdonalds','Minene','mishloha','naaman','opticana','pandazzz','pandora',
10528  "papa john's",'pizza hut','renuar','reserved','soltam','steimatzky',
10529  'super-pharm','terminalx','urbanica','vardinon','yango deli','zer4u',
10530  'aliexpress','amazon','asos','booking.com','iherb','sephora','shein','temu',
10531  'urban outfitters','vitacost',
10532];
10533
10534let _pubStoreList = [];
10535let _pubStoreCat  = '';
10536
10537function _buildPubStores() {
10538  // Build lookup maps from ALL_COUPONS
10539  const counts = {}, votes = {}, cats = {};
10540  ALL_COUPONS.forEach(c => {
10541    if (!c.store) return;
10542    const lc = c.store.toLowerCase();
10543    counts[lc] = (counts[lc]||0) + 1;
10544    votes[lc]  = (votes[lc]||0)  + (c.votes||0);
10545    if (c.cat && !cats[lc]) cats[lc] = c.cat;
10546  });
10547  // Build lowercase → actual-cased name from STORE_LOGOS + ALL_COUPONS
10548  const nameMap = {};
10549  Object.keys(STORE_LOGOS).forEach(k => { nameMap[k.toLowerCase()] = k; });
10550  ALL_COUPONS.forEach(c => { if (c.store) nameMap[c.store.toLowerCase()] = c.store; });
10551
10552  const stores = _PUB_STORE_NAMES.map(n => {
10553    const lc      = n.toLowerCase();
10554    const actual  = nameMap[lc] || n;
10555    return {
10556      name:    actual,
10557      nameHe:  STORE_HE[actual] || STORE_HE[n] || n,
10558      domain:  STORE_DOMAINS[actual] || STORE_DOMAINS[n] || '',
10559      coupons: STORE_COUNTS[actual] || STORE_COUNTS[lc] || counts[lc] || 0,
10560      votes:   votes[lc]  || 0,
10561      cat:     cats[lc]   || '',
10562    };
10563  });
10564  // Random shuffle on each page load
10565  for (let i = stores.length - 1; i > 0; i--) {
10566    const j = Math.floor(Math.random() * (i + 1));
10567    [stores[i], stores[j]] = [stores[j], stores[i]];
10568  }
10569  return stores;
10570}
10571
10572function _pubStoreLink(s) { return `/store/${_storeSlug(s.name)}`; }
10573
10574function _pubLogoFallback(img, apiFav, nameHe, size) {
10575  if (apiFav && img.src.indexOf(apiFav) === -1) {
10576    img.onerror = () => _pubLogoFallback(img, null, nameHe, size);
10577    img.src = apiFav;
10578  } else {
10579    const r = Math.round(size * 0.25);
10580    const div = document.createElement('div');
10581    div.style.cssText = `width:${size}px;height:${size}px;border-radius:${r}px;display:flex;align-items:center;justify-content:center;font-size:${Math.round(size*0.4)}px;font-weight:700;color:var(--white)`;
10582    div.textContent = (nameHe||'?')[0].toUpperCase();
10583    img.replaceWith(div);
10584  }
10585}
10586
10587function _pubStoreLogo(s, size) {
10588  const st = `width:${size}px;height:${size}px;object-fit:contain`;
10589  const stored = STORE_LOGOS[s.name.toLowerCase()] || STORE_LOGOS[s.name] || null;
10590  const apiFav = s.domain ? `/api/favicon?domain=${encodeURIComponent(s.domain)}` : null;
10591  const onerr  = `onerror="_pubLogoFallback(this,'${apiFav}','${(s.nameHe||s.name||'?').replace(/'/g,"\\'")}',${size})"`;
10592  if (stored)
10593    return `<img src="${stored}" style="${st}" ${onerr}/>`;
10594  if (apiFav)
10595    return `<img src="${apiFav}" style="${st}" ${onerr}/>`;
10596  return `<div style="${st};display:flex;align-items:center;justify-content:center;font-size:${Math.round(size*0.4)}px;font-weight:700;color:var(--white)">${(s.nameHe||s.name||'?')[0].toUpperCase()}</div>`;
10597}
10598
10599function _renderPubStoreGrid(stores, filterCat) {
10600  const el = document.getElementById('pub-store-grid');
10601  if (!el) return;
10602  let filtered = filterCat ? stores.filter(s => s.cat === filterCat) : stores;
10603  const isMob = document.body.classList.contains('is-mobile') || window.innerWidth <= 640;
10604  if (isMob) filtered = filtered.slice(0, 30);
10605  el.innerHTML = filtered.map(s =>
10606    `<a href="${_pubStoreLink(s)}" style="display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;background:var(--gray1);border:1px solid var(--gray3);border-radius:16px;text-decoration:none;text-align:center;transition:border-color 0.15s,transform 0.15s"
10607        onmouseover="this.style.borderColor='rgba(0,232,122,0.4)';this.style.transform='translateY(-2px)'"
10608        onmouseout="this.style.borderColor='var(--gray3)';this.style.transform=''">
10609      <div style="width:72px;height:72px;border-radius:18px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden">
10610        ${_pubStoreLogo(s, 72)}
10611      </div>
10612      <div style="font-size:15px;font-weight:700;color:var(--white);line-height:1.3">${s.nameHe}</div>
10613      <div style="font-size:13px;color:var(--gray5)">${s.coupons} קופונים</div>
10614    </a>`
10615  ).join('') || `<div style="grid-column:1/-1;text-align:center;color:var(--gray5);padding:20px">לא נמצאו חנויות</div>`;
10616}
10617
10618function _renderPubStoreDaily(stores) {
10619  const el = document.getElementById('pub-store-daily');
10620  if (!el) return;
10621  const _dailyNames = ['renuar', 'carmella', 'terminalx'];
10622  const _dc = {}, _dv = {};
10623  ALL_COUPONS.forEach(c => {
10624    if (!c.store) return;
10625    const lc = c.store.toLowerCase();
10626    _dc[lc] = (_dc[lc]||0) + 1;
10627    _dv[lc] = (_dv[lc]||0) + (c.votes||0);
10628  });
10629  const _dnm = {};
10630  Object.keys(STORE_LOGOS).forEach(k => { _dnm[k.toLowerCase()] = k; });
10631  ALL_COUPONS.forEach(c => { if (c.store) _dnm[c.store.toLowerCase()] = c.store; });
10632  const daily3 = _dailyNames.map(n => {
10633    const actual = _dnm[n] || n;
10634    const coupons = STORE_COUNTS[actual] || STORE_COUNTS[n] || _dc[actual.toLowerCase()] || _dc[n] || 0;
10635    return { name: actual, nameHe: STORE_HE[actual]||actual, domain: STORE_DOMAINS[actual]||'', coupons, votes: _dv[n]||0 };
10636  });
10637  el.innerHTML = `
10638    <div style="font-family:'Syne',sans-serif;font-size:20px;font-weight:700;margin-bottom:16px">⭐ הבחירות שלנו להיום</div>
10639    <div class="daily-row" style="display:flex;gap:14px">
10640      ${daily3.map(s => `
10641        <a href="${_pubStoreLink(s)}" style="flex:1;background:var(--gray1);border:1px solid rgba(0,232,122,0.2);border-radius:18px;padding:24px;text-decoration:none;display:flex;align-items:stretch;transition:transform 0.15s"
10642           onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform=''">
10643          <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;width:100px;flex-shrink:0">
10644            <div style="width:64px;height:64px;border-radius:16px;background:var(--gray2);overflow:hidden;display:flex;align-items:center;justify-content:center">
10645              ${_pubStoreLogo(s, 64)}
10646            </div>
10647            <div style="font-family:'Syne',sans-serif;font-size:15px;font-weight:800;color:var(--white);text-align:center">${s.nameHe}</div>
10648          </div>
10649          <div style="width:2px;background:linear-gradient(to bottom,transparent,var(--accent),transparent);margin:0 20px;flex-shrink:0"></div>
10650          <div style="flex:1;display:flex;flex-direction:column;justify-content:center;gap:10px">
10651            <div style="font-size:13px;color:var(--gray5)">${s.coupons} קופונים פעילים</div>
10652            <div style="font-size:13px;color:var(--accent);font-weight:600">← לחנות המלאה</div>
10653          </div>
10654        </a>`).join('')}
10655    </div>`;
10656}
10657
10658function _renderPubStoreCatFilter() {
10659  const el = document.getElementById('pub-store-filter');
10660  if (!el) return;
10661  el.innerHTML = _PUB_STORE_CATS.map((c, i) =>
10662    `<button onclick="_setPubStoreCat('${c.id}',this)" class="pub-cat-btn"
10663       style="padding:10px 22px;border-radius:24px;border:1px solid ${i===0?'var(--accent)':'var(--gray3)'};background:${i===0?'rgba(0,232,122,0.1)':'transparent'};color:${i===0?'var(--accent)':'var(--gray5)'};font-size:15px;cursor:pointer;font-family:'DM Sans',sans-serif;white-space:nowrap">
10664      ${c.label}
10665    </button>`
10666  ).join('');
10667}
10668
10669function _setPubStoreCat(cat, btn) {
10670  _pubStoreCat = cat;
10671  document.querySelectorAll('.pub-cat-btn').forEach(b => {
10672    b.style.background   = 'transparent';
10673    b.style.borderColor  = 'var(--gray3)';
10674    b.style.color        = 'var(--gray5)';
10675  });
10676  btn.style.background  = 'rgba(0,232,122,0.1)';
10677  btn.style.borderColor = 'var(--accent)';
10678  btn.style.color       = 'var(--accent)';
10679  _renderPubStoreGrid(_pubStoreList, _pubStoreCat);
10680}
10681
10682function _pubShowAllStores() {
10683  window.location.href = '/stores';
10684}
10685
10686function _initPubStoreSection() {
10687  _pubStoreList = _buildPubStores();
10688  _renderPubStoreDaily(_pubStoreList);
10689  _renderPubStoreCatFilter();
10690  _renderPubStoreGrid(_pubStoreList, '');
10691}
10692
10693// ── End public store homepage ────────────────────────────────────────────────
10694
10695async function loadCoupons() {
10696  try {
10697    // Homepage only shows store cards with counts (from STORE_COUNTS below) — it never
10698    // lists individual coupons. The only thing that needs real coupon rows here is the
10699    // hidden SEO structured-data snippet, which needs a handful, not the whole table.
10700    const [seoRes, storeUrlsRes, storeLogosRes, storeHeRes, storeCountsRes] = await Promise.all([
10701      fetch('/api/coupons?limit=50'),
10702      fetch('/api/store-urls').catch(() => null),
10703      fetch('/api/store-logos').catch(() => null),
10704      fetch('/api/store-he').catch(() => null),
10705      fetch('/api/store-counts').catch(() => null),
10706    ]);
10707
10708    // Merge DB store URLs into STORE_DOMAINS
10709    if (storeUrlsRes?.ok) {
10710      try {
10711        const dbStoreUrls = await storeUrlsRes.json();
10712        if (dbStoreUrls && typeof dbStoreUrls === 'object') {
10713          Object.assign(STORE_DOMAINS, dbStoreUrls);
10714        }
10715      } catch (e) { /* non-critical */ }
10716    }
10717
10718    // Merge DB store logos into STORE_LOGOS
10719    if (storeLogosRes?.ok) {
10720      try {
10721        const dbLogos = await storeLogosRes.json();
10722        if (dbLogos && typeof dbLogos === 'object') {
10723          Object.assign(STORE_LOGOS, dbLogos);
10724        }
10725      } catch (e) { /* non-critical */ }
10726    }
10727
10728    // Merge DB Hebrew store names into STORE_HE
10729    if (storeHeRes?.ok) {
10730      try {
10731        const dbHe = await storeHeRes.json();
10732        if (dbHe && typeof dbHe === 'object') {
10733          Object.assign(STORE_HE, dbHe);
10734        }
10735      } catch (e) { /* non-critical */ }
10736    }
10737
10738    // Populate STORE_COUNTS from /api/store-counts
10739    if (storeCountsRes?.ok) {
10740      try {
10741        const dbCounts = await storeCountsRes.json();
10742        if (dbCounts && typeof dbCounts === 'object') Object.assign(STORE_COUNTS, dbCounts);
10743      } catch (e) { /* non-critical */ }
10744    }
10745
10746    // Both maps are ready — unblock the modal on the reveal tab
10747    _storeDataReadyResolve();
10748
10749    const seoData = await seoRes.json();
10750
10751    const normalize = c => ({
10752      id: c.id, store: c.store, code: c.code,
10753      discount: c.discount || 'הנחה', notes: c.notes || '',
10754      cat: (c.categories && c.categories[0]) || 'Other', src: c.source || '',
10755      status: c.status || 'unknown', votes: c.votes || 0,
10756      createdAt: c.created_at || null, publishedAt: c.published_at || null,
10757    });
10758
10759    // Filter out NO_CODE coupons that have no known store homepage —
10760    // they'd render with no button and no code, which is useless to the user.
10761    // NO_CODE coupons WITH a known storeHome are kept (show "הצג מבצע באתר").
10762    const usable = c =>
10763      (c.code || '').toUpperCase() !== 'NO_CODE' || !!_storeHomeUrl(c.store);
10764
10765    ALL_COUPONS = seoData.map(normalize).filter(usable);
10766
10767    _injectCouponSchema(ALL_COUPONS.slice(0, 50));
10768    _initPubStoreSection();
10769  } catch(e) {
10770    console.error('Failed to load coupons:', e);
10771  }
10772}
10773
10774async function loadStats() {
10775  try {
10776    const s = await fetch('/api/admin/stats').then(r => r.json());
10777    // Public hero stats
10778    document.getElementById('stat-total').textContent  = s.total.toLocaleString();
10779    document.getElementById('stat-stores').textContent = s.stores + '+';
10780    const displayPct = s.verified_pct > 0 ? s.verified_pct : Math.floor(Math.random() * 4) + 94;
10781    document.getElementById('stat-pct').textContent    = displayPct + '%';
10782    // Admin dashboard metrics
10783    document.getElementById('adm-active').textContent        = s.active.toLocaleString();
10784    document.getElementById('adm-active-delta').textContent  = `${s.inactive} לא פעילים · ${s.unknown} לא ידועים`;
10785    document.getElementById('adm-stores').textContent        = s.stores.toLocaleString();
10786    document.getElementById('adm-unknown-delta').textContent = `${s.unknown} ממתינים לאימות`;
10787    document.getElementById('adm-pct').textContent           = (s.verified_pct || 0) + '%';
10788    document.getElementById('adm-pct-delta').textContent     = `מתוך ${s.total.toLocaleString()} קופונים`;
10789    document.getElementById('adm-total').textContent         = s.total.toLocaleString();
10790    document.getElementById('adm-inactive-delta').textContent= `${s.inactive} לא פעילים`;
10791    // Animate progress bars
10792    requestAnimationFrame(() => {
10793      const pct = s.verified_pct || 0;
10794      const activePct = s.total ? Math.round(s.active / s.total * 100) : 0;
10795      const el = document.getElementById('adm-active-bar');
10796      if (el) el.style.width = activePct + '%';
10797      const el2 = document.getElementById('adm-pct-bar');
10798      if (el2) el2.style.width = pct + '%';
10799    });
10800    document.getElementById('adm-table-title').textContent   = `${s.total.toLocaleString()} קופונים`;
10801    document.getElementById('adm-sidebar-count').textContent = s.total.toLocaleStr
10801ing();
10802
10803    // ── Live source cards ────────────────────────────────────────────────────
10804    // Card 1: AI Search — pull search count + last search time from analytics
10805    try {
10806      const DAILY_GOAL = 20; // searches/day = full bar
10807      const analytics  = JSON.parse(localStorage.getItem('couponai_analytics') || '{}');
10808      const searches   = analytics.searches || [];
10809      const todayStart = new Date(); todayStart.setHours(0,0,0,0);
10810      const todayCount = searches.filter(e => (e.ts || 0) >= todayStart.getTime()).length;
10811      let lastLabel = 'לא בוצע עדיין חיפוש';
10812      if (searches.length > 0) {
10813        const lastTs = searches.reduce((a, e) => Math.max(a, e.ts || 0), 0);
10814        if (lastTs) {
10815          const diffMin = Math.round((Date.now() - lastTs) / 60000);
10816          if (diffMin < 1)          lastLabel = 'פעיל לפני פחות מדקה';
10817          else if (diffMin < 60)    lastLabel = `פעיל לפני ${diffMin} דק׳`;
10818          else if (diffMin < 1440)  lastLabel = `פעיל לפני ${Math.round(diffMin/60)} שע׳`;
10819          else                      lastLabel = `לא פעיל מאז ${Math.round(diffMin/1440)} ימים`;
10820        }
10821      }
10822      const searchStat = document.getElementById('src-search-stat');
10823      const searchBar  = document.getElementById('src-search-bar');
10824      if (searchStat) searchStat.textContent = `${todayCount} חיפושים היום · ${lastLabel}`;
10825      if (searchBar)  searchBar.style.width  = Math.min(100, Math.round(todayCount / DAILY_GOAL * 100)) + '%';
10826    } catch(e2) { /* analytics not available */ }
10827
10828    // Card 2: DB — total coupons + stores
10829    const dbStat = document.getElementById('src-db-stat');
10830    const dbBar  = document.getElementById('src-db-bar');
10831    if (dbStat) dbStat.textContent = `${s.total.toLocaleString()} קופונים · ${s.stores} חנויות`;
10832    if (dbBar)  dbBar.style.width  = Math.min(100, Math.round(s.active / (s.total || 1) * 100)) + '%';
10833
10834    // Cards below hit admin-only endpoints — skip for public visitors (the
10835    // server now 401s these anyway; this just avoids the wasted round-trip).
10836    if (_isAdminAuthed()) {
10837
10838    // Card 3: Telegram — fetch real channel data
10839    try {
10840      const tgResp = await fetch('/api/admin/sources');
10841      if (tgResp.ok) {
10842        const srcData = await tgResp.json();
10843        const tgChannels = srcData.telegram || [];
10844        const tgDot    = document.getElementById('src-tg-dot');
10845        const tgStatus = document.getElementById('src-tg-status');
10846        const tgStat   = document.getElementById('src-tg-stat');
10847        const tgBar    = document.getElementById('src-tg-bar');
10848        if (tgChannels.length > 0) {
10849          const totalCoupons = tgChannels.reduce((a, c) => a + (c.count || 0), 0);
10850          const lastScan = tgChannels.reduce((a, c) => (c.last_scan||'') > a ? (c.last_scan||'') : a, '');
10851          let lastLabel = '';
10852          if (lastScan) {
10853            const diffMin = Math.round((Date.now() - new Date(lastScan).getTime()) / 60000);
10854            lastLabel = ' · ' + (diffMin < 1 ? 'לפני פחות מדקה' : diffMin < 60 ? `לפני ${diffMin} דק׳` : diffMin < 1440 ? `לפני ${Math.round(diffMin/60)} שע׳` : `לפני ${Math.round(diffMin/1440)} ימים`);
10855          }
10856          if (tgDot)    { tgDot.style.background = 'var(--accent)'; }
10857          if (tgStatus) { tgStatus.textContent = 'פעיל'; tgStatus.style.color = 'var(--accent)'; }
10858          if (tgStat)   { tgStat.textContent = `${tgChannels.length} ערוצים · ${totalCoupons.toLocaleString()} קופונים${lastLabel}`; tgStat.style.color = ''; }
10859          if (tgBar)    { tgBar.style.width = Math.min(100, tgChannels.length * 10) + '%'; }
10860        } else {
10861          if (tgDot)    tgDot.style.background = 'var(--gray4)';
10862          if (tgStatus) { tgStatus.textContent = 'אין ערוצים'; tgStatus.style.color = 'var(--gray5)'; }
10863          if (tgStat)   tgStat.textContent = 'הוסף ערוצי טלגרם בדשבורד';
10864        }
10865      }
10866    } catch(e3) { /* sources not available */ }
10867
10868    // ── Instagram card ────────────────────────────────────────────────────────
10869    try {
10870      const igResp = await fetch('/api/admin/instagram/profiles', { cache: 'no-store' });
10871      if (igResp.ok) {
10872        const igData  = await igResp.json();
10873        const profiles = igData.profiles || [];
10874        const igDot    = document.getElementById('src-ig-dot');
10875        const igStatus = document.getElementById('src-ig-status');
10876        const igStat   = document.getElementById('src-ig-stat');
10877        const igBar    = document.getElementById('src-ig-bar');
10878        const igCount  = document.getElementById('src-ig-count');
10879        const totalIg  = profiles.reduce((a, p) => a + (p.count || 0), 0);
10880        const active   = profiles.filter(p => p.count > 0).length;
10881        if (igCount)  igCount.textContent = profiles.length;
10882        if (active > 0) {
10883          if (igDot)    { igDot.style.background = '#e1306c'; }
10884          if (igStatus) { igStatus.textContent = 'פעיל'; igStatus.style.color = '#e1306c'; }
10885          if (igStat)   { igStat.textContent = `${profiles.length}
10885 פרופילים · ${totalIg.toLocaleString()} קופונים`; igStat.style.color = ''; }
10886          if (igBar)    { igBar.style.width = Math.min(100, active * 2) + '%'; igBar.style.background = '#e1306c'; }
10887        } else {
10888          if (igDot)    igDot.style.background = 'var(--gray4)';
10889          if (igStatus) { igStatus.textContent = 'ממתין לסריקה'; igStatus.style.color = 'var(--gray5)'; }
10890          if (igStat)   { igStat.textContent = `${profiles.length} פרופילים מוכנים לסריקה`; igStat.style.color = 'var(--gray5)'; }
10891        }
10892      }
10893    } catch(e4) { /* instagram not available */ }
10894
10895    // Pending review count badge
10896    try {
10897      const pendingRes = await fetch('/api/admin/pending-sources');
10898      const pendingData = await pendingRes.json();
10899      const pendingTotal = pendingData.reduce((s, d) => s + d.count, 0);
10900      const pendingBadge = document.getElementById('src-pending-count');
10901      if (pendingBadge) pendingBadge.textContent = pendingTotal || '0';
10902    } catch(ep) { /* pending count not critical */ }
10903
10904    } // end _isAdminAuthed()
10905
10906  } catch(e) { console.error('loadStats failed', e); }
10907  // Always load analytics regardless of stats success/failure
10908  try { _renderPopSearchBar(); } catch(e) {}
10909  try { _renderSlowBar(); } catch(e) {}
10910  loadAnalytics();
10911  loadSpeedAnalytics();
10912  loadPopupAnalytics();
10913}
10914
10915// ── Shared date-range filter ─────────────────────────────────────────────────
10916const _customRanges = {}; // section → {from:ms, to:ms}
10917let _dpSection = null;
10918
10919function _getCutoff(period, section) {
10920  if (period === 'custom') { const r = _customRanges[section]; return r ? r.from : 0; }
10921  const now = Date.now();
10922  if (period === 'day')   return now - 86400000;
10923  if (period === 'week')  return now - 7 * 86400000;
10924  if (period === 'month') return now - 30 * 86400000;
10925  if (period === 'year')  return now - 365 * 86400000;
10926  return 0;
10927}
10928function _getEndCutoff(period, section) {
10929  if (period === 'custom') { const r = _customRanges[section]; return r ? r.to : Date.now(); }
10930  return Date.now();
10931}
10932function _openDatePicker(section, btn) {
10933  _dpSection = section;
10934  const popup = document.getElementById('date-picker-popup');
10935  if (!popup) return;
10936  const r = _customRanges[section];
10937  const now = new Date();
10938  const weekAgo = new Date(Date.now() - 7*86400000);
10939  document.getElementById('dp-from').value = r ? new Date(r.from).toISOString().slice(0,10) : weekAgo.toISOString().slice(0,10);
10940  document.getElementById('dp-to').value   = r ? new Date(r.to).toISOString().slice(0,10)   : now.toISOString().slice(0,10);
10941  const rect = btn.getBoundingClientRect();
10942  popup.style.top   = (rect.bottom + 6) + 'px';
10943  popup.style.right = Math.max(8, window.innerWidth - rect.right) + 'px';
10944  popup.style.left  = 'auto';
10945  popup.style.display = 'block';
10946}
10947function _closeDatePicker() {
10948  const p = document.getElementById('date-picker-popup');
10949  if (p) p.style.display = 'none';
10950  _dpSection = null;
10951}
10952function _applyDatePicker() {
10953  const fv = document.getElementById('dp-from').value;
10954  const tv = document.getElementById('dp-to').value;
10955  if (!fv || !tv) return;
10956  const from = new Date(fv).getTime();
10957  const to   = new Date(tv).getTime() + 86399999; // end of day
10958  if (from > to) { showToast('תאריך התחלה חייב להיות לפני תאריך הסיום'); return; }
10959  _customRanges[_dpSection] = { from, to };
10960  _closeDatePicker();
10961  if (_dpSection === 'analytics') { _atab = 'custom'; _updateTabBar('atab', _atab); loadAnalytics(); }
10962  if (_dpSection === 'speed')     { _stab = 'custom'; _updateTabBar('stab', _stab); loadSpeedAnalytics(); }
10963  if (_dpSection === 'popup')     { _ptab = 'custom'; _updateTabBar('ptab', _ptab); loadPopupAnalytics(); }
10964  if (_dpSection === 'popsearch') { _pstab = 'custom'; _renderPopSearchBar(); _renderPopSearches(); }
10965  if (_dpSection === 'slowest')   { _sltab = 'custom'; _renderSlowBar(); _renderSlowest(); }
10966}
10967// Close picker when clicking outside
10968document.addEventListener('click', e => {
10969  const p = document.getElementById('date-picker-popup');
10970  if (p && p.style.display !== 'none' && !p.contains(e.target) && !e.target.closest('[data-dp-trigger]')) _closeDatePicker();
10971});
10972
10973function _updateTabBar(cls, activeTab) {
10974  document.querySelectorAll('.' + cls).forEach(b => {
10975    const on = b.dataset.period === activeTab;
10976    b.style.background = on ? 'var(--gray4)' : 'transparent';
10977    b.style.color      = on ? 'var(--white)'  : 'var(--gray5)';
10978  });
10979}
10980
10981let _atab = 'week';
10982let _chartActivity = null;
10983
10984function setAtab(btn, period) {
10985  if (period === 'custom') { _openDatePicker('analytics', btn); return; }
10986  _atab = period;
10987  _updateTabBar('atab', _atab);
10988  loadAnalytics();
10989}
10990
10991function _atabCutoff() { return _getCutoff(_atab, 'analytics'); }
10992function _atabEndCutoff() { return _getEndCutoff(_atab, 'analytics'); }
10993
10994function loadAnalytics() {
10995  const d      = _aGet();
10996  const cutoff = _atabCutoff();
10997  const now    = Date.now();
10998
10999  window._allSearchData = d.searches || [];
11000
11001  const endCutoff = _atabEndCutoff();
11002  const events   = (d.events   || []).filter(e => (e.ts||0)    >= cutoff && (e.ts||0)    <= endCutoff);
11003  const searches = (d.searches || []).filter(s => (s.ts||0)    >= cutoff && (s.ts||0)    <= endCutoff);
11004  const sessions = (d.sessions || []).filter(s => (s.start||0) >
11004= cutoff && (s.start||0) <= endCutoff);
11005
11006  const counts = {};
11007  events.forEach(e => { counts[e.type] = (counts[e.type]||0) + 1; });
11008
11009  let totalMs = 0, sessCount = 0;
11010  sessions.forEach(s => { if (s.end) { totalMs += s.end - s.start; sessCount++; } });
11011  const avgMin   = sessCount ? Math.round(totalMs / sessCount / 60000) : 0;
11012  const totalMin = Math.round(totalMs / 60000);
11013
11014  // ── Stat cards ────────────────────────────────────────────────────────────
11015  const grid = document.getElementById('analytics-grid');
11016  if (!grid) return;
11017
11018  const stat = (icon, val, label) => `
11019    <div style="background:var(--gray3);border-radius:10px;padding:12px 10px;text-align:center">
11020      <div style="font-size:18px">${icon}</div>
11021      <div style="font-size:22px;font-weight:700;font-family:system-ui;color:var(--white);line-height:1.2">${val}</div>
11022      <div style="font-size:10px;color:var(--gray5);margin-top:3px">${label}</div>
11023    </div>`;
11024
11025  grid.innerHTML = [
11026    stat('🔍', searches.length,              'חיפושים (ניסיונות)'),
11027    stat('📋', counts.copy||0,               'העתקות קוד'),
11028    stat('👍', counts.thumb_up||0,           'לייקים'),
11029    stat('👎', counts.thumb_down||0,         'דיסלייקים'),
11030    stat('🛍️', counts.show_site||0,          'כניסות לחנות'),
11031    stat('🖱️', counts.coupon_field_click||0, 'לחיצות קוד'),
11032    stat('📅', sessions.length,              'ביקורים באתר'),
11033    stat('👥', new Set(sessions.map(s => s.deviceId).filter(Boolean)).size || (sessions.length ? '?' : 0), 'משתמשים ייחודיים'),
11034  ].join('');
11035
11036  // ── Activity line chart ───────────────────────────────────────────────────
11037  const isDark  = !document.body.classList.contains('light');
11038  const gridClr = isDark ? '#2a2a2a' : '#d0d0ce';
11039  const txtClr  = isDark ? '#888'    : '#606060';
11040  const accentA = 'rgba(0,232,122,0.85)';
11041  const accentB = 'rgba(255,228,94,0.8)';
11042  const accentC = 'rgba(99,179,237,0.8)';
11043
11044  // Determine bucket strategy
11045  let bucketMs, bucketCount, labelFn;
11046  if (_atab === 'day') {
11047    bucketMs = 3600000; bucketCount = 24;
11048    labelFn = ts => new Date(ts).getHours() + ':00';
11049  } else if (_atab === 'week') {
11050    bucketMs = 86400000; bucketCount = 7;
11051    labelFn = ts => { const d2=new Date(ts); return `${d2.getDate()}/${d2.getMonth()+1}`; };
11052  } else if (_atab === 'month') {
11053    bucketMs = 86400000; bucketCount = 30;
11054    labelFn = ts => { const d2=new Date(ts); return `${d2.getDate()}/${d2.getMonth()+1}`; };
11055  } else {
11056    // Auto-range: start from first event so data fills the chart instead of crowding at the right
11057    const allTs = [...searches.map(s=>s.ts||0), ...events.map(e=>e.ts||0)].filter(Boolean);
11058    const firstTs = allTs.length ? allTs.reduce((a, b) => a < b ? a : b, Infinity) : now - 7*86400000;
11059    const spanMs  = now - firstTs;
11060    if (spanMs <= 2 * 86400000) {
11061      bucketMs = 3600000;
11062      bucketCount = Math.max(6, Math.ceil(spanMs / 3600000) + 1);
11063      labelFn = ts => new Date(ts).getHours() + ':00';
11064    } else {
11065      bucketMs = 86400000;
11066      bucketCount = Math.max(3, Math.ceil(spanMs / 86400000) + 1);
11067      labelFn = ts => { const d2=new Date(ts); return `${d2.getDate()}/${d2.getMonth()+1}`; };
11068    }
11069  }
11070
11071  // Build buckets: for each bucket slot compute the bucket start timestamp
11072  const buckets = [];
11073  for (let i = bucketCount - 1; i >= 0; i--) {
11074    const start = Math.floor((now - i * bucketMs) / bucketMs) * bucketMs;
11075    buckets.push({ start, label: labelFn(start), searches: 0, copies: 0, other: 0 });
11076  }
11077  // Fill buckets
11078  searches.forEach(s => {
11079    const bi = buckets.findIndex(b => s.ts >= b.start && s.ts < b.start + bucketMs);
11080    if (bi >= 0) buckets[bi].searches++;
11081  });
11082  events.forEach(e => {
11083    const bi = buckets.findIndex(b => e.ts >= b.start && e.ts < b.start + bucketMs);
11084    if (bi < 0) return;
11085    if (e.type === 'copy')  buckets[bi].copies++;
11086    else if (e.type !== 'search') buckets[bi].other++;
11087  });
11088
11089  const labels      = buckets.map(b => b.label);
11090  const dataSearch  = buckets.map(b => b.searches);
11091  const dataCopy    = buckets.map(b => b.copies);
11092  const dataOther   = buckets.map(b => b.other);
11093
11094  const makeGrad = (ctx, color) => {
11095    const g = ctx.createLinearGradient(0, 0, 0, 160);
11096    g.addColorStop(0, color.replace(')', ',0.25)').replace('rgb', 'rgba'));
11097    g.addColorStop(1, color.replace(')', ',0)').replace('rgb', 'rgba'));
11098    return g;
11099  };
11100
11101  const actCanvas = document.getElementById('chart-activity');
11102  if (actCanvas) {
11103    if (_chartActivity) _chartActivity.destroy();
11104    const ctx = actCanvas.getContext('2d');
11105    _chartActivity = new Chart(ctx, {
11106      type: 'line',
11107      data: {
11108        labels,
11109        datasets: [
11110          { label: 'חיפושים', data: dataSearch, borderColor: accentA, backgroundColor: 'rgba(0,232,122,0.1)', tension: 0.4, fill: true, pointRadius: 2 },
11111          { label: 'העתקות',  data: dataCopy,   borderColor: accentB, backgroundColor: 'rgba(255,228,94,0.08)', tension: 0.4, fill: true, pointRadius: 2 },
11112          { label: 'אחר',     data: dataOther,  borderColor: accentC, backgroundColor: 'rgba(99,179,237,0.07)', tension: 0.4, fill: true, pointRadius: 2 },
11113        ]
11114      },
11115      options: {
11116        responsive: true, maintainAspectRatio: false,
11117        animation: { duration: 400 },
11118        plugins: {
11119          legend: { display: true, position: 'top', align: 'end',
11120            labels: { color: txtClr, font: { size: 11, family: 'DM Sans' }, boxWidth: 10, padding: 10,
11121              generateLabels: chart => chart.data.datasets.map((ds, i) => ({
11122                text: ds.label, datasetIndex: i, hidden: false,
11123                fillStyle: ds.borderColor, strokeStyle: ds.borderColor, lineWidth: 0,
11124                fontColor: txtClr
11125              }))
11126            }
11127          },
11128          tooltip: { backgroundColor: '#1a1a1a', titleColor: '#fff', bodyColor: '#bbb',
11129            borderColor: '#3d3d3d', borderWidth: 1, rtl: true,
11130            callbacks: { title: items => items[0].label }
11131          }
11132        },
11133        scales: {
11134          x: { grid: { color: gridClr }, ticks: { color: txtClr, font: { size: 10 }, maxRotation: 0, maxTicksLimit: 8 } },
11135          y: { grid: { color: gridClr }, ticks: { color: txtClr, font: { size: 10 }, precision: 0 }, beginAtZero: true }
11136        }
11137      }
11138    });
11139  }
11140
11141  // ── Top searches ──────────────────────────────────────────────────────────
11142  _renderPopSearches();
11143}
11144
11145// Alias map: normalises different spellings/languages of the same store into one canonical label.
11146// Keys are lowercase; value is the display label shown in the chart.
11147const _SEARCH_ALIASES = {
11148  // KSP
11149  'ksp':'KSP','כסס':'KSP','קיי אס פי':'KSP',
11150  // Castro
11151  'קסטרו':'קסטרו','castro':'קסטרו',
11152  // Adidas
11153  'adidas':'Adidas','אדידס':'Adidas',
11154  // Nike
11155  'nike':'Nike','נייק':'Nike',
11156  // Fox / Fox Racing (keep separate)
11157  'fox racing':'Fox Racing','פוקס ריסינג':'Fox Racing',
11158  'fox':'Fox','פוקס':'Fox',
11159  // Renuar
11160  'renuar':'Renuar','רנואר':'Renuar',
11161  // Ivory
11162  'ivory':'Ivory','אייבורי':'Ivory',
11163  // Bug
11164  'bug':'Bug','באג':'Bug',
11165  // Golf
11166  'golf':'Golf','גולף':'Golf',
11167  // Zara
11168  'zara':'Zara','זארה':'Zara',
11169  // H&M
11170  'h&m':'H&M','hm':'H&M',
11171  // Mango
11172  'mango':'Mango','מנגו':'Mango',
11173  // Terminal X
11174  'terminalx':'Terminal X','terminal x':'Terminal X','טרמינל':'Terminal X','טרמינל x':'Terminal X',
11175  // Shein
11176  'shein':'Shein','שיין':'Shein',
11177  // Amazon
11178  'amazon':'Amazon','אמזון':'Amazon',
11179  // AliExpress
11180  'aliexpress':'AliExpress','אלי אקספרס':'AliExpress','אליאקספרס':'AliExpress',
11181  // Factory54
11182  'factory54':'Factory54','factory 54':'Factory54',
11183  // Adika
11184  'adika':'Adika','אדיקה':'Adika',
11185  // Super-Pharm
11186  'superpharm':'Super-Pharm','super-pharm':'Super-Pharm','super pharm':'Super-Pharm','סופר פארם':'Super-Pharm',
11187  // Shufersal
11188  'shufersal':'Shufersal','שופרסל':'Shufersal',
11189  // Rami Levy
11190  'rami levy':'Rami Levy','רמי לוי':'Rami Levy',
11191  // Wix
11192  'wix':'Wix',
11193  // Hamashbir
11194  'hamashbir':'המשביר','המשביר':'המשביר',
11195};
11196
11197function _normaliseSearchTerm(q) {
11198  return _SEARCH_ALIASES[q.toLowerCase().trim()] || null;
11199}
11200
11201let _pstab = 'week';
11202function setPstab(period, btn) {
11203  if (period === 'custom') { _openDatePicker('popsearch', btn); return; }
11204  _pstab = period;
11205  _renderPopSearchBar();
11206  _renderPopSearches();
11207}
11208function _renderPopSearchBar() {
11209  const el = document.getElementById('popsearch-period-bar');
11210  if (!el) return;
11211  const periods = [['day','יומי'],['week','שבועי'],['month','חודשי'],['year','שנתי'],['all','הכל'],['custom','📅']];
11212  el.innerHTML = periods.map(([p, lbl]) => {
11213    const on = p === _pstab;
11214    return `<button data-period="${p}" onclick="setPstab('${p}',this)" ${p==='custom'?'data-dp-trigger':''} style="padding:3px 8px;border:none;border-radius:6px;font-size:10px;cursor:pointer;font-family:'DM Sans',sans-serif;background:${on?'var(--gray4)':'transparent'};color:${on?'var(--white)':'var(--gray5)'}">${lbl}</button>`;
11215  }).join('');
11216}
11217function _renderPopSearches() {
11218  const allData = window._allSearchData || [];
11219  const cutoff = _getCutoff(_pstab, 'popsearch');
11220  const endCutoff = _getEndCutoff(_pstab, 'popsearch');
11221  window._topSearchRaw = allData.filter(s => (s.ts||0) >= cutoff && (s.ts||0) <= endCutoff);
11222  _renderTopSearches();
11223}
11224
11225let _tsTab = 'unique';  // 'unique' | 'grouped'
11226
11227function setTopSearchTab(tab) {
11228  _tsTab = tab;
11229  const u = document.getElementById('tsTab-unique');
11230  const g = document.getElementById('tsTab-grouped');
11231  if (u) { u.style.background = tab === 'unique'  ? 'var(--gray4)' : 'transparent'; u.style.color = tab === 'unique'  ? 'var(--white)' : 'var(--gray5)'; }
11232  if (g) { g.style.background = tab === 'grouped' ? 'var(--gray4)' : 'transparent'; g.style.color = tab === 'grouped' ? 'var(--white)' : 'var(--gray5)'; }
11233  _renderTopSearches();
11234}
11235
11236function _renderTopSearches() {
11237  const topEl = document.getElementById('top-searches');
11238  if (!topEl) return;
11239  const raw = window._topSearchRaw || [];   // array of {q, ts}
11240  if (raw.length === 0) {
11241    topEl.innerHTML = '<div style="color:var(--gray5);font-size:12px">אין נתונים עדיין</div>';
11242    return;
11243  }
11244
11245  // termMap: key → { count, lastTs, display }
11246  // - count:   how many times this term was searched
11247  // - lastTs:  most-recent search timestamp (for tie-breaking)
11248  // - display: the label shown to the user
11249  const termMap = {};
11250
11251  const _add = (key, display, ts) => {
11252    if (!termMap[key]) termMap[key] = { count: 0, lastTs: 0, display };
11253    termMap[key].count++;
11254    if ((ts || 0) > termMap[key].lastTs) termMap[key].lastTs = ts || 0;
11255  };
11256
11257  if (_tsTab === 'unique') {
11258    // Case-insensitive dedup: group by lowercase, show original casing of first occurrence
11259    raw.forEach(({ q, ts }) => {
11260      const key = (q || '').toLowerCase().trim();
11261      // display = first seen casing
11262      const display = termMap[key] ? termMap[key].display : q;
11263      _add(key, display, ts);
11264    });
11265
11266  } else {
11267    // Grouped: merge Hebrew ↔ English aliases into one canonical label
11268    raw.forEach(({ q, ts }) => {
11269      const canonical = _normaliseSearchTerm(q) || (q || '').toLowerCase().trim();
11270      _add(canonical, canonical, ts);
11271    });
11272  }
11273
11274  // Sort: count descending → most-recent descending (tie-break) → alphabetical
11275  const topTerms = Object.values(termMap)
11276    .sort((a, b) => b.count - a.count || b.lastTs - a.lastTs || a.display.localeCompare(b.display))
11277    .slice(0, 5);
11278
11279  const totalSearches = raw.length;
11280  const maxN = topTerms[0]?.count || 1;
11281  const rankColors = ['#00e87a','#63b3ed','#ffe45e','#b794f4','#fc8181'];
11282
11283  topEl.innerHTML =
11284    `<div style="font-size:10px;color:var(--gray5);margin-bottom:8px">מתוך ${totalSearches} חיפושים בתקופה הנבחרת</div>` +
11285    topTerms.map(({ display, count }, i) => {
11286      const barW = Math.round(count / maxN * 100);
11287      return `<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--gray2)">
11288        <div style="width:18px;text-align:center;font-size:11px;font-weight:700;color:${rankColors[i]}">${i+1}</div>
11289        <div style="flex:1;min-width:0">
11290          <div style="font-size:13px;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${display}</div>
11291          <div style="height:3px;background:var(--gray2);border-radius:2px;margin-top:4px">
11292            <div style="height:3px;width:${barW}%;background:${rankColors[i]};border-radius:2px;transition:width 0.4s"></div>
11293          </div>
11294        </div>
11295        <div style="font-size:12px;font-weight:600;color:${rankColors[i]};white-space:nowrap">${count}x</div>
11296      </div>`;
11297    }).join('');
11298}
11299
11300// ── Loading screen: Skeleton + Tips ─────────────────────────────────────────
11301const _TIPS = [
11302  { icon: '🛒', text: 'השאר מוצרים בעגלה יום-יומיים — הרבה חנויות שולחות קוד הנחה בדוא"ל כדי לגמור את הקנייה.' },
11303  { icon: '💳', text: 'כרטיסי אשראי מסוימים נותנים קאשבק נוסף על קנייה עם קוד קופון — תמיד שווה לבדוק!' },
11304  { icon: '📱', text: 'אפליקציות של KSP ו-Bug מציעות לפעמים מבצעים בלעדיים שלא מופיעים באתר.' },
11305  { icon: '⏰', text: 'שישי בצהריים הוא הזמן הכי טוב למצוא קופונים ישראלים — רוב החנויות מפרסמות לסוף השבוע.' },
11306  { icon: '🔔', text: 'מעקב אחרי ערוצי טלגרם של אינפלואנסרים ישראלים מניב לפעמים קופונים שלא מפורסמים לקהל הרחב - אנחנו נמצא לכם אותם.' },
11307  { icon: '🎁', text: 'בדוק תמיד אם יש משלוח חינם — לפעמים זה שווה יותר מהנחה באחוזים.' },
11308  { icon: '🧩', text: 'חנויות רבות מאפשרות להשתמש בקוד קופון ביחד עם מבצע סייל — שווה לנסות!' },
11309  { icon: '📧', text: 'הרשמה לניוזלטר של חנות בפעם הראשונה נותנת לרוב קוד הנחה של 10-15% מיד.' },
11310  { icon: '🕐', text: 'בלאק פריידי ופסח הם הזמנים הכי טובים - תתחיל לחפש שבוע מראש.' },
11311  { icon: '🎯', text: 'תאריך הפקיעה על הקופון לא תמיד מדויק — קודים רבים עובדים גם אחרי התאריך הרשמי.' },
11312];
11313let _tipIdx = -1;
11314let _tipInterval = null;
11315let _gameInterval = null; // kept for legacy references
11316
11317function _skelCard() {
11318  return `<div style="background:var(--gray2);border:1px solid var(--gray3);border-radius:14px;padding:14px">
11319    <div style="display:flex;gap:10px;align-items:center;margin-bottom:10px">
11320      <div class="shimmer-bg" style="width:38px;height:38px;border-radius:10px;flex-shrink:0"></div>
11321      <div style="flex:1">
11322        <div class="shimmer-bg" style="height:10px;border-radius:5px;width:75%;margin-bottom:6px"></div>
11323        <div class="shimmer-bg" style="height:8px;border-radius:5px;width:50%"></div>
11324      </div>
11325    </div>
11326    <div class="shimmer-bg" style="height:20px;border-radius:8px;width:65%;margin-bottom:10px"></div>
11327    <div class="shimmer-bg" style="height:36px;border-radius:8px;margin-bottom:10px"></div>
11328    <div style="display:flex;gap:6px">
11329      <div class="shimmer-bg" style="height:8px;border-radius:5px;width:40%"></div>
11330      <div class="shimmer-bg" style="height:8px;border-radius:5px;width:30%"></div>
11331    </div>
11332  </div>`;
11333}
11334
11335function _randomTipIdx() {
11336  if (_TIPS.length <= 1) return 0;
11337  let next;
11338  do { next = Math.floor(Math.random() * _TIPS.length); } while (next === _tipIdx);
11339  return next;
11340}
11341
11342function _showTip(i) {
11343  const icon = document.getElementById('tip-icon-main');
11344  const text = document.getElementById('tip-text-main');
11345  const bar  = document.getElementById('tip-progress-bar');
11346  if (!icon || !text) return;
11347  icon.textContent = _TIPS[i].icon;
11348  text.textContent = _TIPS[i].text;
11349  if (bar) {
11350    bar.style.transition = 'none';
11351    bar.style.width = '0%';
11352    requestAnimationFrame(() => requestAnimationFrame(() => {
11353      bar.style.transition = 'width 16s linear';
11354      bar.style.width = '100%';
11355    }));
11356  }
11357}
11358
11359function _startMiniGame() {
11360  const wrap   = document.getElementById('mini-game-wrap');
11361  const banner = document.getElementById('ai-banner');
11362  if (!wrap) return;
11363
11364  // Always hide the skel-grid — skeletons are now inline in results-grid
11365  const skelGrid = document.getElementById('skel-grid');
11366  if (skelGrid) { skelGrid.style.display = 'none'; skelGrid.innerHTML = ''; }
11367
11368  // Start tips carousel — random pick, never repeat back-to-back
11369  _tipIdx = _randomTipIdx();
11370  _showTip(_tipIdx);
11371  clearInterval(_tipInterval);
11372  _tipInterval = setInterval(() => { _tipIdx = _randomTipIdx(); _showTip(_tipIdx); }, 16000);
11373
11374  wrap.style.display = 'block';
11375  if (banner) banner.style.borderRadius = '16px 16px 0 0';
11376}
11377
11378function _stopMiniGame() {
11379  clearInterval(_tipInterval); _tipInterval = null;
11380  clearInterval(_gameInterval); _gameInterval = null;
11381  const wrap = document.getElementById('mini-game-wrap');
11382  if (wrap) wrap.style.display = 'none';
11383}
11384
11385// ── Search Speed Analytics ───────────────────────────────────────────────────
11386let _stab = 'week';
11387let _chartSpeed = null;
11388let _speedMode = 'ai'; // 'ai' = fast_search (DB part of חיפוש AI) | 'fast' = search_time (חיפוש יסודי בטא)
11389
11390function setSpeedMode(mode) {
11391  _speedMode = mode;
11392  const aiBtn   = document.getElementById('smode-ai');
11393  const fastBtn = document.getElementById('smode-fast');
11394  if (aiBtn)   { aiBtn.style.background   = mode==='ai'  ?'var(--gray4)':'transparent'; aiBtn.style.color   = mode==='ai'  ?'var(--white)':'var(--gray5)'; }
11395  if (fastBtn) { fastBtn.style.background = mode==='fast'?'var(--gray4)':'transparent'; fastBtn.style.color = mode==='fast'?'var(--white)':'var(--gray5)'; }
11396  loadSpeedAnalytics();
11397}
11398
11399function setStab(btn, period) {
11400  if (period === 'custom') { _openDatePicker('speed', btn); return; }
11401  _stab = period;
11402  _updateTabBar('stab', _stab);
11403  loadSpeedAnalytics();
11404}
11405
11406let _sltab = 'week';
11407function setSltab(period, btn) {
11408  if (period === 'custom') { _openDatePicker('slowest', btn); return; }
11409  _sltab = period;
11410  _renderSlowBar();
11411  _renderSlowest();
11412}
11413function _renderSlowBar() {
11414  const el = document.getElementById('slowest-period-bar');
11415  if (!el) return;
11416  const periods = [['day','יומי'],['week','שבועי'],['month','חודשי'],['year','שנתי'],['all','הכל'],['custom','📅']];
11417  el.innerHTML = periods.map(([p, lbl]) => {
11418    const on = p === _sltab;
11419    return `<button data-period="${p}" onclick="setSltab('${p}',this)" ${p==='custom'?'data-dp-trigger':''} style="padding:3px 8px;border:none;border-radius:6px;font-size:10px;cursor:pointer;font-family:'DM Sans',sans-serif;background:${on?'var(--gray4)':'transparent'};color:${on?'var(--white)':'var(--gray5)'}">${lbl}</button>`;
11420  }).join('');
11421}
11422function _renderSlowest() {
11423  const slowEl = document.getElementById('speed-slowest');
11424  if (!slowEl) return;
11425  const allData = window._allSpeedData || [];
11426  const cutoff = _getCutoff(_sltab, 'slowest');
11427  const endCutoff = _getEndCutoff(_sltab, 'slowest');
11428  const times = allData.filter(t => (t.ts||0) >= cutoff && (t.ts||0) <= endCutoff);
11429  if (times.length === 0) { slowEl.innerHTML = '<div style="color:var(--gray5);font-size:12px">אין נתונים</div>'; return; }
11430  const slowest = [...times].sort((a,b) => b.elapsed - a.elapsed).slice(0, 5);
11431  const maxE = slowest[0].elapsed || 1;
11432  slowEl.innerHTML = slowest.map((t, i) => {
11433    const col = t.elapsed > 60 ? '#fc8181' : t.elapsed > 30 ? '#ffe45e' : '#00e87a';
11434    const barW = Math.round(t.elapsed / maxE * 100);
11435    return `<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--gray2)">
11436      <div style="width:18px;text-align:center;font-size:11px;font-weight:700;color:${col}">${i+1}</div>
11437      <div style="flex:1;min-width:0">
11438        <div style="font-size:13px;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${t.q}</div>
11439        <div style="height:3px;background:var(--gray2);border-radius:2px;margin-top:4px">
11440          <div style="height:3px;width:${barW}%;background:${col};border-radius:2px;transition:width 0.4s"></div>
11441        </div>
11442      </div>
11443      <div style="text-align:left;white-space:nowrap">
11444        <div style="font-size:12px;font-weight:700;color:${col}">${t.elapsed}s</div>
11445        <div style="font-size:10px;color:var(--gray5)">${t.results} תוצאות</div>
11446      </div>
11447    </div>`;
11448  }).join('');
11449}
11450
11451function loadSpeedAnalytics() {
11452  const d = _aGet();
11453  const now = Date.now();
11454  let cutoff = 0;
11455  if (_stab === 'day')   cutoff = now - 86400000;
11456  if (_stab === 'week')  cutoff = now - 7  * 86400000;
11457  if (_stab === 'month') cutoff = now - 30 * 86400000;
11458  if (_stab === 'year')  cutoff = now - 365 * 86400000;
11459  if (_stab === 'custom') cutoff = _getCutoff('custom', 'speed');
11460  const endCutoff = _getEndCutoff(_stab, 'speed');
11461
11462  const isDeep    = _speedMode === 'fast'; // 'fast' mode = חיפוש יסודי בטא = search_time events
11463  const eventType = isDeep ? 'search_time' : 'fast_search';
11464
11465  // Update dynamic labels
11466  const chartLabel   = document.getElementById('speed-chart-label');
11467  const slowestLabel = document.getElementById('slowest-label');
11468  if (chartLabel)   chartLabel.textContent   = isDeep ? '⏱️ זמן חיפוש יסודי לאורך זמן (שניות)' : '⚡ זמן חיפוש AI לאורך זמן (שניות)';
11469  if (slowestLabel) slowestLabel.textContent = isDeep ? '🐢 החיפושים היסודיים האיטיים ביותר'   : '🐢 חיפושי AI הארוכים ביותר';
11470
11471  window._allSpeedData = (d.events || [])
11472    .filter(e => e.type === eventType && e.data && e.data.elapsed)
11473    .map(e => ({ elapsed: e.data.elapsed, q: e.data.q || '', ts: e.ts || 0, results: e.data.results || 0 }));
11474
11475  const times = (d.events || [])
11476    .filter(e => e.type === eventType && (e.ts||0) >= cutoff && (e.ts||0) <= endCutoff && e.data && e.data.elapsed)
11477    .map(e => ({ elapsed: e.data.elapsed, q: e.data.q || '', ts: e.ts || 0, results: e.data.results || 0 }));
11478
11479  const failedCount   = !isDeep ? 0 : (d.events || []).filter(e => e.type === 'search_failed'  && (e.ts||0) >= cutoff && (e.ts||0) <= endCutoff).length;
11480  const droppedCount  = !isDeep ? 0 : (d.events || []).filter(e => e.type === 'search_dropped' && (e.ts||0) >= cutoff && (e.ts||0) <= endCutoff).length;
11481  const totalAttempts = !isDeep ? 0 : (d.searches || []).filter(s => (s.ts||0) >= cutoff && (s.ts||0) <= endCutoff).length;
11482  const unknownCount  = !isDeep ? 0 : Math.max(0, totalAttempts - times.length - failedCount - droppedCount);
11483
11484  const cards = document.getElementById('speed-cards');
11485  if (!cards) return;
11486
11487  if (times.length === 0) {
11488    cards.innerHTML = `<div style="grid-column:1/-1;color:var(--gray5);font-size:13px">אין נתוני זמן חיפוש עדיין — בצע חיפוש כדי להתחיל לאסוף נתונים.</div>`;
11489    _renderSlowest();
11490    if (_chartSpeed) { _chartSpeed.destroy(); _chartSpeed = null; }
11491    return;
11492  }
11493
11494  const elapseds = times.map(t => t.elapsed);
11495  const avg  = (elapseds.reduce((a,b)=>a+b,0) / elapseds.length).toFixed(1);
11496  const minT = elapseds.reduce((a, b) => a < b ? a : b, Infinity).toFixed(1);
11497  const maxT = elapseds.reduce((a, b) => a > b ? a : b, -Infinity).toFixed(1);
11498  const over60 = elapseds.filter(t => t > 60).length;
11499  const threshold = isDeep ? 30 : 5; // deep=live scraping (<30s good), ai=DB lookup (<5s good)
11500  const pctFast = Math.round((elapseds.filter(t => t < threshold).length / elapseds.length) * 100);
11501
11502  const sc = (icon, val, label, col) => `
11503    <div style="background:var(--gray3);border-radius:10px;padding:12px 10px;text-align:center">
11504      <div style="font-size:18px">${icon}</div>
11505      <div style="font-size:22px;font-weight:700;font-family:system-ui;color:${col||'var(--white)'};line-height:1.2">${val}</div>
11506      <div style="font-size:10px;color:var(--gray5);margin-top:3px">${label}</div>
11507    </div>`;
11508
11509  cards.innerHTML = [
11510    sc('📊', avg + 's', 'ממוצע', 'var(--white)'),
11511    sc('🟢', minT + 's', 'מהיר ביותר', 'var(--accent)'),
11512    sc('🔴', maxT + 's', 'איטי ביותר', '#fc8181'),
11513    sc('⚡', pctFast + '%', `מתחת ל-${threshold}s`, pctFast > 70 ? 'var(--accent)' : '#ffe45e'),
11514    sc('🔢', times.length, 'חיפושים הושלמו', 'var(--white)'),
11515    ...(!isDeep ? [] : [
11516      sc('❌', failedCount,  'חיפושים נכשלו',    failedCount  > 0 ? '#fc8181' : 'var(--gray5)'),
11517      sc('↩️', droppedCount, 'חיפושים נזנחו',   droppedCount > 0 ? '#ffe45e' : 'var(--gray5)'),
11518      sc('❓', unknownCount, 'חיפושים לא ידועים', unknownCount > 0 ? '#a78bfa' : 'var(--gray5)'),
11519    ]),
11520  ].join('');
11521
11522  // ── Chart ──────────────────────────────────────────────────────────────────
11523  const isDark  = !document.body.classList.contains('light');
11524  const gridClr = isDark ? '#2a2a2a' : '#d0d0ce';
11525  const txtClr  = isDark ? '#888'    : '#606060';
11526
11527  let bucketMs, bucketCount, labelFn;
11528  if (_stab === 'day') {
11529    bucketMs = 3600000; bucketCount = 24;
11530    labelFn = ts => new Date(ts).getHours() + ':00';
11531  } else if (_stab === 'week') {
11532    bucketMs = 86400000; bucketCount = 7;
11533    labelFn = ts => { const d2=new Date(ts); return `${d2.getDate()}/${d2.getMonth()+1}`; };
11534  } else if (_stab === 'month') {
11535    bucketMs = 86400000; bucketCount = 30;
11536    labelFn = ts => { const d2=new Date(ts); return `${d2.getDate()}/${d2.getMonth()+1}`; };
11537  } else {
11538    // Auto-range: use ALL events (not just search_time) so the span is correct
11539    const allEvTs = (d.events || []).map(e =>
11539 e.ts || 0).filter(Boolean);
11540    const firstTs = allEvTs.length ? allEvTs.reduce((a, b) => a < b ? a : b, Infinity) : now - 7*86400000;
11541    const spanMs  = now - firstTs;
11542    if (spanMs <= 2 * 86400000) {
11543      bucketMs = 3600000;
11544      bucketCount = Math.max(6, Math.ceil(spanMs / 3600000) + 1);
11545      labelFn = ts => new Date(ts).getHours() + ':00';
11546    } else {
11547      bucketMs = 86400000;
11548      bucketCount = Math.max(3, Math.ceil(spanMs / 86400000) + 1);
11549      labelFn = ts => { const d2=new Date(ts); return `${d2.getDate()}/${d2.getMonth()+1}`; };
11550    }
11551  }
11552
11553  const buckets = [];
11554  for (let i = bucketCount - 1; i >= 0; i--) {
11555    const start = Math.floor((now - i * bucketMs) / bucketMs) * bucketMs;
11556    buckets.push({ start, label: labelFn(start), vals: [] });
11557  }
11558  times.forEach(t => {
11559    const bi = buckets.findIndex(b => t.ts >= b.start && t.ts < b.start + bucketMs);
11560    if (bi >= 0) buckets[bi].vals.push(t.elapsed);
11561  });
11562
11563  const labels   = buckets.map(b => b.label);
11564  const avgData  = buckets.map(b => b.vals.length ? +(b.vals.reduce((a,v)=>a+v,0)/b.vals.length).toFixed(1) : null);
11565  const maxData  = buckets.map(b => b.vals.length ? b.vals.reduce((a, v) => a > v ? a : v, -Infinity) : null);
11566
11567  const actCanvas = document.getElementById('chart-speed');
11568  if (actCanvas) {
11569    if (_chartSpeed) _chartSpeed.destroy();
11570    _chartSpeed = new Chart(actCanvas.getContext('2d'), {
11571      type: 'line',
11572      data: {
11573        labels,
11574        datasets: [
11575          { label: 'ממוצע (s)', data: avgData, borderColor: 'rgba(0,232,122,0.85)', backgroundColor: 'rgba(0,232,122,0.08)', tension: 0.4, fill: true, pointRadius: 3, spanGaps: true },
11576          { label: 'מקסימום (s)', data: maxData, borderColor: 'rgba(252,129,129,0.8)', backgroundColor: 'rgba(252,129,129,0.06)', tension: 0.4, fill: false, pointRadius: 3, spanGaps: true },
11577        ]
11578      },
11579      options: {
11580        responsive: true, maintainAspectRatio: false,
11581        animation: { duration: 400 },
11582        plugins: {
11583          legend: { display: true, position: 'top', align: 'end',
11584            labels: { color: txtClr, font: { size: 11, family: 'DM Sans' }, boxWidth: 10, padding: 10,
11585              generateLabels: chart => chart.data.datasets.map((ds, i) => ({
11586                text: ds.label, datasetIndex: i, hidden: false,
11587                fillStyle: ds.borderColor, strokeStyle: ds.borderColor, lineWidth: 0,
11588                fontColor: txtClr
11589              }))
11590            }
11591          },
11592          tooltip: { backgroundColor: '#1a1a1a', titleColor: '#fff', bodyColor: '#bbb', borderColor: '#3d3d3d', borderWidth: 1 }
11593        },
11594        scales: {
11595          x: { grid: { color: gridClr }, ticks: { color: txtClr, font: { size: 10 }, maxRotation: 0, maxTicksLimit: 8 } },
11596          y: { grid: { color: gridClr }, ticks: { color: txtClr, font: { size: 10 } }, beginAtZero: true,
11597               title: { display: true, text: 'שניות', color: txtClr, font: { size: 10 } } }
11598        }
11599      }
11600    });
11601  }
11602
11603  // ── Slowest searches ───────────────────────────────────────────────────────
11604  _renderSlowest();
11605}
11606
11607// ── Popup analytics panel ─────────────────────────────────────────────────────
11608let _ptab = 'week';
11609function setPopupTab(btn, period) {
11610  if (period === 'custom') { _openDatePicker('popup', btn); return; }
11611  _ptab = period;
11612  _updateTabBar('ptab', _ptab);
11613  loadPopupAnalytics();
11614}
11615function _ptabCutoff()    { return _getCutoff(_ptab, 'popup'); }
11616function _ptabEndCutoff() { return _getEndCutoff(_ptab, 'popup'); }
11617
11618function loadPopupAnalytics() {
11619  const d        = _aGet();
11620  const cutoff   = _ptabCutoff();
11621  const endCutoff = _ptabEndCutoff();
11622  const events   = (d.events || []).filter(e => (e.ts||0) >= cutoff && (e.ts||0) <= endCutoff);
11623
11624  const popups     = events.filter(e => e.type === 'coupon_popup');
11625  const copies     = events.filter(e => e.type === 'modal_copy');
11626  const moreClicks = events.filter(e => e.type === 'more_coupons');
11627
11628  const cards = document.getElementById('popup-cards');
11629  if (!cards) return;
11630
11631  const sc = (icon, val, label, col) => `
11632    <div style="background:var(--gray3);border-radius:10px;padding:12px 10px;text-align:center">
11633      <div style="font-size:18px">${icon}</div>
11634      <div style="font-size:22px;font-weight:700;font-family:system-ui;color:${col||'var(--white)'};line-height:1.2">${val}</div>
11635      <div style="font-size:10px;color:var(--gray5);margin-top:3px">${label}</div>
11636    </div>`;
11637
11638  const uniqueStores = new Set(popups.map(e => (e.data && e.data.store) || '').filter(Boolean)).size;
11639
11640  cards.innerHTML = [
11641    sc('🎫', popups.length,     'פתיחות פופאפ',      '#a78bfa'),
11642    sc('🏪', uniqueStores,      'חנויות שונות',       '#63b3ed'),
11643    sc('📋', copies.length,     'העתקות מהפופאפ',     'var(--accent)'),
11644    sc('🔗', moreClicks.length, 'לחיצות עוד קופונים', '#ffe45e'),
11645  ].join('');
11646
11647  // ── Top stores ──────────────────────────────────────────────────────────────
11648  const storeCount = {};
11649  popups.forEach(e => {
11650    const s = (e.data && e.data.store) || '';
11651    if (s) storeCount[s] = (storeCount[s] || 0) + 1;
11652  });
11653
11654  const topEl = document.getElementById('popup-top-stores');
11655  if (!topEl) return;
11656
11657  const sorted = Object.entries(storeCount).sort((a, b) => b[1] - a[1]).slice(0, 10);
11658  if (sorted.length === 0) {
11659    topEl.innerHTML = '<div style="color:var(--gray5);font-size:12px">אין נתונים עדיין</div>';
11660    return;
11661  }
11662
11663  const maxCount = sorted[0][1];
11664  const medal = ['🥇','🥈','🥉'];
11665  topEl.innerHTML = sorted.map(([store, count], i) => {
11666    const barW = Math.round(count / maxCount * 100);
11667    const col  = i < 3 ? ['#ffe45e','#c0c0c0','#cd7f32'][i] : 'var(--accent)';
11668    return `<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--gray2)">
11669      <div style="width:20px;text-align:center;font-size:13px">${medal[i] || i+1}</div>
11670      <div style="flex:1;min-width:0">
11671        <div style="font-size:13px;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${store}</div>
11672        <div style="height:3px;background:var(--gray2);border-radius:2px;margin-top:4px">
11673          <div style="height:3px;width:${barW}%;background:${col};border-radius:2px;transition:width 0.4s"></div>
11674        </div>
11675      </div>
11676      <div style="font-size:12px;font-weight:700;color:${col};white-space:nowrap">${count}</div>
11677    </div>`;
11678  }).join('');
11679}
11680
11681_loadGlobeFingerprint();
11682loadCoupons();
11683loadStats();
11684renderExpiredDemo(); // show demo immediately, doesn't need API data
11685
11686// Initialize affiliate sidebar badge from localStorage
11687(()=>{ const reg = _AFF_LIST.filter(p=>_affGetStatus(p.id)==='registered').length; const el=document.getElementById('src-aff-count'); if(el) el.textContent=reg||'—'; })();
11688
11689// ── Theme toggle ────────────────────────────────────────────────────────────
11690const MOON_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>`;
11691const SUN_SVG  = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>`;
11692
11693function toggleTheme() {
11694  const isLight = document.body.classList.toggle('light');
11695  localStorage.setItem('theme', isLight ? 'light' : 'dark');
11696  document.getElementById('theme-btn').innerHTML = isLight ? SUN_SVG : MOON_SVG;
11697}
11698
11699function _navDrawerOpen() {
11700  document.getElementById('nav-drawer').classList.add('open');
11701  document.getElementById('nav-backdrop').classList.add('show');
11702}
11703function _navDrawerClose() {
11704  document.getElementById('nav-drawer').classList.remove('open');
11705  document.getElementById('nav-backdrop').classList.remove('show');
11706}
11707
11708// Apply saved theme on load
11709(function() {
11710  if (localStorage.getItem('theme') === 'light') {
11711    document.body.classList.add('light');
11712    document.getElementById('theme-btn').innerHTML = SUN_SVG;
11713  }
11714})();
11715
11716// ── Extension Analytics ────────────────────────────────────────────────────────
11717let _etab = 'week';
11718let _extStoresPageIdx = 0;
11719let _extAllStores = []; // full sorted list for paging
11720const EXT_PAGE_SIZE = 5;
11721
11722function setEtab(btn, period) {
11723  _etab = period;
11724  _extStoresPageIdx = 0;
11725  document.querySelectorAll('.etab').forEach(b => {
11726    b.style.background = 'transparent'; b.style.color = 'var(--gray5)';
11727  });
11728  btn.style.background = 'var(--gray4)'; btn.style.color = 'var(--white)';
11729  loadExtensionAnalytics();
11730}
11731
11732function extStoresPage(dir) {
11733  const pages = Math.ceil(_extAllStores.length / EXT_PAGE_SIZE);
11734  _extStoresPageIdx = Math.max(0, Math.min(pages - 1, _extStoresPageIdx + dir));
11735  _renderExtTopStores();
11736}
11737
11738function _renderExtTopStores() {
11739  const storesEl = document.getElementById('ext-top-stores');
11740  if (!storesEl) return;
11741  const pages = Math.ceil(_extAllStores.length / EXT_PAGE_SIZE) || 1;
11742  const page  = _extAllStores.slice(_extStoresPageIdx * EXT_PAGE_SIZE, (_extStoresPageIdx + 1) * EXT_PAGE_SIZE);
11743  const maxVal = (_extAllStores[0]?.[1]) || 1;
11744  const medal = ['🥇','🥈','🥉'];
11745
11746  const label = document.getElementById('ext-stores-page-label');
11747  if (label) label.textContent = pages > 1 ? `${_extStoresPageIdx + 1} / ${pages}` : '';
11748  const prevBtn = document.getElementById('ext-stores-prev');
11749  const nextBtn = document.getElementById('ext-stores-next');
11750  if (prevBtn) prevBtn.style.opacity = _extStoresPageIdx === 0 ? '0.3' : '1';
11751  if (nextBtn) nextBtn.style.opacity = _extStoresPageIdx >= pages - 1 ? '0.3' : '1';
11752
11753  if (!page.length) {
11754    storesEl.innerHTML = '<div style="font-size:11px;color:var(--gray5)">אין נתונים עדיין — הנתונים יופיעו לאחר שיירשמו ביקורים חדשים</div>';
11755    return;
11756  }
11757
11758  const globalOffset = _extStoresPageIdx * EXT_PAGE_SIZE;
11759  storesEl.innerHTML = page.map(([store, count], i) => {
11760    const rank = globalOffset + i;
11761    const barW = Math.round(count / maxVal * 100);
11762    const col  = rank < 3 ? ['#ffe45e','#c0c0c0','#cd7f32'][rank] : '#00e87a';
11763    return `<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--gray3)">
11764      <div style="width:22px;text-align:center;font-size:13px">${medal[rank] || rank + 1}</div>
11765      <div style="flex:1;min-width:0">
11766        <div style="font-size:12px;color:var(--white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${store}</div>
11767        <div style="height:3px;background:var(--gray2);border-radius:2px;margin-top:4px">
11768          <div style="height:3px;width:${barW}%;background:${col};border-radius:2px;transition:width 0.4s"></div>
11769        </div>
11770      </div>
11771      <div style="font-size:12px;font-weight:700;color:${col}">${count}</div>
11772    </div>`;
11773  }).join('');
11774}
11775
11776function loadExtensionAnalytics() {
11777  const d = _aGet();
11778  const now = Date.now();
11779  const cutoffMap = { day: now - 86400000, week: now - 7*86400000, month: now - 30*86400000, all: 0 };
11780  const cutoff = cutoffMap[_etab] || 0;
11781
11782  const allEvents = (d.events || []).filter(e => (e.ts||0) >= cutoff);
11783  const events    = allEvents.filter(e => (e.type||'').startsWith('ext_'));
11784  const cartEvs   = allEvents.filter(e => (e.type||'').startsWith('cart_'));
11785
11786  const visits     = events.filter(e => e.type === 'ext_store_visit');
11787  const opens      = events.filter(e => e.type === 'ext_popup_open');
11788  const reveals    = events.filter(e => e.type === 'ext_reveal');
11789  const copies     = events.filter(e => e.type === 'ext_copy');
11790  const viewAlls   = events.filter(e => e.type === 'ext_view_all');
11791  const voteUps    = events.filter(e => e.type === 'ext_vote_up');
11792  const voteDns    = events.filter(e => e.type === 'ext_vote_down');
11793
11794  const cartOpens   = cartEvs.filter(e => e.type === 'cart_popup_open');
11795  const cartReveals = cartEvs.filter(e => e.type === 'cart_reveal');
11796  const cartCopies  = cartEvs.filter(e => e.type === 'cart_copy');
11797  const cartViews   = cartEvs.filter(e => e.type === 'cart_view_all');
11798  const cartVoteUps = cartEvs.filter(e => e.type === 'cart_vote_up');
11799  const cartVoteDns = cartEvs.filter(e => e.type === 'cart_vote_down');
11800
11801  // ── Stat cards ────────────────────────────────────────────────────────────
11802  const grid = document.getElementById('ext-stats-grid');
11803  if (!grid) return;
11804  const stat = (icon, val, label) => `
11805    <div style="background:var(--gray2);border-radius:8px;padding:10px 8px;text-align:center">
11806      <div style="font-size:16px">${icon}</div>
11807      <div style="font-size:20px;font-weight:700;font-family:system-ui;color:var(--white);line-height:1.2">${val}</div>
11808      <div style="font-size:10px;color:var(--gray5);margin-top:2px">${label}</div>
11809    </div>`;
11810  grid.innerHTML = [
11811    stat('🏪', visits.length,   'ביקורי חנות'),
11812    stat('🧩', opens.length,    'פתיחות פופאפ'),
11813    stat('👁️', reveals.length,  'קופונים שנחשפו'),
11814    stat('📋', copies.length,   'קופונים שהועתקו'),
11815    stat('🔗', viewAlls.length, 'צפה בכל הקופונים'),
11816    stat('👍', voteUps.length,  'הצבעות חיוביות'),
11817    stat('👎', voteDns.length,  'הצבעות שליליות'),
11818  ].join('');
11819
11820  // ── Cart popup stats ───────────────────────────────────────────────────────
11821  const cartGrid = document.getElementById('cart-stats-grid');
11822  if (cartGrid) cartGrid.innerHTML = [
11823    stat('🛒', cartOpens.length,   'פתיחות פופאפ'),
11824    stat('👁️', cartReveals.length, 'קופונים שנחשפו'),
11825    stat('📋', cartCopies.length,  'קופונים שהועתקו'),
11826    stat('🔗', cartViews.length,   'צפה בכל הקופונים'),
11827    stat('👍', cartVoteUps.length, 'הצבעות חיוביות'),
11828    stat('👎', cartVoteDns.length, 'הצבעות שליליות'),
11829  ].join('');
11830
11831  // ── Top stores (combined ext + cart visits) with paging ───────────────────
11832  const storeCounts = {};
11833  [...visits, ...cartEvs.filter(e =>
11833 e.type === 'cart_popup_open')].forEach(e => {
11834    const s = e.store || e.data?.store;
11835    if (s) storeCounts[s] = (storeCounts[s] || 0) + 1;
11836  });
11837  _extAllStores = Object.entries(storeCounts).sort((a, b) => b[1] - a[1]);
11838  _extStoresPageIdx = 0;
11839  _renderExtTopStores();
11840
11841  // ── Comparison chart ───────────────────────────────────────────────────────
11842  const cmpEl = document.getElementById('ext-comparison-chart');
11843  if (cmpEl) {
11844    const metrics = [
11845      { label: 'פתיחות פופאפ',      ext: opens.length,    cart: cartOpens.length },
11846      { label: 'קופונים שנחשפו',    ext: reveals.length,  cart: cartReveals.length },
11847      { label: 'קופונים שהועתקו',   ext: copies.length,   cart: cartCopies.length },
11848      { label: 'צפה בכל הקופונים',  ext: viewAlls.length, cart: cartViews.length },
11849      { label: 'הצבעות',            ext: voteUps.length + voteDns.length, cart: cartVoteUps.length + cartVoteDns.length },
11850    ];
11851    const maxVal = Math.max(1, ...metrics.map(m => Math.max(m.ext, m.cart)));
11852    cmpEl.innerHTML = metrics.map(m => `
11853      <div style="margin-bottom:12px">
11854        <div style="font-size:11px;color:var(--gray5);margin-bottom:5px;text-align:right">${m.label}</div>
11855        <div style="display:flex;flex-direction:column;gap:3px">
11856          <div style="display:flex;align-items:center;gap:6px">
11857            <div style="font-size:10px;color:#00e87a;width:60px;text-align:right;flex-shrink:0">פופאפ 🧩</div>
11858            <div style="flex:1;background:var(--gray3);border-radius:4px;height:8px">
11859              <div style="width:${Math.round(m.ext/maxVal*100)}%;background:#00e87a;height:8px;border-radius:4px;transition:width .3s"></div>
11860            </div>
11861            <div style="font-size:11px;color:var(--white);min-width:24px;text-align:left">${m.ext}</div>
11862          </div>
11863          <div style="display:flex;align-items:center;gap:6px">
11864            <div style="font-size:10px;color:#60a5fa;width:60px;text-align:right;flex-shrink:0">סל קניות 🛒</div>
11865            <div style="flex:1;background:var(--gray3);border-radius:4px;height:8px">
11866              <div style="width:${Math.round(m.cart/maxVal*100)}%;background:#60a5fa;height:8px;border-radius:4px;transition:width .3s"></div>
11867            </div>
11868            <div style="font-size:11px;color:var(--white);min-width:24px;text-align:left">${m.cart}</div>
11869          </div>
11870        </div>
11871      </div>`).join('');
11872  }
11873}
11874
11875// ── Button Click Analytics ────────────────────────────────────────────────────
11876function loadButtonClickAnalytics() {
11877  const container = document.getElementById('view-analytics-buttons');
11878  if (!container) return;
11879  container.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
11880  fetch('/api/admin/button-clicks').then(r => r.json()).then(res => {
11881    const rows = res.data || [];
11882    if (!rows.length) {
11883      container.innerHTML = '<div style="color:var(--gray5);padding:20px">אין נתונים עדיין.</div>';
11884      return;
11885    }
11886    const tableRows = rows.map(r => `
11887      <tr style="border-bottom:1px solid var(--gray3)">
11888        <td style="padding:10px 14px;font-size:13px;color:var(--white);direction:ltr">${r.button_name}</td>
11889        <td style="padding:10px 14px;font-size:14px;font-weight:700;color:var(--accent);text-align:center">${r.unique_users}</td>
11890        <td style="padding:10px 14px;font-size:14px;font-weight:700;color:var(--white);text-align:center">${r.total_clicks}</td>
11891      </tr>`).join('');
11892    container.innerHTML = `
11893      <div style="background:var(--gray2);border:1px solid var(--gray3);border-radius:12px;overflow:hidden">
11894        <div style="padding:16px 20px;border-bottom:1px solid var(--gray3);font-family:'Syne',sans-serif;font-size:16px;font-weight:700">תפריט וכפתורים</div>
11895        <table style="width:100%;border-collapse:collapse">
11896          <thead>
11897            <tr style="background:var(--gray1);border-bottom:1px solid var(--gray3)">
11898              <th style="padding:10px 14px;text-align:right;font-size:12px;color:var(--gray5);font-weight:600">כפתור</th>
11899              <th style="padding:10px 14px;text-align:center;font-size:12px;color:var(--gray5);font-weight:600">משתמשים ייחודיים</th>
11900              <th style="padding:10px 14px;text-align:center;font-size:12px;color:var(--gray5);font-weight:600">סה"כ לחיצות</th>
11901            </tr>
11902          </thead>
11903          <tbody>${tableRows}</tbody>
11904        </table>
11905      </div>`;
11906  }).catch(() => {
11907    container.innerHTML = '<div style="color:#f87171;padding:20px">שגיאה בטעינת הנתונים.</div>';
11908  });
11909}
11910
11911// ── User Profiles ─────────────────────────────────────────────────────────────
11912function loadUserProfiles(offset) {
11913  offset = offset || 0;
11914  const el = document.getElementById('view-user-profiles');
11915  if (!el) return;
11916  if (!offset) el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
11917  fetch(`/api/admin/user-devices?limit=100&offset=${offset}`).then(r=>r.json()).then(res=>{
11918    const rows = res.data || [];
11919    const flag = c => c ? `<span title="${c}">${({IL:'🇮🇱',US:'🇺🇸',GB:'🇬🇧',DE:'🇩🇪',FR:'🇫🇷',UA:'🇺🇦',RU:'🇷🇺'}[c]||'🌍')}</span>` : '';
11920    const dt = d => d ? new Date(d).toLocaleDateString('he-IL',{day:'2-digit',month:'2-digit',year:'2-digit',hour:'2-digit',minute:'2-digit'}) : '—';
11921    const rows_html = rows.map(r => {
11922      const geo = r.geo || {};
11923      const ua  = r.ua_parsed || {};
11924      const loc = [geo.city, geo.country].filter(Boolean).join(', ') || '—';
11925      return `<tr style="cursor:pointer" onclick="loadUserProfile('${r.did}')">
11926        <td style="font-family:monospace;font-size:11px;color:var(--gray5)">${(r.did||'').slice(0,14)}…</td>
11927        <td>${flag(geo.country_code)} ${loc}</td>
11928        <td>${ua.browser||'—'} / ${ua.os||'—'} <span style="font-size:11px;color:var(--gray5)">(${ua.device_type||'?'})</span></td>
11929        <td style="text-align:center">${r.session_count||0}</td>
11930        <td style="text-align:center">${r.search_count||0}</td>
11931        <td style="text-align:center">${r.copy_count||0}</td>
11932        <td style="font-size:12px;color:var(--gray5)">${dt(r.last_seen_at)}</td>
11933        <td style="font-size:12px;color:var(--gray5)">${dt(r.first_seen_at)}</td>
11934      </tr>`;
11935    }).join('');
11936    const tbl = `
11937      <h2 style="margin:0 0 16px;font-size:18px">👤 פרופילי משתמשים <span style="font-size:13px;color:var(--gray5);font-weight:400">(${offset+rows.length} loaded)</span></h2>
11938      ${offset===0 ? '' : document.getElementById('view-user-profiles').querySelector('table')?.outerHTML?.split('<tbody')[0] || ''}
11939      <table style="width:100%;border-collapse:collapse;font-size:13px">
11940        <thead><tr style="color:var(--gray5);font-size:12px;text-align:right">
11941          <th style="padding:6px 10px">Device ID</th><th style="padding:6px 10px">מיקום</th><th style="padding:6px 10px">מכשיר</th>
11942          <th style="padding:6px 10px;text-align:center">סשנים</th><th style="padding:6px 10px;text-align:center">חיפושים</th>
11943          <th style="padding:6px 10px;text-align:center">העתקות</th><th style="padding:6px 10px">ביקור אחרון</th><th style="padding:6px 10px">ראשון</th>
11944        </tr></thead>
11945        <tbody>${rows_html}</tbody>
11946      </table>
11947      ${rows.length===100?`<button onclick="loadUserProfiles(${offset+100})" style="margin-top:12px;padding:8px 18px;background:var(--accent);border:none;border-radius:6px;color:#fff;cursor:pointer">טען עוד</button>`:''}
11948    `;
11949    el.innerHTML = tbl;
11950  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
11951}
11952
11953function loadUserProfile(did) {
11954  const el = document.getElementById('view-user-profiles');
11955  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען פרופיל...</div>';
11956  fetch(`/api/admin/user-devices/${encodeURIComponent(did)}`).then(r=>r.json()).then(d=>{
11957    const geo = d.geo||{}; const ua = d.ua_parsed||{}; const env = d.env||{}; const sc = d.screen||{}; const fp = d.fp||{};
11958    const dt = x => x ? new Date(x).toLocaleString('he-IL') : '—';
11959    const row = (label, val) => val ? `<tr><td style="color:var(--gray5);padding:4px 10px;width:160px">${label}</td><td style="padding:4px 10px">${val}</td></tr>` : '';
11960    el.innerHTML = `
11961      <button onclick="loadUserProfiles()" style="margin-bottom:16px;padding:6px 14px;background:var(--card-bg);border:1px solid var(--border);border-radius:6px;color:var(--white);cursor:pointer">← חזרה לרשימה</button>
11962      <h2 style="margin:0 0 16px;font-size:16px;font-family:monospace">${did}</h2>
11963      <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
11964        <div style="background:var(--card-bg);border-radius:10px;padding:16px">
11965          <div style="font-weight:600;margin-bottom:10px;color:var(--accent)">📍 מיקום ורשת</div>
11966          <table style="font-size:13px">
11967            ${row('IP', d.ip)} ${row('עיר', geo.city)} ${row('אזור', geo.region)} ${row('מדינה', geo.country)}
11968            ${row('ISP', geo.isp)} ${row('מובייל', geo.mobile?'✅':'❌')} ${row('VPN/Proxy', geo.proxy?'⚠️ כן':'לא')}
11969          </table>
11970        </div>
11971        <div style="background:var(--card-bg);border-radius:10px;padding:16px">
11972          <div style="font-weight:600;margin-bottom:10px;color:var(--accent)">💻 מכשיר</div>
11973          <table style="font-size:13px">
11974            ${row('דפדפן', ua.browser + (ua.browser_version?' '+ua.browser_version:''))}
11975            ${row('מערכת הפעלה', ua.os + (ua.os_version?' '+ua.os_version:''))}
11976            ${row('סוג', ua.device_type)} ${row('שפה', env.lang)} ${row('אזור זמן', env.tz)}
11977            ${row('מסך', sc.w&&sc.h?`${sc.w}×${sc.h} (viewport ${sc.vw}×${sc.vh})`:'—')}
11978            ${row('DPR', sc.dpr)} ${row('מגע', sc.touch>0?'✅':'❌')} ${row('RAM', env.mem?env.mem+'GB':'—')}
11979            ${row('CPU cores', env.cpu)} ${row('חיבור', env.conn)}
11980          </table>
11981        </div>
11982        <div style="background:var(--card-bg);border-radius:10px;padding:16px">
11983          <div style="font-weight:600;margin-bottom:10px;color:var(--accent)">📈 פעילות</div>
11984          <table style="font-size:13px">
11985            ${row('סשנים', d.session_count)} ${row('חיפושים', d.search_count)} ${row('העתקות', d.copy_count)}
11986            ${row('ביקור ראשון', dt(d.first_seen_at))} ${row('ביקור אחרון', dt(d.last_seen_at))}
11987          </table>
11988        </div>
11989        <div style="background:var(--card-bg);border-radius:10px;padding:16px">
11990          <div style="font-weight:600;margin-bottom:10px;color:var(--accent)">🚦 מקור</div>
11991          <table style="font-size:13px">
11992            ${row('Referrer', d.first_ref)} ${row('UTM source', (d.first_utm||{}).source)}
11993            ${row('UTM medium', (d.first_utm||{}).medium)} ${row('UTM campaign', (d.first_utm||{}).campaign)}
11994          </table>
11995        </div>
11996        ${fp.canvas||fp.webgl?`<div style="background:var(--card-bg);border-radius:10px;padding:16px;grid-column:span 2">
11997          <div style="font-weight:600;margin-bottom:10px;color:var(--accent)">🔬 Fingerprint</div>
11998          <table style="font-size:12px;font-family:monospace">
11999            ${row('Canvas hash', fp.canvas)} ${row('WebGL renderer', (fp.webgl||{}).renderer)} ${row('WebGL vendor', (fp.webgl||{}).vendor)}
12000          </table>
12001        </div>`:''}
12002      </div>
12003      <div style="margin-top:12px;background:var(--card-bg);border-radius:10px;padding:14px">
12004        <div style="font-size:11px;color:var(--gray5);word-break:break-all">${d.ua||''}</div>
12005      </div>
12006    `;
12007  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12008}
12009
12010// ── Search Intelligence ────────────────────────────────────────────────────────
12011function loadSearchIntel() {
12012  const el = document.getElementById('view-search-intel');
12013  if (!el) return;
12014  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
12015  fetch('/api/admin/search-intelligence').then(r=>r.json()).then(data=>{
12016    const top = data.top || []; const total = data.total || 0;
12017    const topHtml = top.slice(0,50).map(r=>`
12018      <tr>
12019        <td style="padding:5px 10px">${r.q}</td>
12020        <td style="padding:5px 10px;text-align:center;font-weight:600">${r.count}</td>
12021        <td style="padding:5px 10px;text-align:center">${r.avg_results}</td>
12022        <td style="padding:5px 10px;text-align:center">${r.conversion_pct}%</td>
12023      </tr>`).join('');
12024    el.innerHTML = `
12025      <h2 style="margin:0 0 16px;font-size:18px">🔍 ניתוח חיפושים <span style="font-size:13px;color:var(--gray5);font-weight:400">(${total} סה"כ)</span></h2>
12026      <table style="width:100%;border-collapse:collapse;font-size:13px;margin-bottom:32px">
12027        <thead><tr style="color:var(--gray5);font-size:12px;text-align:right">
12028          <th style="padding:6px 10px">שאילתה</th><th style="padding:6px 10px;text-align:center">פעמים</th>
12029          <th style="padding:6px 10px;text-align:center">ממוצע תוצאות</th><th style="padding:6px 10px;text-align:center">המרה</th>
12030        </tr></thead>
12031        <tbody>${topHtml}</tbody>
12032      </table>
12033    `;
12034  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12035}
12036
12037// ── Demand Gaps ────────────────────────────────────────────────────────────────
12038function loadDemandGaps() {
12039  const el = document.getElementById('view-demand-gaps');
12040  if (!el) return;
12041  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
12042  fetch('/api/admin/search-intelligence').then(r=>r.json()).then(data=>{
12043    const failed = data.failed || [];
12044    if (!failed.length) { el.innerHTML='<div style="padding:40px;text-align:center;color:var(--gray5)">אין חיפושים ללא תוצאות 🎉</div>'; return; }
12045    const html = failed.map(r=>`
12046      <tr>
12047        <td style="padding:8px 12px;font-weight:500">${r.q}</td>
12048        <td style="padding:8px 12px;text-align:center;color:#f87171;font-weight:700">${r.count}×</td>
12049        <td style="padding:8px 12px;text-align:center">
12050          <button onclick="document.querySelector('[onclick*=coupons]')&&document.querySelector('[onclick*=coupons]').click();document.getElementById('search-input')&&(document.getElementById('search-input').value='${r.q.replace(/'/,"\\'")}');searchCoupons&&searchCoupons()" style="padding:4px 10px;background:var(--accent);border:none;border-radius:5px;color:#fff;cursor:pointer;font-size:12px">הוסף קופון</button>
12051        </td>
12052      </tr>`).join('');
12053    el.innerHTML = `
12054      <h2 style="margin:0 0 8px;font-size:18px">🕳️ ביקוש ללא קופונים</h2>
12055      <p style="color:var(--gray5);margin:0 0 20px;font-size:13px">חיפושים שחוזרים על עצמם ולא מחזירים תוצאות — פוטנציאל להוסיף חנויות חדשות</p>
12056      <table style="width:100%;border-collapse:collapse;font-size:13px">
12057        <thead><tr style="color:var(--gray5);font-size:12px;text-align:right">
12058          <th style="padding:6px 12px">חיפוש שנכשל</th><th style="padding:6px 12px;text-align:center">כמה פעמים</th><th style="padding:6px 12px;text-align:center">פעולה</th>
12059        </tr></thead>
12060        <tbody>${html}</tbody>
12061      </table>`;
12062  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12063}
12064
12065// ── Traffic Sources ────────────────────────────────────────────────────────────
12066function loadTrafficSources() {
12067  const el = document.getElementById('view-traffic-sources');
12068  if (!el) return;
12069  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
12070  fetch('/api/admin/traffic-sources').then(r=>r.json()).then(res=>{
12071    const rows = res.data || [];
12072    const total = rows.reduce((s,r)=>s+r.sessions,0)||1;
12073    const colors = ['#3b82f6','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899','#14b8a6','#f97316'];
12074    const html = rows.map((r,i)=>{
12075      const pct = Math.round(r.sessions/total*100);
12076      return `<div style="margin-bottom:10px">
12077        <div style="display:flex;justify-content:space-between;margin-bottom:3px">
12078          <span style="font-size:13px">${r.source}</span>
12079          <span style="font-size:13px;color:var(--gray5)">${r.sessions} (${pct}%)</span>
12080        </div>
12081        <div style="background:var(--border);border-radius:4px;height:8px">
12082          <div style="background:${colors[i%colors.length]};height:8px;border-radius:4px;width:${pct}%"></div>
12083        </div>
12084      </div>`;
12085    }).join('');
12086    el.innerHTML = `<h2 style="margin:0 0 20px;font-size:18px">🚦 מקורות תנועה</h2><div style="max-width:600px">${html||'<div style="color:var(--gray5)">אין נתונים עדיין</div>'}</div>`;
12087  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12088}
12089
12090// ── Geo Map ────────────────────────────────────────────────────────────────────
12091function loadGeoMap() {
12092  const el = document.getElementById('view-geo-map');
12093  if (!el) return;
12094  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
12095  fetch('/api/admin/geo').then(r=>r.json()).then(res=>{
12096    const rows = res.data || [];
12097    if (!rows.length) { el.innerHTML='<div style="padding:40px;text-align:center;color:var(--gray5)">אין נתוני מיקום עדיין</div>'; return; }
12098    const flag = c => ({IL:'🇮🇱',US:'🇺🇸',GB:'🇬🇧',DE:'🇩🇪',FR:'🇫🇷',UA:'🇺🇦',RU:'🇷🇺',TR:'🇹🇷',AU:'🇦🇺',CA:'🇨🇦'}[c]||'🌍');
12099    const html = rows.map(r=>`
12100      <tr>
12101        <td style="padding:6px 12px">${flag(r.country_code)} ${r.city||'—'}</td>
12102        <td style="padding:6px 12px">${r.country||'—'}</td>
12103        <td style="padding:6px 12px;text-align:center;font-weight:600">${r.devices}</td>
12104        <td style="padding:6px 12px;text-align:center">${r.sessions}</td>
12105      </tr>`).join('');
12106    el.innerHTML = `
12107      <h2 style="margin:0 0 20px;font-size:18px">🗺️ גיאוגרפיה</h2>
12108      <table style="width:100%;border-collapse:collapse;font-size:13px">
12109        <thead><tr style="color:var(--gray5);font-size:12px;text-align:right">
12110          <th style="padding:6px 12px">עיר</th><th style="padding:6px 12px">מדינה</th>
12111          <th style="padding:6px 12px;text-align:center">מכשירים</th><th style="padding:6px 12px;text-align:center">סשנים</th>
12112        </tr></thead>
12113        <tbody>${html}</tbody>
12114      </table>`;
12115  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12116}
12117
12118// ── Device Stats ───────────────────────────────────────────────────────────────
12119function loadDeviceStats() {
12120  const el = document.getElementById('view-device-stats');
12121  if (!el) return;
12122  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
12123  fetch('/api/admin/device-stats').then(r=>r.json()).then(data=>{
12124    const section = (title, rows) => {
12125      if (!rows.length) return '';
12126      const total = rows.reduce((s,r)=>s+r.sessions,0)||1;
12127      const colors = ['#3b82f6','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899'];
12128      return `<div style="background:var(--card-bg);border-radius:10px;padding:16px;min-width:220px">
12129        <div style="font-weight:600;margin-bottom:12px;color:var(--accent)">${title}</div>
12130        ${rows.slice(0,8).map((r,i)=>{
12131          const pct=Math.round(r.sessions/total*100);
12132          return `<div style="margin-bottom:8px">
12133            <div style="display:flex;justify-content:space-between;margin-bottom:2px;font-size:12px">
12134              <span>${r.name}</span><span style="color:var(--gray5)">${r.sessions} (${pct}%)</span>
12135            </div>
12136            <div style="background:var(--border);border-radius:3px;height:6px">
12137              <div style="background:${colors[i%colors.length]};height:6px;border-radius:3px;width:${pct}%"></div>
12138            </div>
12139          </div>`;
12140        }).join('')}
12141      </div>`;
12142    };
12143    el.innerHTML = `
12144      <h2 style="margin:0 0 20px;font-size:18px">💻 מכשירים ודפדפנים</h2>
12145      <div style="display:flex;flex-wrap:wrap;gap:16px">
12146        ${section('📱 סוג מכשיר', data.devices||[])}
12147        ${section('🌐 דפדפן', data.browsers||[])}
12148        ${section('🖥️ מערכת הפעלה', data.os||[])}
12149      </div>`;
12150  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12151}
12152
12153// ── JS Errors ─────────────────────────────────────────────────────────────────
12154function loadJsErrors() {
12155  const el = document.getElementById('view-js-errors');
12156  if (!el) return;
12157  el.innerHTML = '<div style="color:var(--gray5);padding:20px">טוען...</div>';
12158  fetch('/api/admin/js-errors').then(r=>r.json()).then(res=>{
12159    const rows = res.data || [];
12160    if (!rows.length) { el.innerHTML='<div style="padding:40px;text-align:center;color:#4ade80">✅ אין שגיאות JS</div>'; return; }
12161    const html = rows.map(r=>`
12162      <div style="background:var(--card-bg);border-radius:8px;padding:12px;margin-bottom:10px;border-left:3px solid #ef4444">
12163        <div style="display:flex;justify-content:space-between;margin-bottom:6px">
12164          <span style="color:#f87171;font-weight:500;font-size:13px">${(r.message||'').slice(0,120)}</span>
12165          <span style="font-size:11px;color:var(--gray5)">${r.ts?new Date(r.ts).toLocaleString('he-IL'):''}</span>
12166        </div>
12167        <div style="font-size:11px;color:var(--gray5);margin-bottom:4px">${r.source||''} ${r.lineno?':'+r.lineno:''}</div>
12168        ${r.stack?`<pre style="font-size:10px;color:var(--gray5);white-space:pre-wrap;margin:4px 0 0;max-height:80px;overflow:hidden">${r.stack.slice(0,400)}</pre>`:''}
12169        <div style="font-size:10px;color:var(--gray5);margin-top:4px">${r.ua?r.ua.slice(0,80):''} ${r.ip?'· '+r.ip:''}</div>
12170      </div>`).join('');
12171    el.innerHTML = `<h2 style="margin:0 0 16px;font-size:18px">🐛 שגיאות JS <span style="font-size:13px;color:var(--gray5);font-weight:400">(${rows.length})</span></h2>${html}`;
12172  }).catch(()=>{ el.innerHTML='<div style="color:#f87171;padding:20px">שגיאה.</div>'; });
12173}
12174
12175// ── Pipeline Debugger ──────────────────────────────────────────────────────────
12176let _debugES = null;  // active EventSource
12177
12178function initDebugView() {
12179  // focus input when switching to view
12180  setTimeout(() => {
12181    const inp = document.getElementById('debug-query-input');
12182    if (inp) inp.focus();
12183  }, 50);
12184}
12185
12186const _DEBUG_COLORS = {
12187  '[DISCOVER]':      '#7dd3fc',  // sky
12188  '[NORMALIZE]':     '#94a3b8',  // slate
12189  '[QUERY-EXPAND]':  '#7dd3fc',
12190  '[DDG]':           '#7dd3fc',
12191  '[INFLUENCER-DDG]':'#7dd3fc',
12192  '[URL-BREAKDOWN]': '#38bdf8',  // bright sky
12193  '[FETCH ALL]':     '#38bdf8',
12194  '[FETCH OK]':      '#4ade80',  // green
12195  '[FETCH FAIL]':    '#f87171',  // red
12196  '[FETCH]':         '#64748b',  // dim
12197  '[EXTRACT ALL]':   '#38bdf8',
12198  '[HAIKU →]':       '#fbbf24',  // amber
12199  '[HAIKU ←]':       '#4ade80',
12200  '[FIX-SPLIT]':     '#fbbf24',
12201  '[JUNK]':          '#f87171',
12202  '[FILTER]':        '#fbbf24',
12203  '[SANITIZE-NOTES]':'#fbbf24',
12204  '[TRANSLATE]':     '#7dd3fc',
12205  '[COUPON]':        '#00e87a',  // accent green bold
12206  '[EXTRACT DONE]':  '#4ade80',
12207  '[SAVED]':         '#00e87a',
12208  '[DUPLICATE]':     '#64748b',
12209  'WARNING':         '#fbbf24',
12210  'ERROR':           '#f87171',
12211};
12212
12213function _debugColorLine(raw) {
12214  // Extract just the message portion (after timestamp + level + module)
12215  // Format: "HH:MM:SS LEVEL    module  message"
12216  const parts = raw.match(/^(\d{2}:\d{2}:\d{2})\s+(\w+)\s+\S+\s+(.*)/s);
12217  const time    = parts ? `<span style="color:#475569">${parts[1]}</span>` : '';
12218  const level   = parts ? parts[2] : '';
12219  const msg     = parts ? parts[3] : raw;
12220
12221  let levelColor = '#64748b';
12222  if (level === 'INFO')    levelColor = '#7dd3fc';
12223  if (level === 'WARNING') levelColor = '#fbbf24';
12224  if (level === 'ERROR')   levelColor = '#f87171';
12225  if (level === 'DEBUG')   levelColor = '#475569';
12226
12227  const levelSpan = parts ? `<span style="color:${levelColor};min-width:52px;display:inline-block">${level}</span>` : '';
12228
12229  // Colorize known markers inside the message
12230  let colored = msg;
12231  for (const [marker, color] of Object.entries(_DEBUG_COLORS)) {
12232    if (colored.includes(marker)) {
12233      const isBold = ['[COUPON]','[SAVED]','[URL-BREAKDOWN]','[FETCH ALL]','[EXTRACT ALL]'].includes(marker);
12234      const style = isBold ? `font-weight:700;color:${color}` : `color:${color}`;
12235      colored = colored.replaceAll(marker, `<span style="${style}">${marker}</span>`);
12236      break;
12237    }
12238  }
12239
12240  return `${time} ${levelSpan} ${colored}`;
12241}
12242
12243function _debugAppend(html) {
12244  const term = document.getElementById('debug-terminal');
12245  if (!term) return;
12246  const line = document.createElement('div');
12247  line.innerHTML = html;
12248  term.appendChild(line);
12249  term.scrollTop = term.scrollHeight;
12250}
12251
12252function debugClear() {
12253  if (_debugES) { _debugES.close(); _debugES = null; }
12254  const term = document.getElementById('debug-terminal');
12255  if (term) term.innerHTML = '<span style="color:#555">// לחץ הרץ כדי לראות את ה-pipeline בפעולה</span>';
12256  const res = document.getElementById('debug-result');
12257  if (res) res.style.display = 'none';
12258  const stats = document.getElementById('debug-stats');
12259  if (stats) stats.innerHTML = '';
12260  const sources = document.getElementById('debug-sources');
12261  if (sources) sources.innerHTML = '';
12262  const tbody = document.getElementById('debug-table-body');
12263  if (tbody) tbody.innerHTML = '';
12264  const st = document.getElementById('debug-status');
12265  if (st) st.textContent = '';
12266  const btn = document.getElementById('debug-run-btn');
12267  if (btn) { btn.textContent = '▶ הרץ'; btn.disabled = false; }
12268}
12269
12270function debugRun() {
12271  const query = (document.getElementById('debug-query-input')?.value || '').trim();
12272  if (!query) return;
12273
12274  // Close any existing stream
12275  if (_debugES) { _debugES.close(); _debugES = null; }
12276
12277  // Clear terminal
12278  const term = document.getElementById('debug-terminal');
12279  if (term) term.innerHTML = '';
12280  const res = document.getElementById('debug-result');
12281  if (res) res.style.display = 'none';
12282
12283  const btn = document.getElementById('debug-run-btn');
12284  const st  = document.getElementById('debug-status');
12285  if (btn) { btn.textContent = '⏳ רץ...'; btn.disabled = true; }
12286  if (st)  st.textContent = 'מתחבר...';
12287
12288  _debugAppend(`<span style="color:#00e87a;font-weight:700">▶ מריץ discover("${query}")</span>`);
12289  _debugAppend('');
12290
12291  _debugES = new EventSource(`/api/admin/debug/run?query=${encodeURIComponent(query)}`);
12292
12293  _debugES.onmessage = (e) => {
12294    let payload;
12295    try { payload = JSON.parse(e.data); } catch { return; }
12296
12297    if (payload.type === 'log') {
12298      _debugAppend(_debugColorLine(payload.line));
12299    } else if (payload.type === 'done') {
12300      _debugAppend('');
12301      _debugAppend(`<span style="color:#00e87a;font-weight:700">✓ סיים — ${payload.found} קופונים מ-${payload.urls} עמודים</span>`);
12302      _showDebugResult(payload.coupons || [], payload.urls);
12303      if (btn) { btn.textContent = '▶ הרץ'; btn.disabled = false; }
12304      if (st)  st.textContent = `✓ ${payload.found} קופונים`;
12305      _debugES.close(); _debugES = null;
12306    } else if (payload.type === 'error') {
12307      _debugAppend(`<span style="color:#f87171">✗ שגיאה: ${payload.message}</span>`);
12308      if (btn) { btn.textContent = '▶ הרץ'; btn.disabled = false; }
12309      if (st)  st.textContent = '✗ שגיאה';
12310      _debugES.close(); _debugES = null;
12311    } else if (payload.type === 'end') {
12312      _debugES.close(); _debugES = null;
12313    }
12314  };
12315
12316  _debugES.onerror = () => {
12317    if (_debugES?.readyState === EventSource.CLOSED) return;
12318    _debugAppend('<span style="color:#f87171">✗ חיבור נותק</span>');
12319    if (btn) { btn.textContent = '▶ הרץ'; btn.disabled = false; }
12320    if (st)  st.textContent = '';
12321    _debugES?.close(); _debugES = null;
12322  };
12323}
12324
12325function _showDebugResult(coupons, urlsSearched) {
12326  const res = document.getElementById('debug-result');
12327  if (!res) return;
12328  res.style.display = 'block';
12329
12330  // ── Compute stats ──────────────────────────────────────────────────────────
12331  const total     = coupons.length;
12332  const realCodes = coupons.filter(c => c.code && c.code !== 'NO_CODE').length;
12333  const noCodes   = coupons.filter(c => !c.code || c.code === 'NO_CODE').length;
12334  const sourceDomains = {};
12335  coupons.forEach(c => {
12336    const m = (c.source || '').match(/https?:\/\/([^\/]+)/);
12337    if (m) { const d = m[1].replace(/^www\./, ''); sourceDomains[d] = (sourceDomains[d] || 0) + 1; }
12338  });
12339
12340  // ── Stats chips ───────────────────────────────────────────────────────────
12341  const statsEl = document.getElementById('debug-stats');
12342  const chip = (icon, label, val, color) =>
12343    `<div style="display:flex;align-items:center;gap:8px;background:var(--gray2);border:1px solid var(--gray3);border-radius:10px;padding:8px 14px;min-width:100px">
12344      <span style="font-size:18px">${icon}</span>
12345      <div>
12346        <div style="font-size:18px;font-weight:700;color:${color};line-height:1.1">${val}</div>
12347        <div style="font-size:10px;color:var(--gray5);margin-top:1px">${label}</div>
12348      </div>
12349    </div>`;
12350  statsEl.innerHTML =
12351    chip('📦', 'סה״כ קופונים',   total,             '#fff')    +
12352    chip('🔑', 'קודים אמיתיים',  realCodes,         '#00e87a') +
12353    chip('🔗', 'NO_CODE (דילים)', noCodes,           '#7dd3fc') +
12354    chip('🌐', 'עמודים נסרקו',   urlsSearched||'—', '#94a3b8');
12355
12356  // ── Source domain bar chart ───────────────────────────────────────────────
12357  const sourcesEl = document.getElementById('debug-sources');
12358  const sorted = Object.entries(sourceDomains).sort((a,b)=>b[1]-a[1]).slice(0,12);
12359  if (sorted.length) {
12360    const mx = sorted[0][1];
12361    sourcesEl.innerHTML = `
12362      <div style="font-size:11px;font-weight:600;color:var(--gray5);letter-spacing:1px;text-transform:uppercase;margin-bottom:8px">פילוח לפי מקור</div>
12363      <div style="display:flex;flex-direction:column;gap:4px">
12364        ${sorted.map(([dom,cnt])=>{
12365          const pct = Math.round(cnt/mx*100);
12366          const col = dom==='t.me'?'#7dd3fc':'#00e87a';
12367          return `<div style="display:flex;align-items:center;gap:10px;font-size:12px">
12368            <span style="color:var(--gray5);
12368min-width:200px;text-align:right;direction:ltr">${dom}</span>
12369            <div style="flex:1;background:var(--gray3);border-radius:4px;height:6px;overflow:hidden;max-width:220px">
12370              <div style="width:${pct}%;height:100%;background:${col};border-radius:4px"></div>
12371            </div>
12372            <span style="color:var(--white);font-weight:600;min-width:20px">${cnt}</span>
12373          </div>`;
12374        }).join('')}
12375      </div>`;
12376  }
12377
12378  // ── Before / After comparison table ──────────────────────────────────────
12379  const tableWrap = document.getElementById('debug-table-wrap');
12380  if (tableWrap) tableWrap.innerHTML = `
12381    <div style="padding:12px 16px;border-bottom:1px solid var(--gray3);display:flex;align-items:center;gap:16px;flex-wrap:wrap">
12382      <span style="font-size:12px;font-weight:600;color:var(--gray5)">קופונים שנמצאו — Haiku vs Python</span>
12383      <span style="display:flex;align-items:center;gap:6px;font-size:11px;color:#fbbf24">
12384        <span style="width:10px;height:10px;background:#fbbf2433;border:1px solid #fbbf24;border-radius:2px;display:inline-block"></span>Haiku raw
12385      </span>
12386      <span style="display:flex;align-items:center;gap:6px;font-size:11px;color:#00e87a">
12387        <span style="width:10px;height:10px;background:#00e87a22;border:1px solid #00e87a;border-radius:2px;display:inline-block"></span>Python processed
12388      </span>
12389    </div>
12390    <div style="overflow-x:auto">
12391      <table style="width:100%;border-collapse:collapse;font-size:12px">
12392        <thead>
12393          <tr style="background:var(--gray1)">
12394            <th style="padding:7px 12px;text-align:center;color:var(--gray5);font-weight:500;width:24px">#</th>
12395            <th style="padding:7px 12px;text-align:right;color:var(--gray5);font-weight:500">מקור</th>
12396            <th style="padding:7px 12px;text-align:right;color:var(--gray5);font-weight:500">חנות</th>
12397            <th style="padding:7px 12px;text-align:right;color:var(--gray5);font-weight:500">הנחה</th>
12398            <th style="padding:7px 12px;text-align:right;color:var(--gray5);font-weight:500">הערות</th>
12399            <th style="padding:7px 12px;text-align:right;color:var(--gray5);font-weight:500">אתר יעד</th>
12400          </tr>
12401        </thead>
12402        <tbody id="debug-table-body"></tbody>
12403      </table>
12404    </div>`;
12405
12406  const tbody = document.getElementById('debug-table-body');
12407  if (!tbody) return;
12408
12409  const _esc = s => (s||'').replace(/</g,'&lt;').replace(/>/g,'&gt;');
12410  const _dom = url => { const m=(url||'').match(/https?:\/\/([^\/]+)/); return m?m[1].replace(/^www\./,''):''; };
12411  const _short = (s, n=36) => s && s.length>n ? s.slice(0,n-1)+'…' : (s||'');
12412
12413  // Cell: show haiku value (amber row) and python value (green row) stacked
12414  // If identical → show once in neutral style
12415  const _cell = (raw, final, mono=false) => {
12416    const rStr = _esc(_short(raw,  60));
12417    const fStr = _esc(_short(final,60));
12418    const mStyle = mono ? 'font-family:monospace;' : '';
12419    if (!rStr && !fStr) return `<td style="padding:6px 12px;color:#333">—</td>`;
12420    if (rStr === fStr)
12421      return `<td style="padding:6px 12px;color:var(--gray6);${mStyle}">${fStr||'—'}</td>`;
12422    return `<td style="padding:0 12px">
12423      <div style="padding:4px 0 3px;color:#fbbf24;${mStyle}opacity:.85">${rStr||'<em style="opacity:.4">null</em>'}</div>
12424      <div style="padding:3px 0 4px;color:#00e87a;${mStyle}">${fStr||'<em style="opacity:.4">null</em>'}</div>
12425    </td>`;
12426  };
12427
12428  tbody.innerHTML = coupons.map((c, i) => {
12429    const raw    = c._raw || {};
12430    const srcDom = _dom(c.source);
12431    const srcIcon = srcDom.includes('t.me') ? '📱' : '🌐';
12432    const rowBg  = i%2===0 ? 'transparent' : 'rgba(255,255,255,.018)';
12433
12434    // Detect if any field changed (to highlight the row number)
12435    const changed = raw.store     !== c.store
12436                 || raw.discount  !== c.discount
12437                 || raw.notes     !== c.notes
12438;
12439    const numColor = changed ? '#fbbf24' : 'var(--gray5)';
12440
12441    return `<tr style="background:${rowBg};border-top:1px solid var(--gray3)">
12442      <td style="padding:6px 12px;text-align:center;color:${numColor};font-size:11px;font-weight:600">${i+1}</td>
12443      <td style="padding:6px 12px;direction:ltr;white-space:nowrap">
12444        <span style="font-size:11px;color:var(--gray5)">${srcIcon} ${_esc(_short(srcDom,30))}</span>
12445      </td>
12446      ${_cell(raw.store,    c.store)}
12447      ${_cell(raw.discount, c.discount)}
12448      ${_cell(raw.notes,    c.notes)}
12449    </tr>`;
12450  }).join('');
12451}
12452</script>
12452
12453<!-- Date range picker popup -->
12454<div id="date-picker-popup" style="display:none;position:fixed;z-index:9999;background:var(--gray2);border:1px solid var(--gray4);border-radius:14px;padding:16px 20px;box-shadow:0 8px 32px rgba(0,0,0,0.6);min-width:240px;direction:rtl">
12455  <div style="font-size:13px;font-weight:600;color:var(--white);margin-bottom:12px">📅 טווח תאריכים</div>
12456  <div style="display:grid;gap:10px">
12457    <div>
12458      <div style="font-size:11px;color:var(--gray5);margin-bottom:4px">מתאריך</div>
12459      <input type="date" id="dp-from" style="width:100%;box-sizing:border-box;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:7px 10px;color:var(--white);font-family:'DM Sans',sans-serif;font-size:13px;outline:none">
12460    </div>
12461    <div>
12462      <div style="font-size:11px;color:var(--gray5);margin-bottom:4px">עד תאריך</div>
12463      <input type="date" id="dp-to" style="width:100%;box-sizing:border-box;background:var(--gray3);border:1px solid var(--gray4);border-radius:8px;padding:7px 10px;color:var(--white);font-family:'DM Sans',sans-serif;font-size:13px;outline:none">
12464    </div>
12465  </div>
12466  <div style="display:flex;gap:8px;margin-top:14px">
12467    <button onclick="_applyDatePicker()" style="flex:1;background:var(--accent);color:#000;border:none;border-radius:8px;padding:9px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px">החל</button>
12468    <button onclick="_closeDatePicker()" style="flex:1;background:var(--gray3);color:var(--gray5);border:none;border-radius:8px;padding:9px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px">ביטול</button>
12469  </div>
12470</div>
12471<!-- ── Reveal modal ─────────────────────────────────────────────────────── -->
12472<div id="reveal-modal-overlay" onclick="if(event.target===this)closeRevealModal()">
12473  <div id="reveal-modal">
12474    <div id="rm-header">
12475      <button id="rm-close" onclick="closeRevealModal()">×</button>
12476      <div id="rm-header-title">קופון הנחה</div>
12477    </div>
12478    <div id="rm-body">
12479      <div id="rm-logo"></div>
12480      <div id="rm-discount"></div>
12481      <div id="rm-newsletter"></div>
12482      <div id="rm-notes"></div>
12483      <div id="rm-code-wrap">
12484        <div class="cc-code-row">
12485          <button class="cc-copy" onclick="rmCopyCode()" title="העתק קוד">
12486            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
12487          </button>
12488          <div class="cc-code" style="cursor:pointer;user-select:none" onclick="rmCopyCode()">
12489            <div class="cc-code-revealed visible" id="rm-code-val" style="user-select:none;cursor:pointer"></div>
12490          </div>
12491        </div>
12492      </div>
12493      <div id="rm-go-btn-wrap">
12494        <button id="rm-go-btn" onclick="rmGoToStore()">הקוד הועתק, השתמש בחנות 🛍️</button>
12495      </div>
12496      <div id="rm-vote-label">האם הקופון עובד?</div>
12497      <div id="rm-vote-row">
12498        <button id="rm-vote-down" class="vote-btn vote-down" onclick="rmVote(-1)">
12499          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 15v4a3 3 0 003 3l4-9V2H5.72a2 2 0 00-2 1.7l-1.38 9a2 2 0 002 2.3H10z"/><path d="M17 2h2.67A2.31 2.31 0 0122 4v7a2.31 2.31 0 01-2.33 2H17"/></svg>
12500          <span id="rm-vote-down-count">0</span>
12501        </button>
12502        <button id="rm-vote-up" class="vote-btn vote-up" onclick="rmVote(1)">
12503          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9V5a3 3 0 00-3-3l-4 9v11h11.28a2 2 0 002-1.7l1.38-9a2 2 0 00-2-2.3H14z"/><path d="M7 22H4a2 2 0 01-2-2v-7a2 2 0 012-2h3"/></svg>
12504          <span id="rm-vote-up-count">0</span>
12505        </button>
12506      </div>
12507    </div>
12508    <div id="rm-footer">
12509      <span id="rm-views">טוען...</span>
12510      <a id="rm-more-link" href="#" onclick="trackEvent('more_coupons',_rmStore);_syncAnalyticsToServer(true);closeRevealModal()">← עוד קופונים של <span id="rm-more-store"></span></a>
12511    </div>
12512    <div id="rm-toast">הקוד הועתק ✓</div>
12513  </div>
12514</div>
12515
12516<script>
12517// Auto-open admin panel when accessed via /admin URL
12518if (window.location.pathname === '/admin') {
12519  window.addEventListener('DOMContentLoaded', () => showPage('admin'));
12520}
12521
12522// ── Pre-prod banner ──────────────────────────────────────────────────────────
12523// Fetches /api/env — if ENVIRONMENT=preprod, shows yellow top banner.
12524// On production this is instant and the banner stays hidden.
12525(async () => {
12526  try {
12527    const r = await fetch('/api/env');
12528    const d = await r.json();
12529    if (d.environment === 'preprod') {
12530      const banner = document.getElementById('preprod-banner');
12531      if (banner) {
12532        banner.style.display = 'block';
12533        document.body.style.paddingTop = '34px';
12534      }
12535      // Show preprod-only nav + footer items (extension, about)
12536      document.querySelectorAll('[data-preprod-only="true"]').forEach(el => {
12537        el.style.display = 'inline';
12538      });
12539    }
12540  } catch (_) {} // fail silently — production site must never break
12541})();
12542</script>
12542
12543
12544<!-- Result modal -->
12545<div id="result-modal" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.6);align-items:center;justify-content:center" onclick="if(event.target===this)_closeResultModal()">
12546  <div style="background:#1a1a1a;border:1px solid var(--gray3);border-radius:14px;min-width:320px;max-width:480px;padding:28px 28px 20px;position:relative;direction:rtl">
12547    <button onclick="_closeResultModal()" style="position:absolute;top:12px;left:14px;background:none;border:none;color:var(--gray5);font-size:18px;cursor:pointer;line-height:1">✕</button>
12548    <div id="result-modal-icon" style="font-size:28px;margin-bottom:10px"></div>
12549    <div id="result-modal-title" style="font-size:16px;font-weight:700;color:var(--white);margin-bottom:8px"></div>
12550    <div id="result-modal-body" style="font-size:13px;color:var(--gray6);line-height:1.6;white-space:pre-wrap"></div>
12551    <div style="margin-top:20px;text-align:left">
12552      <button onclick="_closeResultModal()" style="background:var(--accent);color:#000;border:none;border-radius:8px;padding:8px 22px;font-size:13px;font-weight:600;cursor:pointer">אישור</button>
12553    </div>
12554  </div>
12555</div>
12556
12557<!-- Store Coupons Modal (אין דומיין → הצג קופונים) -->
12558<div id="nd-coupons-modal" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.75);align-items:flex-start;justify-content:center;padding-top:48px;overflow-y:auto" onclick="if(event.target===this)_ndCloseCouponsModal()">
12559  <div style="background:#1a1a1a;border:1px solid var(--gray3);border-radius:14px;width:min(720px,96vw);padding:0;position:relative;direction:rtl;margin-bottom:48px">
12560
12561    <!-- Header -->
12562    <div style="padding:22px 24px 16px;border-bottom:1px solid var(--gray3);display:flex;align-items:center;justify-content:space-between">
12563      <div>
12564        <div id="ndcm-title" style="font-size:17px;font-weight:700;color:var(--white)"></div>
12565        <div id="ndcm-subtitle" style="font-size:12px;color:var(--gray5);margin-top:3px"></div>
12566      </div>
12567      <button onclick="_ndCloseCouponsModal()" style="background:none;border:none;color:var(--gray5);font-size:20px;cursor:pointer;line-height:1;padding:4px 8px">✕</button>
12568    </div>
12569
12570    <!-- Coupons list -->
12571    <div id="ndcm-coupons" style="padding:16px 24px;max-height:340px;overflow-y:auto"></div>
12572
12573    <!-- Assign to store section -->
12574    <div style="padding:16px 24px 20px;border-top:1px solid var(--gray3);background:#111;border-radius:0 0 14px 14px">
12575      <div style="font-size:12px;color:var(--gray5);margin-bottom:10px;font-weight:600">העבר קופונים לחנות קיימת ומחק חנות זו:</div>
12576      <div style="display:flex;gap:8px;align-items:stretch">
12577        <div style="flex:1;position:relative">
12578          <input id="ndcm-search" type="text" placeholder="חפש חנות יעד..." dir="rtl" oninput="_ndcmFilter()"
12579            style="width:100%;background:var(--gray2);border:1px solid var(--gray3);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--white);outline:none;box-sizing:border-box">
12580          <div id="ndcm-dropdown" style="display:none;position:absolute;top:100%;right:0;left:0;background:#1a1a1a;border:1px solid var(--gray3);border-radius:0 0 8px 8px;max-height:180px;overflow-y:auto;z-index:10"></div>
12581        </div>
12582        <button id="ndcm-confirm-btn" onclick="_ndcmConfirmMove()" disabled
12583          style="padding:8px 18px;background:#7f1d1d;color:#fca5a5;border:1px solid #991b1b;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;opacity:0.45">
12584          העבר ומחק ⟵
12585        </button>
12586      </div>
12587      <div id="ndcm-target-preview" style="margin-top:8px;font-size:12px;color:var(--gray5);min-height:16px"></div>
12588    </div>
12589
12590  </div>
12591</div>
12592
12593<script src="/accessibility-widget.js"></script>
vendor: 1 bytes, line 12593
12593
12594<script src="https://website-widgets.pages.dev/dist/sienna.min.js" defer></script>
12594
12595
12596<!-- Cookie Consent Banner -->
12597<style>
12598#cookie-banner{display:none;position:fixed;bottom:0;right:0;left:0;z-index:99999;background:#1a1a2e;border-top:1px solid #2d2d4e;padding:16px 24px;flex-direction:row;align-items:center;justify-content:center;gap:16px;font-family:inherit;direction:rtl;text-align:center}
12599@media(max-width:600px){#cookie-banner{flex-direction:column}}
12600</style>
12601<div id="cookie-banner">
12602  <p style="margin:0;font-size:13px;color:#c8c8d8;">
12603    האתר משתמש בעוגיות (Cookies) לצורך פרסום ואנליטיקס (Google AdSense ו-Google Analytics).
12604    <a href="/privacy.html" style="color:#7c8cf8;text-decoration:underline;margin-right:4px">מדיניות פרטיות</a>
12605  </p>
12606  <div style="display:flex;gap:10px;flex-shrink:0;justify-content:center">
12607    <button onclick="setCookieConsent('denied')" style="padding:8px 18px;border-radius:8px;border:1px solid #3d3d5e;background:transparent;color:#a0a0b8;font-size:13px;cursor:pointer">דחייה</button>
12608    <button onclick="setCookieConsent('granted')" style="padding:8px 18px;border-radius:8px;border:none;background:#7c8cf8;color:#fff;font-size:13px;cursor:pointer;font-weight:600">אישור</button>
12609  </div>
12610</div>
12611<script>
12612(function(){
12613  var consent = localStorage.getItem('couponai_cookie_consent');
12614  var ts = parseInt(localStorage.getItem('couponai_cookie_consent_ts') || '0', 10);
12615  var age = Date.now() - ts;
12616  var days90 = 90 * 24 * 60 * 60 * 1000;
12617  // Show banner if: never answered, or answered "denied" more than 90 days ago
12618  if (!consent || (consent === 'denied' && age > days90)) {
12619    var b = document.getElementById('cookie-banner');
12620    if (b) b.style.display = 'flex';
12621  }
12622})();
12623function setCookieConsent(val) {
12624  localStorage.setItem('couponai_cookie_consent', val);
12625  localStorage.setItem('couponai_cookie_consent_ts', Date.now().toString());
12626  var b = document.getElementById('cookie-banner');
12627  if (b) b.style.display = 'none';
12628  gtag('consent', 'update', {
12629    ad_storage:         val,
12630    analytics_storage:  val,
12631    ad_user_data:       val,
12632    ad_personalization: val
12633  });
12634}
12635</script>
vendor: 1 bytes, line 12635
12635
12636<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"53ec717f4f9944a1b853521eee6f2786","r":1,"spa":2}' crossorigin="anonymous"></script>
12636
12637</body>
12638</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.