Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
## 2024-06-21 - Added skip-to-content link
**Learning:** Found a missing skip-to-content link, which is a key accessibility feature to help keyboard and screen reader users bypass navigation. Additionally learned that giving `<main>` `tabindex="-1"` and removing its outline when `:focus-visible` ensures proper focus handling after clicking the skip link without disruptive visual outlines.
**Action:** Always include a skip-to-content link near the start of the `body` and manage target focus appropriately.

## 2026-06-26 - Added scroll-padding-top for sticky header
**Learning:** Anchor links on pages with a fixed or sticky header often scroll the target element behind the header, hiding the content. This is particularly problematic in single-page structures with heavy internal navigation.
**Action:** Use `scroll-padding-top` on the `html` element with a value equal to the sticky header's height to ensure anchor links scroll to a visible position.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
venv/
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2024-06-20 - Unnecessary initial DOM updates for default language
**Learning:** The simple static i18n implementation runs `node.textContent = dict[node.dataset.i18n]` for every translatable node on the initial script load, even when the HTML is already written in the target language (Korean). This creates unnecessary layout/paint operations and blocking time on the main thread for elements that don't need text changes.
**Action:** Always check if the current value matches the desired value before updating the DOM (`node.textContent !== newText`), and add early exits when setting state to the same value to avoid redundant DOM traversal and writes.
## 2024-06-27 - ์ดˆ๊ธฐ ์–ธ์–ด ๋กœ๋“œ ์‹œ ๋ถˆํ•„์š”ํ•œ DOM ํƒ์ƒ‰ ์ œ๊ฑฐ
**Learning:** ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ์š”์ฒญ๋œ ์–ธ์–ด๊ฐ€ HTML์˜ ๊ธฐ๋ณธ ์–ธ์–ด(ko)์™€ ๋™์ผํ•œ ๊ฒฝ์šฐ, ๋ชจ๋“  DOM ํ…์ŠคํŠธ ๋…ธ๋“œ๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ  ์น˜ํ™˜ํ•˜๋Š” ๋ถˆํ•„์š”ํ•œ ์ž‘์—…์„ ์ƒ๋žตํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋จ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
**Action:** `isInitialDefault` ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ๋ถˆํ•„์š”ํ•œ DOM ์ˆœํšŒ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@
**Vulnerability:** Unhandled exceptions when accessing `localStorage` in strict browser privacy modes (e.g., when cookies are blocked).
**Learning:** Browsers throw a `SecurityError` when `localStorage` is accessed and the user has blocked third-party cookies or is in a strict privacy mode. If unhandled, this crashes the executing script, leading to a degraded user experience (DoS-like behavior for privacy-conscious users).
**Prevention:** Always wrap `localStorage.getItem` and `localStorage.setItem` in `try-catch` blocks to fail securely and fall back to sensible defaults.
## 2026-06-27 - ์™ธ๋ถ€ ๋งํฌ์˜ reverse tabnabbing ์ทจ์•ฝ์  ์™„ํ™”
**Vulnerability:** ์™ธ๋ถ€ ๋งํฌ(ํŠนํžˆ ์ฐธ์กฐ๋ฌธํ—Œ ๋งํฌ ๋“ฑ)์— `target="_blank"` ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ƒˆ ํƒญ์œผ๋กœ ์—ฌ๋Š” ๋™์ž‘์„ ์œ ๋„ํ•  ๋•Œ, `rel="noopener noreferrer"` ์†์„ฑ์ด ๋ˆ„๋ฝ๋˜์–ด Reverse Tabnabbing ๊ณต๊ฒฉ์— ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ์Œ.
**Learning:** `rel="noopener noreferrer"`๊ฐ€ ์—†์œผ๋ฉด ์ƒˆ๋กœ ์—ด๋ฆฐ ํƒญ์˜ ํŽ˜์ด์ง€๊ฐ€ `window.opener` ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์›๋ž˜ ํŽ˜์ด์ง€์˜ `location`์„ ์•…์˜์ ์ธ ์‚ฌ์ดํŠธ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
**Prevention:** ์™ธ๋ถ€ ๋งํฌ๋ฅผ ์ƒˆ ํƒญ์œผ๋กœ ์—ด๊ธฐ ์œ„ํ•ด `target="_blank"`๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ `rel="noopener noreferrer"`๋ฅผ ํ•จ๊ป˜ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ถ€๋ชจ ์ฐฝ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# ๋ณ€๊ฒฝ ์ด๋ ฅ (CHANGELOG)

## [Unreleased]
### ์ถ”๊ฐ€๋จ
- `styles.css`์— `scroll-padding-top: 81px` ์ถ”๊ฐ€ํ•˜์—ฌ ๊ณ ์ • ํ—ค๋”์™€ ์•ต์ปค ๋งํฌ ๋Œ€์ƒ ๊ฐ„์˜ ๊ฒน์นจ ํ˜„์ƒ์„ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.
- ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ์œ„ํ•ด `.gitignore`์— `venv/` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋ฌด์‹œํ•˜๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
- **์„ฑ๋Šฅ ๊ฐœ์„ **: `i18n.js`์—์„œ ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ๊ธฐ๋ณธ ์–ธ์–ด๊ฐ€ ํ•œ๊ตญ์–ด(ko)์ธ ๊ฒฝ์šฐ ๋ถˆํ•„์š”ํ•œ DOM ์ˆœํšŒ ๋ฐ ํ…์ŠคํŠธ ์—…๋ฐ์ดํŠธ๋ฅผ ์ƒ๋žตํ•˜๋„๋ก ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.
- **ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€**: ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ ๋กœ์ง์˜ ๋ฌด๊ฒฐ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด `test_i18n.html` ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
24 changes: 16 additions & 8 deletions i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,7 @@ function setLanguage(lang) {

const dict = messages[lang] || messages.ko;

if (!i18nNodes) {
i18nNodes = document.querySelectorAll("[data-i18n]");
if (!langButtons) {
langButtons = document.querySelectorAll("[data-lang]");
metaDesc = document.querySelector('meta[name="description"]');
ogDesc = document.querySelector('meta[property="og:description"]');
Expand Down Expand Up @@ -346,13 +345,22 @@ function setLanguage(lang) {
}
}

// Only update textContent if it actually changed to avoid layout recalculations
i18nNodes.forEach((node) => {
const newText = dict[node.dataset.i18n];
if (newText && node.textContent !== newText) {
node.textContent = newText;
// โšก Bolt: ๊ธฐ๋ณธ ์–ธ์–ด๋กœ ์ดˆ๊ธฐ ๋กœ๋“œ ์‹œ ๋ถˆํ•„์š”ํ•œ DOM ํ…์ŠคํŠธ ์ฝ๊ธฐ ๋ฐ ํƒ์ƒ‰ ์ƒ๋žต (์„ฑ๋Šฅ ๊ฐœ์„ )
const isInitialDefault = lang === "ko" && !i18nNodes;

if (!isInitialDefault) {
if (!i18nNodes) {
i18nNodes = document.querySelectorAll("[data-i18n]");
}
});

// Only update textContent if it actually changed to avoid layout recalculations
i18nNodes.forEach((node) => {
const newText = dict[node.dataset.i18n];
if (newText && node.textContent !== newText) {
node.textContent = newText;
}
});
}

langButtons.forEach((button) => {
const pressed = String(button.dataset.lang === lang);
Expand Down
29 changes: 15 additions & 14 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self'; object-src 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests;">
<meta name="referrer" content="strict-origin-when-cross-origin">
<title>๋งฅ๋ฝ์ง€ํ˜œ ์—ฐ๊ตฌ์‹ค | Contextual Wisdom Lab</title>
<meta
name="description"
Expand Down Expand Up @@ -40,7 +41,7 @@
<a href="#projects" data-i18n="nav.projects">ํ”„๋กœ์ ํŠธ</a>
<a href="#forks" data-i18n="nav.forks">Fork</a>
<a href="#work" data-i18n="nav.work">์ž‘์—…</a>
<a href="https://github.com/ContextualWisdomLab">GitHub</a>
<a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab">GitHub</a>
</nav>
<div class="language-switch" role="group" aria-label="Language">
<button type="button" data-lang="ko" aria-pressed="true">KO</button>
Expand All @@ -57,7 +58,7 @@ <h1 data-i18n="hero.title">๋งฅ๋ฝ์ง€ํ˜œ ์—ฐ๊ตฌ์‹ค</h1>
๊ตฌ์Šฌ์ด ์„œ ๋ง์ด์–ด๋„ ๊ฟฐ์–ด์•ผ ๋ณด๋ฐฐ์ด๋“ฏ, ๋ฌธ์„œ, ๋ฉ”์ผ, ๋กœ๊ทธ, ํšŒ์˜๋ก์„ ๋งฅ๋ฝ ์•ˆ์—์„œ ์—ฎ์–ด ์‚ฌ๋žŒ์ด ๋ฌด์—‡์„ ๊ฒฐ์ •ํ•˜๊ณ  ๋ฌด์—‡์„ ์‹คํ–‰ํ• ์ง€ ๋ณด์ด๊ฒŒ ํ•˜๋Š” AI ์˜์‚ฌ๊ฒฐ์ • ์ง€์› ์‹œ์Šคํ…œ์„ ์—ฐ๊ตฌํ•˜๊ณ  ๋งŒ๋“ญ๋‹ˆ๋‹ค.
</p>
<div class="hero-actions" role="group" aria-label="Homepage actions">
<a class="button primary" href="https://github.com/ContextualWisdomLab" data-i18n="hero.primaryCta">GitHub ๋ณด๊ธฐ</a>
<a target="_blank" rel="noopener noreferrer" class="button primary" href="https://github.com/ContextualWisdomLab" data-i18n="hero.primaryCta">GitHub ๋ณด๊ธฐ</a>
<a class="button secondary" href="#dikw" data-i18n="hero.secondaryCta">DIKW ๋ณด๊ธฐ</a>
</div>
</div>
Expand Down Expand Up @@ -251,19 +252,19 @@ <h2 data-i18n="references.title">์ฐธ๊ณ ๋ฌธํ—Œ</h2>
<ol class="reference-list">
<li>
<span data-i18n="references.ackoff">Ackoff, R. L. (1989). From data to wisdom. Journal of Applied Systems Analysis, 16(1), 3-9.</span>
<a href="https://faculty.ung.edu/kmelton/documents/datawisdom.pdf">https://faculty.ung.edu/kmelton/documents/datawisdom.pdf</a>
<a target="_blank" rel="noopener noreferrer" href="https://faculty.ung.edu/kmelton/documents/datawisdom.pdf">https://faculty.ung.edu/kmelton/documents/datawisdom.pdf</a>
</li>
<li>
<span data-i18n="references.baskarada">Baskarada, S., &amp; Koronios, A. (2013). Data, information, knowledge, wisdom (DIKW): A semiotic theoretical and empirical exploration of the hierarchy and its quality dimension. Australasian Journal of Information Systems, 18(1).</span>
<a href="https://doi.org/10.3127/ajis.v18i1.748">https://doi.org/10.3127/ajis.v18i1.748</a>
<a target="_blank" rel="noopener noreferrer" href="https://doi.org/10.3127/ajis.v18i1.748">https://doi.org/10.3127/ajis.v18i1.748</a>
</li>
<li>
<span data-i18n="references.fricke">Frickรฉ, M. (2009). The knowledge pyramid: A critique of the DIKW hierarchy. Journal of Information Science, 35(2), 131-142.</span>
<a href="https://doi.org/10.1177/0165551508094050">https://doi.org/10.1177/0165551508094050</a>
<a target="_blank" rel="noopener noreferrer" href="https://doi.org/10.1177/0165551508094050">https://doi.org/10.1177/0165551508094050</a>
</li>
<li>
<span data-i18n="references.brienza">Brienza, J. P., Kung, F. Y. H., Santos, H. C., Bobocel, D. R., &amp; Grossmann, I. (2018). Wisdom, bias, and balance: Toward a process-sensitive measurement of wisdom-related cognition. Journal of Personality and Social Psychology, 115(6), 1093-1126.</span>
<a href="https://doi.org/10.1037/pspp0000171">https://doi.org/10.1037/pspp0000171</a>
<a target="_blank" rel="noopener noreferrer" href="https://doi.org/10.1037/pspp0000171">https://doi.org/10.1037/pspp0000171</a>
</li>
</ol>
</section>
Expand Down Expand Up @@ -324,31 +325,31 @@ <h2 data-i18n="projects.title">๊ณต๊ฐœ ํ”„๋กœ์ ํŠธ</h2>
</div>
<div class="naruon-grid project-grid">
<article>
<h3><a href="https://github.com/ContextualWisdomLab/naruon" data-i18n="projects.naruonTitle">Naruon</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/naruon" data-i18n="projects.naruonTitle">Naruon</a></h3>
<p data-i18n="projects.naruonBody">๋ฉ”์ผ, ์ฒจ๋ถ€, ์ผ์ •, ์ž‘์—…์„ ๋งฅ๋ฝ์œผ๋กœ ๋ฌถ์–ด ํŒ๋‹จ๊ณผ ์‹คํ–‰์œผ๋กœ ์—ฐ๊ฒฐํ•˜๋Š” AI ์ด๋ฉ”์ผ ์›Œํฌ์ŠคํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.</p>
</article>
<article>
<h3><a href="https://github.com/ContextualWisdomLab/pg-erd-cloud" data-i18n="projects.pgErdTitle">pg-erd-cloud</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/pg-erd-cloud" data-i18n="projects.pgErdTitle">pg-erd-cloud</a></h3>
<p data-i18n="projects.pgErdBody">PostgreSQL ์Šคํ‚ค๋งˆ๋ฅผ ๋ฆฌ๋ฒ„์Šค ์—”์ง€๋‹ˆ์–ด๋งํ•˜๊ณ  ERD์™€ DDL ๊ณต์œ  ํ๋ฆ„์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ผ์šฐ๋“œ MVP์ž…๋‹ˆ๋‹ค.</p>
</article>
<article>
<h3><a href="https://github.com/ContextualWisdomLab/bandscope" data-i18n="projects.bandscopeTitle">BandScope</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/bandscope" data-i18n="projects.bandscopeTitle">BandScope</a></h3>
<p data-i18n="projects.bandscopeBody">๊ณก์„ ์„น์…˜, ์—ญํ• , ํ…œํฌ, ์—ฐ์Šต ์šฐ์„ ์ˆœ์œ„๋กœ ๋ถ„์„ํ•˜๋Š” ๋กœ์ปฌ ์šฐ์„  ๋ฆฌํ—ˆ์„ค ์•ฑ์ž…๋‹ˆ๋‹ค.</p>
</article>
<article>
<h3><a href="https://github.com/ContextualWisdomLab/codec-carver" data-i18n="projects.codecCarverTitle">codec-carver</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/codec-carver" data-i18n="projects.codecCarverTitle">codec-carver</a></h3>
<p data-i18n="projects.codecCarverBody">๊ธด ๋…น์Œ์„ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์กดํ•œ FLAC/Opus ์กฐ๊ฐ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” Python CLI์ž…๋‹ˆ๋‹ค.</p>
</article>
<article>
<h3><a href="https://github.com/ContextualWisdomLab/newsdom-api" data-i18n="projects.newsdomTitle">newsdom-api</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/newsdom-api" data-i18n="projects.newsdomTitle">newsdom-api</a></h3>
<p data-i18n="projects.newsdomBody">์Šค์บ”๋œ ์ผ๋ณธ์–ด ์‹ ๋ฌธ PDF๋ฅผ ๊ธฐ์‚ฌ, ์ œ๋ชฉ, ๋ณธ๋ฌธ, ์ด๋ฏธ์ง€ ๊ตฌ์กฐ์˜ DOMํ˜• JSON์œผ๋กœ ํŒŒ์‹ฑํ•˜๋Š” API์ž…๋‹ˆ๋‹ค.</p>
</article>
<article>
<h3><a href="https://github.com/ContextualWisdomLab/scopeweave" data-i18n="projects.scopeweaveTitle">scopeweave</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/scopeweave" data-i18n="projects.scopeweaveTitle">scopeweave</a></h3>
<p data-i18n="projects.scopeweaveBody">ํŠธ๋ฆฌ ํŽธ์ง‘, ์ง„ํ–‰๋ฅ  ๊ณ„์‚ฐ, CSV/JSON, ์ฃผ๊ฐ„ Gantt๋ฅผ ์ง€์›ํ•˜๋Š” ์ •์  HTML/CSS/JS WBS ํ”Œ๋ž˜๋„ˆ์ž…๋‹ˆ๋‹ค.</p>
</article>
<article>
<h3><a href="https://github.com/ContextualWisdomLab/VibeSec" data-i18n="projects.vibesecTitle">VibeSec</a></h3>
<h3><a target="_blank" rel="noopener noreferrer" href="https://github.com/ContextualWisdomLab/VibeSec" data-i18n="projects.vibesecTitle">VibeSec</a></h3>
<p data-i18n="projects.vibesecBody">๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ์•ฑ์„ ์œ„ํ•œ ๋ณด์•ˆ ๊ฐ€๋“œ๋ ˆ์ผ์ž…๋‹ˆ๋‹ค. AI ๊ฐœ๋ฐœ ๋„๊ตฌ ๊ทœ์น™, ์ •์  ์ ๊ฒ€, ๋ฆฌ๋ทฐ์™€ ์ˆ˜์ • ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.</p>
</article>
</div>
Expand Down Expand Up @@ -419,7 +420,7 @@ <h2 data-i18n="work.title">์—ฐ๊ตฌ์—์„œ ์ œํ’ˆ์œผ๋กœ</h2>
>
<p>
<span data-i18n="footer.founded">Founded by</span>
<a href="https://github.com/seonghobae">Seongho Bae</a>.
<a target="_blank" rel="noopener noreferrer" href="https://github.com/seonghobae">Seongho Bae</a>.
<span data-i18n="footer.line">Context into judgment. Judgment into action.</span>
</p>
</footer>
Expand Down
Loading