1/* Chroma AI â ì²´ë¥ ìê° ê¸°ë° í¼ëë°± íì 2 ì¼ì ìê° ë¨¸ë¬¸ 방문ììê²ë§ ì¡°ì©í ë ¸ì¶. ë³ì (íì) + í ì¤ ì견(ì í) â 3 Supabase site_feedback í ì´ë¸ì ìµëª insert. 4 5 ë ¸ì¶ ê·ì¹(ê³¼íê² ë¨ì§ ìëë¡): 6 · DWELL_MS ëì "ì¤ì ë¡ ë³´ê³ ìì ë"ë§ ì¹´ì´í¸ (í ì¨ê¹ ì ì¼ìì ì§) 7 · ì ì¶íë©´ ì구ì ì¼ë¡ ë¤ì ì ë¸ 8 · ë«ì¼ë©´ SNOOZE_DAYS ëì ì ë¸ 9 · ê²°ì /ì¸ì¦ ë± ì í íë¦ íì´ì§ììë ìì ëì ì í¨ 10*/ 11(function () { 12 'use strict'; 13 14 var DWELL_MS = 45000; // 45ì´ ì²´ë¥ í ë ¸ì¶ 15 var SNOOZE_DAYS = 30; // ë«ìì ë ì¬ë ¸ì¶ ì ì 16 var KEY = 'chroma-feedback'; 17 18 // ì í íë¦ ë°©í´ ê¸ì§ â ê²°ì ·ë¡ê·¸ì¸Â·ì½ë°± íì´ì§ ì ì¸. 19 // /community ì /theme ì ì ë¡ë ì ë íì (community.html)ì´ ê°ì ì°ì¸¡ íë¨ì 20 // ì°ë¯ë¡ ì ì¸íë¤. ë íì ì´ ê²¹ì¹ë©´ ìµì ì ê²½íì´ ëê³ , 커뮤ëí° ë°©ë¬¸ììê² 21 // ì¬ì´í¸ ë§ì¡±ëë³´ë¤ ì ë¡ë ì ëê° ì°ì ì´ë¤. 22 var BLOCKED = ['/upgrade', '/login', '/auth-callback', '/thank-you', '/reset-password', '/admin', '/uninstall', 23 '/community', '/theme']; 24 var path = location.pathname.replace(/\.html$/, ''); 25 for (var i = 0; i < BLOCKED.length; i++) { 26 if (path.indexOf(BLOCKED[i]) === 0) return; 27 } 28 29 // ì´ì ìí íì¸ (ì ì¶ ìë£ / ì¤ëì¦ ì¤ì´ë©´ ì¢ ë£) 30 var state = null; 31 try { state = JSON.parse(localStorage.getItem(KEY) || 'null'); } catch (e) { /* noop */ } 32 if (state && state.done) return; 33 if (state && state.snoozeUntil && Date.now() < state.snoozeUntil) return; 34 35 function save(obj) { try { localStorage.setItem(KEY, JSON.stringify(obj)); } catch (e) { /* noop */ } } 36 37 var SB_REST = 'https://vwvyjkmvpdppojsrpdwv.supabase.co/rest/v1'; 38 var SB_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZ3dnlqa212cGRwcG9qc3JwZHd2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzgwNDY0NzgsImV4cCI6MjA5MzYyMjQ3OH0.U_PFEL1cz6JWwzk0cvZyEr79snHn56T0-waCvAZMjTI'; 39 40 var isKo = function () { 41 if (document.body && document.body.classList.contains('ko')) return true; 42 try { if (localStorage.getItem('chroma-lang') === 'ko') return true; } catch (e) { /* noop */ } 43 return (navigator.language || '').toLowerCase().indexOf('ko') === 0; 44 }; 45 46 // ââ ì²´ë¥ ìê° ì¸¡ì (íì´ ë³´ì´ë ëìë§ ëì ) ââââââââââââââââââââââââââââ 47 var elapsed = 0, last = Date.now(), timer = null, shown = false; 48 function tick() { 49 if (document.visibilityState === 'visible') elapsed += Date.now() - last; 50 last = Date.now(); 51 if (elapsed >= DWELL_MS && !shown) { shown = true; clearInterval(timer); show(); } 52 } 53 document.addEventListener('visibilitychange', function () { last = Date.now(); }); 54 timer = setInterval(tick, 1000); 55 56 // ââ UI âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 57 function show() { 58 var ko = isKo(); 59 var wrap = document.createElement('div'); 60 wrap.id = 'chromaFb'; 61 wrap.setAttribute('role', 'dialog'); 62 wrap.setAttribute('aria-label', ko ? 'í¼ëë°±' : 'Feedback'); 63 wrap.innerHTML = '' 64 + '<style>' 65 + '#chromaFb{position:fixed;right:20px;bottom:20px;z-index:2147483000;width:320px;max-width:calc(100vw - 32px);' 66 + 'background:#fff;border:1px solid #e7eaf0;border-radius:16px;box-shadow:0 18px 50px rgba(12,19,34,.18);' 67 + "font-family:'Pretendard','Inter',system-ui,sans-serif;color:#0c1322;padding:18px 18px 16px;" 68 + 'opacity:0;transform:translateY(14px);transition:opacity .32s ease,transform .32s cubic-bezier(.16,1,.3,1)}' 69 + '#chromaFb.in{opacity:1;transform:none}' 70 + '#chromaFb h3{margin:0 0 4px;font-size:15px;font-weight:800;letter-spacing:-.01em}' 71 + '#chromaFb p{margin:0 0 12px;font-size:12.5px;line-height:1.5;color:#54607a}' 72 + '#chromaFb .fb-stars{display:flex;gap:4px;margin-bottom:12px}' 73 + '#chromaFb .fb-star{cursor:pointer;font-size:24px;line-height:1;color:#dfe3ea;background:none;border:0;padding:0 2px;transition:transform .12s ease,color .12s ease}' 74 + '#chromaFb .fb-star:hover{transform:scale(1.14)}' 75 + '#chromaFb .fb-star.on{color:#ffb020}' 76 + '#chromaFb textarea{width:100%;box-sizing:border-box;min-height:64px;resize:vertical;font-family:inherit;' 77 + 'font-size:12.5px;line-height:1.5;padding:9px 11px;border:1px solid #e7eaf0;border-radius:10px;color:#0c1322;outline:none}' 78 + '#chromaFb textarea:focus{border-color:#146ef5}' 79 + '#chromaFb .fb-row{display:flex;gap:8px;margin-top:11px}' 80 + '#chromaFb button.fb-act{flex:1;appearance:none;cursor:pointer;font-family:inherit;font-size:13px;font-weight:700;' 81 + 'padding:10px;border-radius:10px;border:1px solid #e7eaf0;background:#fff;color:#54607a}' 82 + '#chromaFb button.fb-act.primary{background:#146ef5;border-color:#146ef5;color:#fff}' 83 + '#chromaFb button.fb-act.primary:disabled{opacity:.45;cursor:not-allowed}' 84 + '#chromaFb .fb-x{position:absolute;top:10px;right:12px;border:0;background:none;cursor:pointer;' 85 + 'font-size:18px;line-height:1;color:#97a1b2;padding:2px 4px}' 86 + '#chromaFb .fb-done{text-align:center;padding:6px 0 2px}' 87 + '#chromaFb .fb-done .fb-ok{font-size:26px}' 88 + '@media (prefers-reduced-motion: reduce){#chromaFb{transition:none}}' 89 + '</style>' 90 + '<button class="fb-x" aria-label="' + (ko ? 'ë«ê¸°' : 'Close') + '">×</button>' 91 + '<h3>' + (ko ? 'ì ê¹, ì´ë ì ¨ëì?' : 'How is it going?') + '</h3>' 92 + '<p>' + (ko ? 'Chroma AI를 ë ì¢ê² ë§ëë ë° í° íì´ ë©ëë¤. 10ì´ë©´ ì¶©ë¶í´ì.' 93 : 'Your feedback helps us make Chroma AI better. Takes 10 seconds.') + '</p>' 94 + '<div class="fb-stars" role="radiogroup"></div>' 95 + '<textarea placeholder="' + (ko ? 'ë¨ê¸°ê³ ì¶ì ë§ì´ ìë¤ë©´â¦ (ì í)' : 'Anything you want to tell us? (optional)') + '"></textarea>' 96 + '<div class="fb-row">' 97 + '<button class="fb-act fb-later">' + (ko ? 'ëì¤ì' : 'Later') + '</button>' 98 + '<button class="fb-act primary fb-send" disabled>' + (ko ? 'ë³´ë´ê¸°' : 'Send') + '</button>' 99 + '</div>'; 100 wrap.style.position = 'fixed'; 101 document.body.appendChild(wrap); 102 requestAnimationFrame(function () { wrap.classList.add('in'); }); 103 104 // ë³ì 105 var rating = 0; 106 var starsEl = wrap.querySelector('.fb-stars'); 107 var sendBtn = wrap.querySelector('.fb-send'); 108 for (var s = 1; s <= 5; s++) { 109 var b = document.createElement('button'); 110 b.className = 'fb-star'; 111 b.type = 'button'; 112 b.textContent = 'â '; 113 b.dataset.v = String(s); 114 b.setAttribute('aria-label', s + (ko ? 'ì ' : ' stars')); 115 starsEl.appendChild(b); 116 } 117 starsEl.addEventListener('click', function (e) { 118 var t = e.target.closest('.fb-star'); 119 if (!t) return; 120 rating = Number(t.dataset.v) || 0; 121 Array.prototype.forEach.call(starsEl.children, function (el, idx) { 122 el.classList.toggle('on', idx < rating); 123 }); 124 sendBtn.disabled = rating === 0; 125 }); 126 127 function close(snooze) { 128 if (snooze) save({ snoozeUntil: Date.now() + SNOOZE_DAYS * 864e5 }); 129 wrap.classList.remove('in'); 130 setTimeout(function () { wrap.remove(); }, 320); 131 } 132 wrap.querySelector('.fb-x').addEventListener('click', function () { close(true); }); 133 wrap.querySelector('.fb-later').addEventListener('click', function () { close(true); }); 134 135 sendBtn.addEventListener('click', function () { 136 if (!rating) return; 137 sendBtn.disabled = true; 138 sendBtn.textContent = ko ? 'ë³´ë´ë ì¤â¦' : 'Sendingâ¦'; 139 var payload = { 140 rating: rating, 141 message: (wrap.querySelector('textarea').value || '').trim().slice(0, 2000) || null, 142 page: location.pathname, 143 lang: ko ? 'ko' : 'en', 144 user_agent: (navigator.userAgent || '').slice(0, 300), 145 }; 146 fetch(SB_REST + '/site_feedback', { 147 method: 'POST', 148 headers: { 149 apikey: SB_ANON, 150 Authorization: 'Bearer ' + SB_ANON, 151 'Content-Type': 'application/json', 152 Prefer: 'return=minimal', 153 }, 154 body: JSON.stringify(payload), 155 }).catch(function () { /* ì¤í¨í´ë ì¬ì©ììê² ê°ì¬ íì â ì¬ì´íì§ ìì */ }) 156 .then(function () { 157 save({ done: true }); 158 try { 159 if (window.gtag) window.gtag('event', 'feedback_submit', { rating: rating }); 160 } catch (e) { /* noop */ } 161 wrap.innerHTML = wrap.querySelector('style').outerHTML 162 + '<div class="fb-done"><div class="fb-ok">ð</div><h3 style="margin-top:6px">' 163 + (ko ? 'ê°ì¬í©ëë¤!' : 'Thank you!') + '</h3><p style="margin:4px 0 0">' 164 + (ko ? 'ë³´ë´ì£¼ì ì견ì ê°ì ì ë°ë¡ ë°ìíê² ìµëë¤.' : 'We read every note â thank you for helping.') 165 + '</p></div>'; 166 setTimeout(function () { close(false); }, 2200); 167 }); 168 }); 169 } 170})();
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.