-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
83 lines (72 loc) · 2.94 KB
/
script.js
File metadata and controls
83 lines (72 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/* ============================================================
허브 페이지 — 책 카드 렌더링 + 테마 토글
============================================================ */
(function () {
const THEME_KEY = "just:theme";
/* ---------- Theme ---------- */
const themeToggle = document.getElementById("themeToggle");
function applyTheme(theme) {
document.documentElement.dataset.theme = theme;
if (themeToggle) themeToggle.checked = theme === "dark";
localStorage.setItem(THEME_KEY, theme);
}
const savedTheme =
localStorage.getItem(THEME_KEY) ||
(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
applyTheme(savedTheme);
if (themeToggle) {
themeToggle.addEventListener("change", () => {
applyTheme(themeToggle.checked ? "dark" : "light");
});
}
/* ---------- Helpers ---------- */
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
// Soft accent: 12% mix between accent and white-ish for the icon background.
// We use color-mix when available; otherwise fall back to the accent itself.
function softenAccent(hex) {
return `color-mix(in srgb, ${hex} 14%, transparent)`;
}
/* ---------- Render book grid ---------- */
const grid = document.getElementById("bookGrid");
if (grid && Array.isArray(window.JUST_BOOKS)) {
grid.innerHTML = window.JUST_BOOKS.map(renderCard).join("");
}
function renderCard(book) {
const status = window.JUST_STATUS_LABEL[book.status] || {
text: book.status,
className: "",
};
const disabled = book.status === "coming-soon";
const accent = book.accent || "#2e6ff2";
const practiceBtn = book.practiceUrl
? `<a class="primary" href="${escapeHtml(book.practiceUrl)}">실습 시작</a>`
: `<a class="primary" aria-disabled="true" tabindex="-1">실습 준비중</a>`;
const bookBtn = book.bookUrl
? `<a href="${escapeHtml(book.bookUrl)}" target="_blank" rel="noopener">책 보러가기</a>`
: `<a aria-disabled="true" tabindex="-1">책 준비중</a>`;
return `
<article class="book-card${disabled ? " disabled" : ""}"
style="--card-accent: ${escapeHtml(accent)}; --card-accent-soft: ${softenAccent(accent)};">
<div class="card-thumb">
<span class="card-thumb-icon">${escapeHtml(book.icon || book.slug.slice(0, 2).toUpperCase())}</span>
<span class="card-status ${status.className}">${escapeHtml(status.text)}</span>
</div>
<div class="card-body">
<h2 class="card-title">${escapeHtml(book.title)}</h2>
<p class="card-tagline">${escapeHtml(book.tagline || "")}</p>
<p class="card-desc">${escapeHtml(book.description || "")}</p>
<div class="card-actions">
${practiceBtn}
${bookBtn}
</div>
</div>
</article>
`;
}
})();