feat(ui): header toggles, section collapse, kiwi-brand colors#1
feat(ui): header toggles, section collapse, kiwi-brand colors#1kickthemoon0817 wants to merge 1 commit into
Conversation
โฆck-links - Add KO/EN segmented pill toggle and sun/moon theme toggle to header bar - Add section collapse/expand with chevron indicators on h2/h3 headings - Apply kiwi brand color to section numbering, footnote refs, and footnote section numbers - Add back-to-TOC links on section heading numbers - Fix duplicate numbering in TOC entries - Fix inconsistent line spacing from external link icons and footnote superscripts - Hide footnote backref arrows, add clickable numbered links in footnotes section - Make header and title bar span full wrapper width - Add emoji spacing in logo - Port all changes to renderer template Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces significant UI and UX enhancements to the kiwi-paper documentation template, including a new header control bar with language (KO/EN) and theme (Light/Dark) toggles, and a dynamic section collapse feature for improved readability. The feedback focuses on resolving a state mismatch in the language toggle that causes flickering on page load, removing unnecessary !important CSS declarations to maintain stylesheet maintainability, and replacing brittle innerHTML manipulations with safer DOM methods to preserve existing heading structures. Additionally, it is recommended to move inline logo spacing to external CSS for better separation of concerns.
| <div class="seg-toggle-item active" data-val="en">EN</div> | ||
| <div class="seg-toggle-item" data-val="ko">KO</div> |
There was a problem hiding this comment.
์ธ์ด ํ ๊ธ์ ์ด๊ธฐ ์ํ๊ฐ ์ผ๊ด๋์ง ์์ ํ์ด์ง ๋ก๋ ์ ๊น๋นก์(flicker)์ด ๋ฐ์ํฉ๋๋ค. ํ์ฌ HTML์์๋ 'EN'์ด ํ์ฑํ ์ํ๋ก ๋ ๋๋ง๋์ง๋ง, JavaScript(var currentLang = 'ko')์์๋ ํ์ด์ง ๋ก๋ ์งํ 'KO'๋ก ์ํ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค. ์ด๊ธฐ ๋ ๋๋ง ์์ ๋ถํฐ ์๋ํ ๊ธฐ๋ณธ ์ธ์ด(KO)๊ฐ ํ์ฑํ๋๋๋ก HTML์ ์์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
| <div class="seg-toggle-item active" data-val="en">EN</div> | |
| <div class="seg-toggle-item" data-val="ko">KO</div> | |
| <div class="seg-toggle-item" data-val="en">EN</div> | |
| <div class="seg-toggle-item active" data-val="ko">KO</div> |
| <div class="seg-toggle-item active" data-val="en">EN</div> | ||
| <div class="seg-toggle-item" data-val="ko">KO</div> |
There was a problem hiding this comment.
์ธ์ด ํ ๊ธ์ ์ด๊ธฐ ์ํ๊ฐ ์ผ๊ด๋์ง ์์ ํ์ด์ง ๋ก๋ ์ ๊น๋นก์(flicker)์ด ๋ฐ์ํฉ๋๋ค. ํ์ฌ HTML์์๋ 'EN'์ด ํ์ฑํ ์ํ๋ก ๋ ๋๋ง๋์ง๋ง, JavaScript(var currentLang = 'ko')์์๋ ํ์ด์ง ๋ก๋ ์งํ 'KO'๋ก ์ํ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค. ์ด๊ธฐ ๋ ๋๋ง ์์ ๋ถํฐ ์๋ํ ๊ธฐ๋ณธ ์ธ์ด(KO)๊ฐ ํ์ฑํ๋๋๋ก HTML์ ์์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
| <div class="seg-toggle-item active" data-val="en">EN</div> | |
| <div class="seg-toggle-item" data-val="ko">KO</div> | |
| <div class="seg-toggle-item" data-val="en">EN</div> | |
| <div class="seg-toggle-item active" data-val="ko">KO</div> |
| .footnote-ref a { color: #0275d8; font-size: 12px; font-weight: normal; } | ||
| .footnote-backref { text-decoration: none; margin-left: 0.3em; color: var(--namu-brand); } | ||
| .footnote-ref a { color: var(--namu-brand); font-size: 12px; font-weight: normal; } | ||
| a[data-footnote-ref] { color: var(--namu-brand) !important; font-weight: 600; } |
There was a problem hiding this comment.
!important ์ ์ธ์ ๋ถํ์ํด ๋ณด์
๋๋ค. a[data-footnote-ref] ์ ํ์๋ ์ด๋ฏธ ๋ค๋ฅธ ์คํ์ผ์ ์ฌ์ ์ํ๊ธฐ์ ์ถฉ๋ถํ ๋ช
์์ฑ์ ๊ฐ์ง๋ฉฐ, ์คํ์ผ์ํธ์์ ๋์ค์ ์ ์ธ๋์๊ธฐ ๋๋ฌธ์ ์ฐ์ ์์๊ฐ ๋์ต๋๋ค. !important๋ฅผ ์ฌ์ฉํ๋ฉด ๋์ค์ CSS๋ฅผ ๋๋ฒ๊น
ํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ๋ ์ด๋ ค์์ง ์ ์์ต๋๋ค.
| a[data-footnote-ref] { color: var(--namu-brand) !important; font-weight: 600; } | |
| a[data-footnote-ref] { color: var(--namu-brand); font-weight: 600; } |
| <button class="settings-toggle" aria-label="๋์คํ๋ ์ด ์ค์ " title="๋์คํ๋ ์ด ์ค์ "> | ||
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> | ||
| </button> | ||
| <a class="wiki-logo" href="#">๐ฅ<span style="margin-left:0.35em"></span>kiwi-paper</a> |
There was a problem hiding this comment.
๋ก๊ณ ์ ์์ด์ฝ๊ณผ ํ
์คํธ ์ฌ์ด์ ์ฌ๋ฐฑ์ ์ฃผ๊ธฐ ์ํด ์ธ๋ผ์ธ ์คํ์ผ์ด ์ ์ฉ๋ ๋น <span> ํ๊ทธ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋ CSS์ HTML์ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ๋ ์์น์ ์ด๊ธ๋๋ฉฐ, ์๋งจํฑํ์ง ์์ต๋๋ค. CSS ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ์คํ์ผ์ ๊ด๋ฆฌํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
| <button class="settings-toggle" aria-label="๋์คํ๋ ์ด ์ค์ " title="๋์คํ๋ ์ด ์ค์ "> | ||
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> | ||
| </button> | ||
| <a class="wiki-logo" href="${navigation.index || '#'}">๐ฅ<span style="margin-left:0.35em"></span>kiwi-paper</a> |
There was a problem hiding this comment.
๋ก๊ณ ์ ์์ด์ฝ๊ณผ ํ
์คํธ ์ฌ์ด์ ์ฌ๋ฐฑ์ ์ฃผ๊ธฐ ์ํด ์ธ๋ผ์ธ ์คํ์ผ์ด ์ ์ฉ๋ ๋น <span> ํ๊ทธ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋ CSS์ HTML์ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ๋ ์์น์ ์ด๊ธ๋๋ฉฐ, ์๋งจํฑํ์ง ์์ต๋๋ค. CSS ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ์คํ์ผ์ ๊ด๋ฆฌํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
| .footnote-ref a { color: var(--link-color); font-size: 12px; font-weight: normal; } | ||
| .footnote-backref { text-decoration: none; margin-left: 0.3em; color: var(--namu-brand); } | ||
| .footnote-ref a { color: var(--namu-brand); font-size: 12px; font-weight: normal; } | ||
| a[data-footnote-ref] { color: var(--namu-brand) !important; font-weight: 600; } |
There was a problem hiding this comment.
!important ์ ์ธ์ ๋ถํ์ํด ๋ณด์
๋๋ค. a[data-footnote-ref] ์ ํ์๋ ์ด๋ฏธ ๋ค๋ฅธ ์คํ์ผ์ ์ฌ์ ์ํ๊ธฐ์ ์ถฉ๋ถํ ๋ช
์์ฑ์ ๊ฐ์ง๋ฉฐ, ์คํ์ผ์ํธ์์ ๋์ค์ ์ ์ธ๋์๊ธฐ ๋๋ฌธ์ ์ฐ์ ์์๊ฐ ๋์ต๋๋ค. !important๋ฅผ ์ฌ์ฉํ๋ฉด ๋์ค์ CSS๋ฅผ ๋๋ฒ๊น
ํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ๋ ์ด๋ ค์์ง ์ ์์ต๋๋ค.
| a[data-footnote-ref] { color: var(--namu-brand) !important; font-weight: 600; } | |
| a[data-footnote-ref] { color: var(--namu-brand); font-weight: 600; } |
| document.querySelectorAll('.wiki-article h2[id], .wiki-article h3[id]').forEach(function(h) { | ||
| var text = h.textContent; | ||
| var match = text.match(/^(\d+(\.\d+)*\.)\s*/); | ||
| if (!match) return; | ||
| var num = match[1]; | ||
| var rest = text.slice(match[0].length); | ||
| h.innerHTML = '<a href="#toc">' + num + '</a> ' + rest; | ||
| }); |
There was a problem hiding this comment.
innerHTML์ ์ฌ์ฉํ์ฌ ์ ๋ชฉ์ ์์ ํ๋ ๋ฐฉ์์ ์ ๋ชฉ์ <code> ํ๊ทธ์ ๊ฐ์ ์ธ๋ผ์ธ HTML ์์๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ ์ด๋ฅผ ์ ๊ฑฐํ ์ ์์ด ์ ์ฌ์ ์ผ๋ก ์ทจ์ฝํฉ๋๋ค. textContent๋ก ํ
์คํธ๋ฅผ ๊ฐ์ ธ์จ ๋ค innerHTML๋ก ์ฌ์์ฑํ๋ฉด ๊ธฐ์กด์ HTML ๊ตฌ์กฐ๊ฐ ์ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ ์์ ์ ์ธ ๋ฐฉ๋ฒ์ DOM ์กฐ์ ๋ฉ์๋(์: insertBefore, createTextNode)๋ฅผ ์ฌ์ฉํ์ฌ ๋งํฌ๋ฅผ ์ฝ์
ํ๋ ๊ฒ์
๋๋ค.
| document.querySelectorAll('.wiki-article h2[id], .wiki-article h3[id]').forEach(function(h) { | |
| var text = h.textContent; | |
| var match = text.match(/^(\d+(\.\d+)*\.)\s*/); | |
| if (!match) return; | |
| var num = match[1]; | |
| var rest = text.slice(match[0].length); | |
| h.innerHTML = '<a href="#toc">' + num + '</a> ' + rest; | |
| }); | |
| document.querySelectorAll('.wiki-article h2[id], .wiki-article h3[id]').forEach(function(h) { | |
| const firstNode = h.firstChild; | |
| if (firstNode && firstNode.nodeType === Node.TEXT_NODE) { | |
| const text = firstNode.nodeValue; | |
| const match = text.match(/^(\d+(\.\d+)*\.)\s*/); | |
| if (match) { | |
| const num = match[1]; | |
| const restText = text.slice(match[0].length); | |
| const link = document.createElement('a'); | |
| link.href = '#toc'; | |
| link.textContent = num; | |
| h.insertBefore(link, firstNode); | |
| firstNode.nodeValue = ' ' + restText.trimStart(); | |
| } | |
| } | |
| }); |
There was a problem hiding this comment.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3a3710bc5
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function setupCollapse(parent, headingSelector, stopTags) { | ||
| var headings = parent.querySelectorAll(headingSelector); | ||
| headings.forEach(function(h) { | ||
| var wrapper = document.createElement('div'); | ||
| wrapper.className = 'section-content'; |
There was a problem hiding this comment.
Restore folding updater after replacing section-fold logic
This change removes the old updateFolding implementation, but applyAll() still calls updateFolding() on startup and whenever settings are changed. In the renderer output, that produces a ReferenceError at runtime, so initial settings application is interrupted and later settings updates fail in the same path. Please either reintroduce a folding updater or remove that call and wire fold behavior into the new collapse flow.
Useful? React with ๐ย / ๐.
| if (!match) return; | ||
| var num = match[1]; | ||
| var rest = text.slice(match[0].length); | ||
| h.innerHTML = '<a href="#toc">' + num + '</a> ' + rest; |
There was a problem hiding this comment.
Avoid injecting heading text via innerHTML for TOC links
Using h.textContent and then assigning h.innerHTML re-parses heading text as HTML, which strips existing inline formatting in headings and can turn escaped angle-bracket text into real elements. This is a content-integrity regression for headings containing code/emphasis or literal <...> text. Build the backlink with DOM nodes (anchor + text node) or escape the remainder before insertion.
Useful? React with ๐ย / ๐.
Summary
Test plan
๐ค Generated with Claude Code