mingle-website/js/devlog.js
68893236+KINDNICK@users.noreply.github.com 5a0c6a8f70 Add: DevLog 페이지 + 블로그 빌드 시스템 + 팝업 제거 + 싸인 이미지 추가
- DevLog(블로그) 인프라: build-blog.js (MD→HTML), devlog.css, devlog.js
- DevLog 목록/포스트 페이지 4개 언어 (ko/en/ja/zh)
- 글 2편 작성 + 번역: 관성식vs광학식, 광학식 파이프라인
- 전체 네비게이션에 DevLog 탭 추가 (37+ HTML)
- 메인 팝업(요금제 변경 안내) 제거 (ko/en/ja/zh)
- i18n.js: 언어별 페이지에서 번역 JSON 항상 로드하도록 수정
- 방문자 싸인 이미지 3장 추가 (webp 변환)
- sitemap, i18n JSON, package.json 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 03:10:04 +09:00

132 lines
5.6 KiB
JavaScript

/**
* 밍글 스튜디오 블로그 목록 페이지
* blog/index.json에서 글 목록을 불러와 렌더링
*/
(function() {
'use strict';
var grid = document.getElementById('blogGrid');
var filtersWrap = document.getElementById('blogFilters');
var loadingEl = document.getElementById('blogLoading');
if (!grid) return;
var lang = document.documentElement.lang || 'ko';
var langPrefix = { ko: '', en: '/en', ja: '/ja', zh: '/zh' };
var prefix = langPrefix[lang] || '';
var readMore = { ko: '자세히 보기', en: 'Read more', ja: '続きを読む', zh: '阅读更多' };
var emptyText = { ko: '아직 작성된 글이 없습니다.', en: 'No posts yet.', ja: 'まだ記事がありません。', zh: '暂无文章。' };
var allText = { ko: '전체', en: 'All', ja: 'すべて', zh: '全部' };
var allPosts = [];
var currentFilter = 'all';
function init() {
fetch('/devlog/index.json?t=' + Date.now())
.then(function(res) { return res.json(); })
.then(function(posts) {
allPosts = posts;
if (loadingEl) loadingEl.style.display = 'none';
buildFilters();
renderPosts();
})
.catch(function() {
if (loadingEl) loadingEl.style.display = 'none';
grid.innerHTML = '<div class="blog-empty"><i class="fas fa-pen-fancy"></i><p>' + emptyText[lang] + '</p></div>';
});
}
function buildFilters() {
if (!filtersWrap || allPosts.length === 0) return;
var categories = {};
allPosts.forEach(function(p) {
var cat = (p.categories && p.categories[lang]) || (p.categories && p.categories.ko) || p.category || '';
if (cat) categories[cat] = true;
});
var cats = Object.keys(categories).sort();
if (cats.length < 2) { filtersWrap.style.display = 'none'; return; }
var html = '<button class="blog-filter-btn active" data-cat="all">' + allText[lang] + '</button>';
cats.forEach(function(c) {
html += '<button class="blog-filter-btn" data-cat="' + c + '">' + c + '</button>';
});
filtersWrap.innerHTML = html;
filtersWrap.addEventListener('click', function(e) {
var btn = e.target.closest('.blog-filter-btn');
if (!btn) return;
filtersWrap.querySelectorAll('.blog-filter-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
currentFilter = btn.getAttribute('data-cat');
renderPosts();
});
}
function getCategory(post) {
return (post.categories && post.categories[lang]) || (post.categories && post.categories.ko) || post.category || '';
}
function renderPosts() {
var filtered = currentFilter === 'all' ? allPosts : allPosts.filter(function(p) { return getCategory(p) === currentFilter; });
if (filtered.length === 0) {
grid.innerHTML = '<div class="blog-empty"><i class="fas fa-pen-fancy"></i><p>' + emptyText[lang] + '</p></div>';
return;
}
var html = '';
filtered.forEach(function(post) {
var title = (post.titles && post.titles[lang]) || (post.titles && post.titles.ko) || post.slug;
var desc = (post.descriptions && post.descriptions[lang]) || (post.descriptions && post.descriptions.ko) || '';
var cat = getCategory(post);
var thumb = post.thumbnail ? '/blog/posts/' + post.slug + '/' + post.thumbnail : '';
var url = prefix + '/devlog/' + post.slug;
var date = formatDate(post.date);
html += '<article class="blog-card">';
html += '<a href="' + url + '">';
if (thumb) {
html += '<img class="blog-card-thumb" src="' + thumb + '" alt="' + escapeHtml(title) + '" loading="lazy">';
} else {
html += '<div class="blog-card-thumb-placeholder"><i class="fas fa-pen-fancy"></i></div>';
}
html += '</a>';
html += '<div class="blog-card-body">';
if (cat) html += '<span class="blog-card-category">' + escapeHtml(cat) + '</span>';
html += '<h2 class="blog-card-title"><a href="' + url + '" style="color:inherit;text-decoration:none">' + escapeHtml(title) + '</a></h2>';
html += '<p class="blog-card-desc">' + escapeHtml(desc) + '</p>';
html += '<div class="blog-card-footer">';
html += '<span class="blog-card-date"><i class="far fa-calendar-alt"></i> ' + date + '</span>';
html += '<a href="' + url + '" class="blog-card-link">' + readMore[lang] + ' →</a>';
html += '</div></div></article>';
});
grid.innerHTML = html;
}
function formatDate(dateStr) {
if (!dateStr) return '';
var d = new Date(dateStr + 'T00:00:00');
if (isNaN(d)) return dateStr;
var y = d.getFullYear(), m = d.getMonth() + 1, day = d.getDate();
if (lang === 'ko') return y + '.' + m + '.' + day;
if (lang === 'ja' || lang === 'zh') return y + '/' + m + '/' + day;
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
return months[m - 1] + ' ' + day + ', ' + y;
}
function escapeHtml(str) {
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();