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>
69 lines
2.6 KiB
JavaScript
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);
|
|
}
|
|
}
|