Files
ai-dev-portal/public/js/util.js
2620227 2b74b59f81 refactor(js): app.js 를 브라우저 네이티브 ESM 모듈로 분할
public/js/{util,ui,reactions,modals,comments,feed,main}.js 로 분리하고
dashboard/project 에서 <script type="module" src="/public/js/main.js"> 로 로드.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:53:46 +09:00

69 lines
2.6 KiB
JavaScript

// 공용 DOM 헬퍼 · 토스트 · 이스터에그(이모지 날리기). 다른 모듈이 import 해서 쓴다.
export const $ = (s, r) => (r || document).querySelector(s);
export const $$ = (s, r) => Array.prototype.slice.call((r || document).querySelectorAll(s));
export const EGGS_ON = true;
const CHECK_SVG =
'<svg width="14" height="14" viewBox="0 0 16 16" fill="#3fb950" aria-hidden="true"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L1.72 8.78a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/></svg>';
let toastTimer = null;
export function toast(msg) {
if (!msg) return;
const old = $(".toast"); if (old) old.remove();
const t = document.createElement("div");
t.className = "toast";
t.innerHTML = '<span class="toast-ico">' + CHECK_SVG + "</span>";
t.appendChild(document.createTextNode(msg));
document.body.appendChild(t);
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(() => { if (t.parentNode) t.remove(); }, 2700);
}
let flyId = 0;
function flyLayer() {
let l = $(".flyers");
if (!l) { l = document.createElement("div"); l.className = "flyers"; document.body.appendChild(l); }
return l;
}
function addFlyer(content, style, ttl) {
const span = document.createElement("span");
span.className = "flyer"; span.textContent = content;
Object.keys(style).forEach((k) => { span.style[k] = style[k]; });
flyLayer().appendChild(span);
span._id = ++flyId;
setTimeout(() => { if (span.parentNode) span.remove(); }, ttl);
}
export function burstThumbs() {
if (!EGGS_ON) return;
const cx = window.innerWidth / 2;
for (let i = 0; i < 6; i++) {
const dx = (Math.random() - 0.5) * 180;
addFlyer("👍", {
left: (cx + dx) + "px", top: (window.innerHeight * 0.62) + "px",
fontSize: (22 + Math.random() * 14) + "px",
animation: "aidev-flyup " + (0.9 + Math.random() * 0.3) + "s ease-out " + (i * 60) + "ms forwards",
opacity: 0,
}, 1500);
}
}
export function burstRocket() {
if (!EGGS_ON) return;
addFlyer("🚀", {
left: "-40px", top: (window.innerHeight * (0.18 + Math.random() * 0.2)) + "px",
fontSize: "34px", animation: "aidev-rocket 1.1s ease-in forwards",
}, 1300);
}
export function burstSparkle(x, y) {
if (!EGGS_ON) return;
const chars = ["✨", "⭐", "💫"];
for (let i = 0; i < 7; i++) {
const dx = (Math.random() - 0.5) * 90, dy = (Math.random() - 0.5) * 90;
addFlyer(chars[i % 3], {
left: (x + dx) + "px", top: (y + dy) + "px",
fontSize: (12 + Math.random() * 12) + "px",
animation: "aidev-sparkle " + (0.5 + Math.random() * 0.3) + "s ease-out forwards",
}, 900);
}
}