<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://changpop.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=%EC%B0%BD%ED%8C%9D%EC%9C%84%ED%82%A4</id>
	<title>창팝위키 - 사용자 기여 [ko]</title>
	<link rel="self" type="application/atom+xml" href="https://changpop.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=%EC%B0%BD%ED%8C%9D%EC%9C%84%ED%82%A4"/>
	<link rel="alternate" type="text/html" href="https://changpop.wiki/%ED%8A%B9%EC%88%98:%EA%B8%B0%EC%97%AC/%EC%B0%BD%ED%8C%9D%EC%9C%84%ED%82%A4"/>
	<updated>2026-09-05T05:25:58Z</updated>
	<subtitle>사용자 기여</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EC%82%AC%EC%95%85%ED%95%9C_%EB%9D%BC%EB%9D%BC&amp;diff=2862</id>
		<title>가사:사악한 라라</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EC%82%AC%EC%95%85%ED%95%9C_%EB%9D%BC%EB%9D%BC&amp;diff=2862"/>
		<updated>2026-09-04T14:29:43Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 별도 가사로 분리&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;今日も新しい服のコレクション&lt;br /&gt;
私に似合うニューファッション&lt;br /&gt;
変装準備はすべて完了&lt;br /&gt;
普通のLaraとは違う事情&lt;br /&gt;
邪悪なLaraの登録&lt;br /&gt;
&lt;br /&gt;
人を騙すのは悪いみたい？&lt;br /&gt;
騙してみたらそうでもない&lt;br /&gt;
この美しい世界&lt;br /&gt;
ほんとに扱いやすい&lt;br /&gt;
&lt;br /&gt;
Night of the Bodies&lt;br /&gt;
踊りなゾンビたちの舞踏会&lt;br /&gt;
息の留まったここにPumping&lt;br /&gt;
Night of the Boides&lt;br /&gt;
防腐剤の臭いがする舞台&lt;br /&gt;
あんたたちも私と踊りなさい&lt;br /&gt;
邪悪なLaraと一緒の世界&lt;br /&gt;
&lt;br /&gt;
私より目立つ奴らが憎い&lt;br /&gt;
本当に憎悪するし憎たらしい&lt;br /&gt;
ぜんぶまとめて死んでしまえばいい&lt;br /&gt;
私が上に行けばいいだなんて？&lt;br /&gt;
ありえない&lt;br /&gt;
戯言はぜんぶPass&lt;br /&gt;
うまくやるより&lt;br /&gt;
他人を引きずり下ろす&lt;br /&gt;
その方がずっと簡単なのよ&lt;br /&gt;
&lt;br /&gt;
踊れ死体の Boogie Woogie&lt;br /&gt;
スタイルは邪悪に Woogie Boogie&lt;br /&gt;
踊れ死体の Boogie Woogie&lt;br /&gt;
スタイルは邪悪に Woogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
Boogie Boogie Boogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
&lt;br /&gt;
Night of the Bodies&lt;br /&gt;
踊りなゾンビたちの舞踏会&lt;br /&gt;
息の留まったここにPumping&lt;br /&gt;
Night of the Bodies　&lt;br /&gt;
防腐剤の臭いがする舞台&lt;br /&gt;
あんたたちも私と踊りなさい&lt;br /&gt;
邪悪なLaraと一緒の世界&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EC%82%AC%EC%95%85%ED%95%9C_%EB%9D%BC%EB%9D%BC/%EB%B0%9C%EC%9D%8C&amp;diff=2861</id>
		<title>가사:사악한 라라/발음</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EC%82%AC%EC%95%85%ED%95%9C_%EB%9D%BC%EB%9D%BC/%EB%B0%9C%EC%9D%8C&amp;diff=2861"/>
		<updated>2026-09-04T14:29:28Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: 쿄우모 아타라시이 후쿠노 코레쿠숀 와타시니 니아우 뉴-홧숀 헨소쥰비와 스베테 칸료 츠우노 라라토와 치가우 지죠 쟈아쿠나 라라노 토죠  히토오 다마스노와 와루이미타이? 다마시테미타라 소우데모나이 코노 우츠쿠시이 세카이 혼토니 아츠카이 야스이  Night of the Bodies 오도리나 좀비타치노 부토우카이 이키노 토맛타 코코니 Pumping Night of the Bodies　 보우후자이...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;쿄우모 아타라시이 후쿠노 코레쿠숀&lt;br /&gt;
와타시니 니아우 뉴-홧숀&lt;br /&gt;
헨소쥰비와 스베테 칸료&lt;br /&gt;
츠우노 라라토와 치가우 지죠&lt;br /&gt;
쟈아쿠나 라라노 토죠&lt;br /&gt;
&lt;br /&gt;
히토오 다마스노와 와루이미타이?&lt;br /&gt;
다마시테미타라 소우데모나이&lt;br /&gt;
코노 우츠쿠시이 세카이&lt;br /&gt;
혼토니 아츠카이 야스이&lt;br /&gt;
&lt;br /&gt;
Night of the Bodies&lt;br /&gt;
오도리나 좀비타치노 부토우카이&lt;br /&gt;
이키노 토맛타 코코니 Pumping&lt;br /&gt;
Night of the Bodies　&lt;br /&gt;
보우후자이노 니오이가 스루 부타이&lt;br /&gt;
안타타치모 와타시토 오도리나사이&lt;br /&gt;
쟈아쿠나 라라토 잇쇼노 세카이&lt;br /&gt;
&lt;br /&gt;
와타시요리 메다츠 야츠라가 니쿠이&lt;br /&gt;
혼토니 조오스루시 니쿠타라시이&lt;br /&gt;
젠부 마토메테 신데시마이바 이이&lt;br /&gt;
와타시가 우에니 이케바 이이다난테?&lt;br /&gt;
아리에나이&lt;br /&gt;
타와고토와 젠부 Pass&lt;br /&gt;
우마쿠 야루요리&lt;br /&gt;
타닌오 히키즈리 오로스&lt;br /&gt;
소노호우가 즛토 칸탄나노요&lt;br /&gt;
&lt;br /&gt;
오도레 시타이노 Boogie Woogie&lt;br /&gt;
스타이루와 쟈아쿠니 Woogie Boogie&lt;br /&gt;
오도레 시타이노 Boogie Woogie&lt;br /&gt;
스타이루와 쟈아쿠니 Woogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
Boogie Boogie Boogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
&lt;br /&gt;
Night of the Bodies&lt;br /&gt;
오도리나 좀비타치노 부토우카이&lt;br /&gt;
이키노 토맛타 코코니 Pumping&lt;br /&gt;
Night of the Bodies　&lt;br /&gt;
보우후자이노 니오이가 스루 부타이&lt;br /&gt;
안타타치모 와타시토 오도리나사이&lt;br /&gt;
쟈아쿠나 라라토 잇쇼노 세카이&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EC%82%AC%EC%95%85%ED%95%9C_%EB%9D%BC%EB%9D%BC/%EB%B2%88%EC%97%AD&amp;diff=2860</id>
		<title>가사:사악한 라라/번역</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EC%82%AC%EC%95%85%ED%95%9C_%EB%9D%BC%EB%9D%BC/%EB%B2%88%EC%97%AD&amp;diff=2860"/>
		<updated>2026-09-04T14:29:13Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: 오늘도 새로운 옷의 컬렉션 나에게 어울리는 New 패션 변장 준비는 모두 완료 평범한 라라와는 다른 사정 사악한 라라의 등장  남을 속이는게 뭐가 나빠? 속여보니까 그렇지도 않던데 아름다운 이 세계는 정말 다루기 쉬워  Night of the Boides 춤춰라 좀비들의 무도회 숨이 멎은 이 곳에 억지로 펌핑 Night of the Boides 방부제 냄새가 진동하는 이 곳 너희도 나랑 함께 춤을 추...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;오늘도 새로운 옷의 컬렉션&lt;br /&gt;
나에게 어울리는 New 패션&lt;br /&gt;
변장 준비는 모두 완료&lt;br /&gt;
평범한 라라와는 다른 사정&lt;br /&gt;
사악한 라라의 등장&lt;br /&gt;
&lt;br /&gt;
남을 속이는게 뭐가 나빠?&lt;br /&gt;
속여보니까 그렇지도 않던데&lt;br /&gt;
아름다운 이 세계는&lt;br /&gt;
정말 다루기 쉬워&lt;br /&gt;
&lt;br /&gt;
Night of the Boides&lt;br /&gt;
춤춰라 좀비들의 무도회&lt;br /&gt;
숨이 멎은 이 곳에 억지로 펌핑&lt;br /&gt;
Night of the Boides&lt;br /&gt;
방부제 냄새가 진동하는 이 곳&lt;br /&gt;
너희도 나랑 함께 춤을 추는거야&lt;br /&gt;
사악한 라라와 함께하는 세상&lt;br /&gt;
&lt;br /&gt;
나보다 잘나가는 녀석들이 미워&lt;br /&gt;
정말로 증오스럽고 가증스러워&lt;br /&gt;
전부 모조리 죽어버리면 좋겠어&lt;br /&gt;
내가 위로 올라가면 된다니?&lt;br /&gt;
말도 안돼&lt;br /&gt;
헛소리는 전부 PASS&lt;br /&gt;
내가 잘나가는 것 보다&lt;br /&gt;
남을 끌어내리는 편이&lt;br /&gt;
그게 훨씬 쉬운 일인걸&lt;br /&gt;
&lt;br /&gt;
춤춰라 시체들의 Boogie Woogie&lt;br /&gt;
스타일은 사악하게 Woogie Boogie&lt;br /&gt;
춤춰라 시체들의 Boogie Woogie&lt;br /&gt;
스타일은 사악하게 Woogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
Boogie Boogie Boogie Boogie&lt;br /&gt;
Boogie Boogie Woogie Boogie&lt;br /&gt;
&lt;br /&gt;
Night of the Boides&lt;br /&gt;
춤춰라 좀비들의 무도회&lt;br /&gt;
숨이 멎은 이 곳에 억지로 펌핑&lt;br /&gt;
Night of the Boides&lt;br /&gt;
방부제 냄새가 진동하는 이 곳&lt;br /&gt;
너희도 나랑 함께 춤을 추는거야&lt;br /&gt;
사악한 라라와 함께하는 세상&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B8%B0%EB%8B%A4%EB%A6%B4%EA%B2%8C&amp;diff=2859</id>
		<title>기다릴게</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B8%B0%EB%8B%A4%EB%A6%B4%EA%B2%8C&amp;diff=2859"/>
		<updated>2026-09-04T13:51:28Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=-eZdVADWjSc&lt;br /&gt;
|제작자=신창섭 팬&lt;br /&gt;
|제작방법=Suno&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵|창드컵=2024년 5편}}&lt;br /&gt;
|공개일=2025-03-14&lt;br /&gt;
|링크={{링크|링크=https://www.youtube.com/watch?v=u_CsFuA1mVk&lt;br /&gt;
|레이블=초기 버전}}&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%ED%92%8D%EB%A5%98%EA%B0%9D%EB%AA%BD&amp;diff=2858</id>
		<title>풍류객몽</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%ED%92%8D%EB%A5%98%EA%B0%9D%EB%AA%BD&amp;diff=2858"/>
		<updated>2026-09-04T13:51:02Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: {{문서:창팝 |유튜브Id=3rA9vuxz1YI |제작자=스피노의혈육 |다른언어제목=風流客夢 |수록컵={{수록 창드컵|창드컵=2024년 5편}} }} {{창팝 기본 개요}} &amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;  == 가사 == {{가사}}  {{창팝 둘러보기}}&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=3rA9vuxz1YI&lt;br /&gt;
|제작자=스피노의혈육&lt;br /&gt;
|다른언어제목=風流客夢&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=2024년 5편}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%B5%9C%EC%B4%88%EA%B3%B5%EA%B0%9C_%EB%A6%B4%EB%A0%88%EC%9D%B4/%EC%95%84%EC%B9%B4%EC%9D%B4%EB%B8%8C/2026%EB%85%84_9%EC%9B%94&amp;diff=2857</id>
		<title>최초공개 릴레이/아카이브/2026년 9월</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%B5%9C%EC%B4%88%EA%B3%B5%EA%B0%9C_%EB%A6%B4%EB%A0%88%EC%9D%B4/%EC%95%84%EC%B9%B4%EC%9D%B4%EB%B8%8C/2026%EB%85%84_9%EC%9B%94&amp;diff=2857"/>
		<updated>2026-09-04T13:46:53Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: {{문서:월별 최초공개 릴레이 아카이브|월=2026-09}}&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:월별 최초공개 릴레이 아카이브|월=2026-09}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%83%81%EB%82%A8%EC%9E%90_%ED%8A%B9&amp;diff=2856</id>
		<title>상남자 특</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%83%81%EB%82%A8%EC%9E%90_%ED%8A%B9&amp;diff=2856"/>
		<updated>2026-09-04T13:45:23Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=0QI6tDxcNLY&lt;br /&gt;
|제작자=리스항르&lt;br /&gt;
|화자=쌀숭이&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵|창드컵=2026년 3편|수록날짜=2026-08-03|제외날짜=2026-09-04}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%A6%AC%EC%84%A0%EC%A1%B1%EC%97%90%EA%B2%8C_%EC%B0%BD%ED%8C%9D%EC%9D%84_%EB%A7%8C%EB%93%A4_%EC%9E%90%EA%B2%A9%EC%9D%B4_%EC%9E%88%EB%8A%94_%EA%B1%B8%EA%B9%8C%EC%9A%94&amp;diff=2855</id>
		<title>리선족에게 창팝을 만들 자격이 있는 걸까요</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%A6%AC%EC%84%A0%EC%A1%B1%EC%97%90%EA%B2%8C_%EC%B0%BD%ED%8C%9D%EC%9D%84_%EB%A7%8C%EB%93%A4_%EC%9E%90%EA%B2%A9%EC%9D%B4_%EC%9E%88%EB%8A%94_%EA%B1%B8%EA%B9%8C%EC%9A%94&amp;diff=2855"/>
		<updated>2026-09-04T13:45:04Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=dHdQZX3DPkk&lt;br /&gt;
|제작자=리스항르&lt;br /&gt;
|화자=쌀숭이&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵|창드컵=2026년 3편|수록날짜=2026-08-03|제외날짜=2026-09-04}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%A9%94%EC%9D%B4%ED%94%8C_%EA%B0%84%EB%A7%8C%EC%97%90_%EB%B3%B5%EA%B7%80%ED%95%A0%EA%B9%8C_%EC%8B%B6%EB%8B%A4%EA%B0%80%EB%8F%84&amp;diff=2854</id>
		<title>메이플 간만에 복귀할까 싶다가도</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%A9%94%EC%9D%B4%ED%94%8C_%EA%B0%84%EB%A7%8C%EC%97%90_%EB%B3%B5%EA%B7%80%ED%95%A0%EA%B9%8C_%EC%8B%B6%EB%8B%A4%EA%B0%80%EB%8F%84&amp;diff=2854"/>
		<updated>2026-09-04T13:44:49Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=LYc6nPEva3s&lt;br /&gt;
|제작자=리반&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵|창드컵=2026년 3편|수록날짜=2026-08-03|제외날짜=2026-09-04}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=IDOL,_ALWAYS&amp;diff=2853</id>
		<title>IDOL, ALWAYS</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=IDOL,_ALWAYS&amp;diff=2853"/>
		<updated>2026-09-04T13:44:33Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=x7DZ89ublus&lt;br /&gt;
|제작자=pretty_&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=2025년 5편}}&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵|창드컵=2026년 3편|수록날짜=2026-08-03|제외날짜=2026-09-04}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%B0%BD&amp;diff=2852</id>
		<title>창</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%B0%BD&amp;diff=2852"/>
		<updated>2026-09-04T13:43:57Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 일괄 가져오기: 26-3 패치 260904&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=-rFgRsZc2uQ&lt;br /&gt;
|제작자=리스항르&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=2026년 3편|수록일=2026-09-04}}&lt;br /&gt;
|고화질썸네일없음=예&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%BC%AC%EC%9A%B0%EB%A9%B4_%EA%BA%BC%EC%A7%80%EC%84%B8%EC%9A%94&amp;diff=2851</id>
		<title>꼬우면 꺼지세요</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%BC%AC%EC%9A%B0%EB%A9%B4_%EA%BA%BC%EC%A7%80%EC%84%B8%EC%9A%94&amp;diff=2851"/>
		<updated>2026-09-04T13:43:56Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 일괄 가져오기: 26-3 패치 260904&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=5q9c3iWCj5Q&lt;br /&gt;
|제작자=머미슝좍&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=2026년 3편|수록일=2026-09-04}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Reboot%27s_Fault&amp;diff=2850</id>
		<title>Reboot&#039;s Fault</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Reboot%27s_Fault&amp;diff=2850"/>
		<updated>2026-09-04T13:43:53Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 일괄 가져오기: 26-3 패치 260904&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=3nuzLR1qKvA&lt;br /&gt;
|제작자=인포딕&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=2026년 3편|수록일=2026-09-04}}&lt;br /&gt;
|별명=탓할 존재가 필요해&lt;br /&gt;
|곡명=Reboot&#039;s Fault&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%8C%80%EB%A8%B9%EC%9D%B4_%EA%B0%96%EC%B6%B0%EC%A0%B8_%EC%9E%88%EC%9C%BC%EB%8B%88%EA%B9%8C_%EC%9C%A0%EC%A0%80%EB%93%A4%EC%9D%B4_%EC%A7%80%EB%A5%B4%EB%8A%94_%EA%B1%B0%EB%9D%BC%EA%B3%A0&amp;diff=2849</id>
		<title>쌀먹이 갖춰져 있으니까 유저들이 지르는 거라고</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%8C%80%EB%A8%B9%EC%9D%B4_%EA%B0%96%EC%B6%B0%EC%A0%B8_%EC%9E%88%EC%9C%BC%EB%8B%88%EA%B9%8C_%EC%9C%A0%EC%A0%80%EB%93%A4%EC%9D%B4_%EC%A7%80%EB%A5%B4%EB%8A%94_%EA%B1%B0%EB%9D%BC%EA%B3%A0&amp;diff=2849"/>
		<updated>2026-09-04T13:43:53Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 일괄 가져오기: 26-3 패치 260904&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=ovLS4xUxecQ&lt;br /&gt;
|제작자=Black Friday&lt;br /&gt;
|화자=메벤남&lt;br /&gt;
|제작방법=Suno&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=2026년 3편|수록일=2026-09-04}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.css&amp;diff=2842</id>
		<title>미디어위키:가사.css</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.css&amp;diff=2842"/>
		<updated>2026-08-31T14:08:23Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;.mw--lyrics-container-before {&lt;br /&gt;
	margin-block: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-container[data-lyrics-viewer-multimode=&amp;quot;true&amp;quot;] .mw--lyrics-variation-container {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	gap: var(--space-md);&lt;br /&gt;
	overflow-x: scroll;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 가로 스크롤이 실제로 생겼을 때만 위 요소의 부유를 무시하고 새 줄에서 시작합니다.&lt;br /&gt;
   .mw--lyrics-container-before는 뷰어 컨테이너보다 앞에 나오는 요소이므로 :has()로 뒤쪽 형제의 하위 컨테이너를 역참조하고,&lt;br /&gt;
   스크롤 생김 여부는 스크립트가 갱신하는 data-lyrics-viewer-overflow 속성으로 판단합니다. */&lt;br /&gt;
.mw--lyrics-container-before:has(~ .mw--lyrics-container[data-lyrics-viewer-overflow=&amp;quot;true&amp;quot;]),&lt;br /&gt;
.mw--lyrics-container-before:has(~ * .mw--lyrics-container[data-lyrics-viewer-overflow=&amp;quot;true&amp;quot;]) {&lt;br /&gt;
	clear: both;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 클래스를 반복해 특이성을 높여 멀티모드 규칙(같은 특이성, 더 앞에 선언됨)보다 우선합니다 */&lt;br /&gt;
.citizen-overflow-content.citizen-overflow-content &amp;gt; .mw--lyrics-variation-container {&lt;br /&gt;
	overflow-x: initial;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 가사만 존재할 때는 뷰어 바를 우측에 부유시켜 폭이 내용만큼 줄어들게 한다 —&lt;br /&gt;
   부유된 바는 블록 흐름에서 빠지므로 뒤의 가사 본문이 같은 줄을 함께 쓸 수 있다. */&lt;br /&gt;
.mw--lyrics-viewer-bar--no-variation-toggle {&lt;br /&gt;
	float: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Citizen 스티키 헤더는 다중 선택 모드이고 변형 토글이 존재할 때만 동작하도록 고정합니다.&lt;br /&gt;
   Citizen이 밴드 클래스(citizen-overflow-sticky-row)를 사후에 부여하고 wikipage.content마다 되살리므로&lt;br /&gt;
   클래스를 제거하는 대신 스티키 이동의 근원인 transform/animation을 무효화합니다. */&lt;br /&gt;
.mw--lyrics-container:not( [data-lyrics-viewer-multimode=&amp;quot;true&amp;quot;] ) &amp;gt; .citizen-overflow-sticky-row,&lt;br /&gt;
.mw--lyrics-container:has( &amp;gt; .citizen-overflow-sticky-row.mw--lyrics-viewer-bar--no-variation-toggle ) &amp;gt; .citizen-overflow-sticky-row {&lt;br /&gt;
	transform: none !important;&lt;br /&gt;
	animation: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 뷰어 바 기본 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-bar {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	gap: var(--space-xs);&lt;br /&gt;
	flex-wrap: wrap;&lt;br /&gt;
	margin-bottom: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 변형 토글이 있을 때 설정 버튼만 우측 끝으로 밀어낸다 — float로 폭이 줄었을 때는 남는 공간이 없어 무시된다 */&lt;br /&gt;
.mw--lyrics-viewer-settings {&lt;br /&gt;
	margin-left: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-variation {&lt;br /&gt;
	max-width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Dialog 내부 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-dialog-body {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	flex-direction: column;&lt;br /&gt;
	gap: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-dialog-target {&lt;br /&gt;
	margin: 0;&lt;br /&gt;
	color: var(--color-subtle);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card {&lt;br /&gt;
	border: var(--border-subtle);&lt;br /&gt;
	border-radius: var(--border-radius-base);&lt;br /&gt;
	padding: var(--space-xs) var(--space-sm);&lt;br /&gt;
	cursor: pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card:hover {&lt;br /&gt;
	background-color: var(--background-color-button-quiet--hover);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card__header {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	gap: var(--space-xs);&lt;br /&gt;
	cursor: pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card__description {&lt;br /&gt;
	margin: var(--space-xxs) 0 0 var(--space-lg);&lt;br /&gt;
	color: var(--color-subtle);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card__input-wrap {&lt;br /&gt;
	margin: var(--space-xs) 0 0 var(--space-lg);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-dialog-preview {&lt;br /&gt;
	padding: var(--space-xs);&lt;br /&gt;
	background: var(--background-color-neutral-subtle);&lt;br /&gt;
	border-radius: var(--border-radius-base);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 편집 모달 내부 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-edit-dialog-body {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	flex-direction: column;&lt;br /&gt;
	gap: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-textarea .cdx-text-area__textarea {&lt;br /&gt;
	min-height: 50vh;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-diff {&lt;br /&gt;
	border: var(--border-subtle);&lt;br /&gt;
	border-radius: var(--border-radius-base);&lt;br /&gt;
	padding: var(--space-xs);&lt;br /&gt;
	overflow-x: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-diff-empty {&lt;br /&gt;
	margin: 0;&lt;br /&gt;
	color: var(--color-subtle);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-error {&lt;br /&gt;
	margin: 0;&lt;br /&gt;
	color: var(--color-error);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Popover 내부 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-popover-body {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	flex-direction: column;&lt;br /&gt;
	gap: var(--space-md);&lt;br /&gt;
	width: max-content;&lt;br /&gt;
	max-width: calc(100vw - 32px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-popover-row {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	justify-content: space-between;&lt;br /&gt;
	gap: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-popover-row__label {&lt;br /&gt;
	font-weight: var(--font-weight-medium);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-popover-section-title {&lt;br /&gt;
	margin-bottom: var(--space-xxs);&lt;br /&gt;
	font-weight: var(--font-weight-medium);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 가사 본문 편집 버튼 — 우상단 절대 위치 */&lt;br /&gt;
.mw--lyrics-variation .ts-lyric-content {&lt;br /&gt;
	position: relative;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-variation-edit {&lt;br /&gt;
	position: absolute;&lt;br /&gt;
	top: var(--space-xs);&lt;br /&gt;
	right: var(--space-xs);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 작은 화면에서 다른 가사 추가 버튼은 아이콘만 표시 */&lt;br /&gt;
@media (max-width: 639px) {&lt;br /&gt;
	.mw--lyrics-viewer-add__label {&lt;br /&gt;
		display: none;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.js&amp;diff=2841</id>
		<title>미디어위키:가사.js</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.js&amp;diff=2841"/>
		<updated>2026-08-31T14:08:00Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* eslint-disable mediawiki/class-doc */&lt;br /&gt;
/**&lt;br /&gt;
 * MediaWiki 가사 Viewer — MediaWiki: 네임스페이스 JS&lt;br /&gt;
 * UseResource 확장으로 직접 로드되는 클라이언트 스크립트입니다.&lt;br /&gt;
 * 리소스 모듈 등록 없이 MediaWiki: 네임스페이스의 JS 페이지로 동작합니다.&lt;br /&gt;
 * 의존성: MediaWiki 1.45+ ResourceLoader가 제공하는 vue, @wikimedia/codex (또는 codex)&lt;br /&gt;
 */&lt;br /&gt;
/* global mw, Vue, require */&lt;br /&gt;
( function () {&lt;br /&gt;
	&#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
	// 디버그 플래그 저장 키&lt;br /&gt;
	var DEBUG_STORAGE_KEY = &#039;mw-lyrics-viewer-debug&#039;;&lt;br /&gt;
&lt;br /&gt;
	// 디버그 로그 버퍼 저장 키 — 새로고침으로 콘솔이 초기화되어도 이전 세션 로그를 확인할 수 있게 버퍼링합니다.&lt;br /&gt;
	var DEBUG_LOG_STORAGE_KEY = &#039;mw-lyrics-viewer-debuglog&#039;;&lt;br /&gt;
	var DEBUG_LOG_MAX_ENTRIES = 50;&lt;br /&gt;
&lt;br /&gt;
	// 디버그 활성화 여부 확인&lt;br /&gt;
	function isDebugEnabled() {&lt;br /&gt;
		try {&lt;br /&gt;
			// 디버그 플래그가 저장되어 있으면 활성화로 반환합니다.&lt;br /&gt;
			if ( window.localStorage.getItem( DEBUG_STORAGE_KEY ) === &#039;1&#039; ) { return true; }&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
		try {&lt;br /&gt;
			return /(?:\?|&amp;amp;)lyricsViewerDebug=1(?:&amp;amp;|$)/.test( window.location.search );&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 디버그 로그 출력&lt;br /&gt;
	function debugLog() {&lt;br /&gt;
		// 디버그가 비활성화되어 있다면 중단합니다.&lt;br /&gt;
		if ( !isDebugEnabled() ) { return; }&lt;br /&gt;
&lt;br /&gt;
		var args = Array.prototype.slice.call( arguments );&lt;br /&gt;
		args.unshift( &#039;[LyricsViewer:debug]&#039; );&lt;br /&gt;
		try { window.console.log.apply( window.console, args ); } catch ( error ) {}&lt;br /&gt;
		bufferDebugLog( &#039;log&#039;, args );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 디버그 경고 출력&lt;br /&gt;
	function debugWarn() {&lt;br /&gt;
		var args = Array.prototype.slice.call( arguments );&lt;br /&gt;
		args.unshift( &#039;[LyricsViewer]&#039; );&lt;br /&gt;
		try { window.console.warn.apply( window.console, args ); } catch ( error ) {}&lt;br /&gt;
		bufferDebugLog( &#039;warn&#039;, args );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 디버그 로그를 저장소 버퍼에 기록&lt;br /&gt;
	function bufferDebugLog( level, logArguments ) {&lt;br /&gt;
		try {&lt;br /&gt;
			var entries = JSON.parse( window.sessionStorage.getItem( DEBUG_LOG_STORAGE_KEY ) || &#039;[]&#039; );&lt;br /&gt;
			entries.push( {&lt;br /&gt;
				level: level,&lt;br /&gt;
				time: new Date().toISOString(),&lt;br /&gt;
				text: Array.prototype.map.call( logArguments, function ( item ) {&lt;br /&gt;
					// 객체는 사람이 읽을 수 있는 형태로 직렬화합니다.&lt;br /&gt;
					if ( typeof item === &#039;object&#039; ) {&lt;br /&gt;
						try { return JSON.stringify( item ); } catch ( innerError ) { return String( item ); }&lt;br /&gt;
					}&lt;br /&gt;
					return String( item );&lt;br /&gt;
				} ).join( &#039; &#039; )&lt;br /&gt;
			} );&lt;br /&gt;
&lt;br /&gt;
			// 최대 개수를 넘으면 오래된 로그부터 버립니다.&lt;br /&gt;
			if ( entries.length &amp;gt; DEBUG_LOG_MAX_ENTRIES ) { entries = entries.slice( -DEBUG_LOG_MAX_ENTRIES ); }&lt;br /&gt;
			window.sessionStorage.setItem( DEBUG_LOG_STORAGE_KEY, JSON.stringify( entries ) );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 새로고침 전 세션에 기록된 디버그 로그를 콘솔로 다시 출력&lt;br /&gt;
	function dumpBufferedDebugLogs() {&lt;br /&gt;
		try {&lt;br /&gt;
			var rawEntries = window.sessionStorage.getItem( DEBUG_LOG_STORAGE_KEY );&lt;br /&gt;
			window.sessionStorage.removeItem( DEBUG_LOG_STORAGE_KEY );&lt;br /&gt;
			var entries = rawEntries ? JSON.parse( rawEntries ) : [];&lt;br /&gt;
&lt;br /&gt;
			// 이전 세션에 기록된 로그가 있다면 출력합니다.&lt;br /&gt;
			if ( entries.length ) {&lt;br /&gt;
				entries.forEach( function ( entry ) {&lt;br /&gt;
					var consoleMethod = entry.level === &#039;warn&#039; ? window.console.warn : window.console.log;&lt;br /&gt;
					consoleMethod.call( window.console, &#039;[LyricsViewer:debug@&#039; + entry.time + &#039;]&#039;, entry.text );&lt;br /&gt;
				} );&lt;br /&gt;
			}&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 전역 디버그 핸들 노출&lt;br /&gt;
	try {&lt;br /&gt;
		window.__lyricsViewerDebug = window.__lyricsViewerDebug || {};&lt;br /&gt;
		window.__lyricsViewerDebug.enable = function () {&lt;br /&gt;
			try { window.localStorage.setItem( DEBUG_STORAGE_KEY, &#039;1&#039; ); } catch ( error ) {}&lt;br /&gt;
			try { window.console.info( &#039;[LyricsViewer] 디버그 모드 활성화 — 새로고침 후 상세 로그가 출력되며, 새로고침 전 세션의 로그도 함께 출력됩니다.&#039; ); } catch ( error2 ) {}&lt;br /&gt;
		};&lt;br /&gt;
		window.__lyricsViewerDebug.disable = function () {&lt;br /&gt;
			try { window.localStorage.removeItem( DEBUG_STORAGE_KEY ); } catch ( error ) {}&lt;br /&gt;
			try { window.console.info( &#039;[LyricsViewer] 디버그 모드 비활성화&#039; ); } catch ( error2 ) {}&lt;br /&gt;
		};&lt;br /&gt;
		window.__lyricsViewerDebug.isEnabled = isDebugEnabled;&lt;br /&gt;
	} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
	// ※ Codex 아이콘은 ResourceLoader에 모듈이 존재하지 않으므로 MediaWiki 1.44+의&lt;br /&gt;
	//   list=codexicons API를 통해 필요한 아이콘만 URL 기반으로 동적 로드합니다.&lt;br /&gt;
	//   외부 CDN보다 Same-Origin이라 더 안전합니다.&lt;br /&gt;
	//   https://www.mediawiki.org/wiki/API:CodexIcons&lt;br /&gt;
	//   &amp;quot;For performance reasons, there is no ResourceLoader module named codex-icons containing all the icons&amp;quot;&lt;br /&gt;
	var API_ICON_NAMES = [ &#039;cdxIconAdd&#039;, &#039;cdxIconEdit&#039;, &#039;cdxIconSettings&#039; ];&lt;br /&gt;
&lt;br /&gt;
	// 아이콘 세션 캐시 키 — 대상 아이콘 목록이나 응답 형식이 바뀌면 버전을 올립니다.&lt;br /&gt;
	var API_ICON_STORAGE_KEY = &#039;mw-lyrics-viewer-codex-icons-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
	var apiIconCache = null;&lt;br /&gt;
	var apiIconFetchPromise = null;&lt;br /&gt;
&lt;br /&gt;
	// 라디오 그룹 고유 번호 카운터&lt;br /&gt;
	var radioGroupCounter = 0;&lt;br /&gt;
&lt;br /&gt;
	// 캐시된 아이콘을 콜백으로 전달&lt;br /&gt;
	function deliverCachedIcons( callback ) {&lt;br /&gt;
		try { callback( apiIconCache ); } catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 진행 중인 요청에 콜백 연결&lt;br /&gt;
	function attachToPendingFetch( callback ) {&lt;br /&gt;
		apiIconFetchPromise.then( function ( cachedIcons ) {&lt;br /&gt;
			try { callback( cachedIcons ); } catch ( error ) {}&lt;br /&gt;
		} ).catch( function () {} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// API 응답에서 필요한 아이콘만 추출&lt;br /&gt;
	function pickNeededIcons( iconData ) {&lt;br /&gt;
		var pickedIcons = {};&lt;br /&gt;
		API_ICON_NAMES.forEach( function ( iconName ) {&lt;br /&gt;
			pickedIcons[ iconName ] = iconData[ iconName ] || null;&lt;br /&gt;
		} );&lt;br /&gt;
		return pickedIcons;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// API URL 생성&lt;br /&gt;
	function buildCodexIconsApiUrl() {&lt;br /&gt;
		var namesParam = encodeURIComponent( API_ICON_NAMES.join( &#039;|&#039; ) );&lt;br /&gt;
		var queryString = &#039;action=query&amp;amp;list=codexicons&amp;amp;names=&#039; + namesParam + &#039;&amp;amp;format=json&amp;amp;formatversion=2&#039;;&lt;br /&gt;
		try {&lt;br /&gt;
			return mw.util.wikiScript( &#039;api&#039; ) + &#039;?&#039; + queryString;&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return &#039;/w/api.php?&#039; + queryString;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 세션 저장소에서 아이콘 캐시를 읽습니다. 형식이 맞지 않으면 캐시 없음으로 취급합니다.&lt;br /&gt;
	function readIconsFromSessionStorage() {&lt;br /&gt;
		try {&lt;br /&gt;
			var storedIcons = JSON.parse( window.sessionStorage.getItem( API_ICON_STORAGE_KEY ) || &#039;null&#039; );&lt;br /&gt;
&lt;br /&gt;
			// 필요한 아이콘 이름이 모두 갖춰진 객체인지 검사합니다.&lt;br /&gt;
			if ( !storedIcons || typeof storedIcons !== &#039;object&#039; ) { return null; }&lt;br /&gt;
			for ( var index = 0; index &amp;lt; API_ICON_NAMES.length; index++ ) {&lt;br /&gt;
				if ( !( API_ICON_NAMES[ index ] in storedIcons ) ) { return null; }&lt;br /&gt;
			}&lt;br /&gt;
			return storedIcons;&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 아이콘 캐시를 세션 저장소에 기록합니다. 실제 아이콘 데이터가 하나도 없다면 기록하지 않습니다.&lt;br /&gt;
	function saveIconsToSessionStorage( icons ) {&lt;br /&gt;
		try {&lt;br /&gt;
			var hasIcon = false;&lt;br /&gt;
			API_ICON_NAMES.forEach( function ( iconName ) {&lt;br /&gt;
				if ( icons[ iconName ] ) { hasIcon = true; }&lt;br /&gt;
			} );&lt;br /&gt;
			if ( !hasIcon ) { return; }&lt;br /&gt;
			window.sessionStorage.setItem( API_ICON_STORAGE_KEY, JSON.stringify( icons ) );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// list=codexicons API에서 아이콘 JSON 가져오기&lt;br /&gt;
	function fetchIconsFromApi( callback ) {&lt;br /&gt;
		// 캐시된 아이콘이 있다면 즉시 전달합니다.&lt;br /&gt;
		if ( apiIconCache ) { deliverCachedIcons( callback ); return; }&lt;br /&gt;
&lt;br /&gt;
		// 진행 중인 요청이 있다면 콜백만 연결합니다.&lt;br /&gt;
		if ( apiIconFetchPromise ) { attachToPendingFetch( callback ); return; }&lt;br /&gt;
&lt;br /&gt;
		// 세션 저장소에 캐시가 남아 있다면 요청 없이 사용합니다.&lt;br /&gt;
		var storedIcons = readIconsFromSessionStorage();&lt;br /&gt;
		if ( storedIcons ) {&lt;br /&gt;
			apiIconCache = storedIcons;&lt;br /&gt;
			deliverCachedIcons( callback );&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// fetch API를 사용할 수 없다면 경고를 남기고 중단합니다.&lt;br /&gt;
		if ( typeof window.fetch !== &#039;function&#039; ) { debugWarn( &#039;fetch API를 사용할 수 없어 list=codexicons에서 아이콘을 가져올 수 없습니다.&#039; ); return; }&lt;br /&gt;
&lt;br /&gt;
		var apiUrl = buildCodexIconsApiUrl();&lt;br /&gt;
&lt;br /&gt;
		// API 요청 시작&lt;br /&gt;
		apiIconFetchPromise = window.fetch( apiUrl ).then( function ( response ) {&lt;br /&gt;
			// 응답이 정상이 아니라면 에러를 던집니다.&lt;br /&gt;
			if ( !response.ok ) { throw new Error( &#039;HTTP &#039; + response.status ); }&lt;br /&gt;
			return response.json();&lt;br /&gt;
		} ).then( function ( responseData ) {&lt;br /&gt;
			var iconData = responseData &amp;amp;&amp;amp; responseData.query &amp;amp;&amp;amp; responseData.query.codexicons ? responseData.query.codexicons : responseData;&lt;br /&gt;
			var loadedIcons = pickNeededIcons( iconData );&lt;br /&gt;
			apiIconCache = loadedIcons;&lt;br /&gt;
			saveIconsToSessionStorage( loadedIcons );&lt;br /&gt;
			debugLog( &#039;list=codexicons API에서 아이콘 로드 성공:&#039;, apiUrl, &#039;iconAdd:&#039;, !!loadedIcons.cdxIconAdd, &#039;iconEdit:&#039;, !!loadedIcons.cdxIconEdit, &#039;iconSettings:&#039;, !!loadedIcons.cdxIconSettings );&lt;br /&gt;
			return loadedIcons;&lt;br /&gt;
		} ).catch( function ( fetchError ) {&lt;br /&gt;
			debugWarn( &#039;list=codexicons API에서 아이콘을 가져오지 못했습니다: &#039; + apiUrl + &#039; — &#039; + ( fetchError &amp;amp;&amp;amp; fetchError.message ) );&lt;br /&gt;
			apiIconFetchPromise = null;&lt;br /&gt;
			throw fetchError;&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		attachToPendingFetch( callback );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 브라우저 저장소 키&lt;br /&gt;
	var STORAGE_PREFIX = &#039;mw-lyrics-viewer-&#039;;&lt;br /&gt;
	var STORAGE_KEYS = {&lt;br /&gt;
		multimode: STORAGE_PREFIX + &#039;multimode&#039;,&lt;br /&gt;
		fontSize: STORAGE_PREFIX + &#039;fontSize&#039;,&lt;br /&gt;
		fontFamily: STORAGE_PREFIX + &#039;fontFamily&#039;,&lt;br /&gt;
		align: STORAGE_PREFIX + &#039;align&#039;,&lt;br /&gt;
		lineHeight: STORAGE_PREFIX + &#039;lineHeight&#039;,&lt;br /&gt;
		letterSpacing: STORAGE_PREFIX + &#039;letterSpacing&#039;&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	// UI 문자열&lt;br /&gt;
	var MESSAGES = {&lt;br /&gt;
		defaultVariationLabel: &#039;가사&#039;,&lt;br /&gt;
		documentNamespacePrefix: &#039;가사:&#039;,&lt;br /&gt;
		addButtonLabel: &#039;다른 가사 추가&#039;,&lt;br /&gt;
		addDialogTitle: &#039;다른 가사 추가&#039;,&lt;br /&gt;
		targetDocumentLabel: &#039;대상 문서:&#039;,&lt;br /&gt;
		customVariationLabel: &#039;그 외&#039;,&lt;br /&gt;
		customVariationPlaceholder: &#039;가사 변형 이름 입력&#039;,&lt;br /&gt;
		documentNameLabel: &#039;문서 이름:&#039;,&lt;br /&gt;
		createLabel: &#039;생성&#039;,&lt;br /&gt;
		editLabel: &#039;편집&#039;,&lt;br /&gt;
		editButtonAriaLabel: &#039;편집&#039;,&lt;br /&gt;
		editButtonTooltip: &#039;편집&#039;,&lt;br /&gt;
		addVariationAriaLabel: &#039;추가&#039;,&lt;br /&gt;
		addVariationTooltip: &#039;추가&#039;,&lt;br /&gt;
		closeLabel: &#039;닫기&#039;,&lt;br /&gt;
		editModalTitle: &#039;가사 편집&#039;,&lt;br /&gt;
		saveLabel: &#039;저장&#039;,&lt;br /&gt;
		editSummaryPlaceholder: &#039;편집 요약&#039;,&lt;br /&gt;
		unsavedChangesMessage: &#039;저장하지 않은 변경 사항이 있습니다. 정말로 닫으시겠습니까?&#039;,&lt;br /&gt;
		saveFailedMessage: &#039;저장에 실패했습니다. 잠시 후 다시 시도해 주세요.&#039;,&lt;br /&gt;
		diffLabel: &#039;차이 보기&#039;,&lt;br /&gt;
		noDiffLabel: &#039;차이 없음&#039;,&lt;br /&gt;
		displaySettingsTitle: &#039;가사 표시 설정&#039;,&lt;br /&gt;
		displaySettingsAriaLabel: &#039;가사 표시 설정&#039;,&lt;br /&gt;
		multiSelectLabel: &#039;다중 선택&#039;,&lt;br /&gt;
		fontSizeLabel: &#039;글자 크기&#039;,&lt;br /&gt;
		fontSizeSmall: &#039;작게&#039;,&lt;br /&gt;
		fontSizeMedium: &#039;보통&#039;,&lt;br /&gt;
		fontSizeLarge: &#039;크게&#039;,&lt;br /&gt;
		fontFamilyLabel: &#039;글꼴&#039;,&lt;br /&gt;
		fontFamilyDefault: &#039;기본&#039;,&lt;br /&gt;
		fontFamilySerif: &#039;세리프&#039;,&lt;br /&gt;
		fontFamilySans: &#039;산세리프&#039;,&lt;br /&gt;
		fontFamilyMono: &#039;고정폭&#039;,&lt;br /&gt;
		alignmentLabel: &#039;정렬&#039;,&lt;br /&gt;
		alignLeft: &#039;왼쪽&#039;,&lt;br /&gt;
		alignCenter: &#039;가운데&#039;,&lt;br /&gt;
		alignRight: &#039;오른쪽&#039;,&lt;br /&gt;
		lineHeightLabel: &#039;줄 간격&#039;,&lt;br /&gt;
		lineHeightNarrow: &#039;좁게&#039;,&lt;br /&gt;
		lineHeightNormal: &#039;보통&#039;,&lt;br /&gt;
		lineHeightWide: &#039;넓게&#039;,&lt;br /&gt;
		letterSpacingLabel: &#039;글자 간격&#039;,&lt;br /&gt;
		letterSpacingNarrow: &#039;좁게&#039;,&lt;br /&gt;
		letterSpacingNormal: &#039;보통&#039;,&lt;br /&gt;
		letterSpacingWide: &#039;넓게&#039;,&lt;br /&gt;
		variationTypeTranslation: { value: &#039;번역&#039;, label: &#039;번역&#039;, description: &#039;외국어 가사를 한국어로 번역한 가사&#039; },&lt;br /&gt;
		variationTypePronunciation: { value: &#039;발음&#039;, label: &#039;발음&#039;, description: &#039;가사를 발음에 맞게 표기한 가사&#039; },&lt;br /&gt;
		variationTypePrompt: { value: &#039;프롬프트&#039;, label: &#039;프롬프트&#039;, description: &#039;AI 음악 생성 도구 등에 사용할 수 있도록 구조나 지시문이 포함된 가사&#039; }&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	// 표준 가사 문서 유형&lt;br /&gt;
	var STANDARD_LYRICS_TYPES = [&lt;br /&gt;
		MESSAGES.variationTypeTranslation,&lt;br /&gt;
		MESSAGES.variationTypePronunciation,&lt;br /&gt;
		MESSAGES.variationTypePrompt&lt;br /&gt;
	];&lt;br /&gt;
&lt;br /&gt;
	// 스타일 매핑 테이블&lt;br /&gt;
	var FONT_SIZE_MAP = { small: &#039;0.875em&#039;, medium: &#039;&#039;, large: &#039;1.2em&#039; };&lt;br /&gt;
	var FONT_FAMILY_MAP = {&lt;br /&gt;
		default: &#039;&#039;,&lt;br /&gt;
		serif: &#039;serif&#039;,&lt;br /&gt;
		&#039;sans-serif&#039;: &#039;sans-serif&#039;,&lt;br /&gt;
		monospace: &#039;monospace&#039;&lt;br /&gt;
	};&lt;br /&gt;
	var LINE_HEIGHT_MAP = { narrow: &#039;1.4&#039;, normal: &#039;&#039;, wide: &#039;2.0&#039; };&lt;br /&gt;
	var LETTER_SPACING_MAP = { narrow: &#039;-0.02em&#039;, normal: &#039;&#039;, wide: &#039;0.06em&#039; };&lt;br /&gt;
&lt;br /&gt;
	// Viewer 템플릿&lt;br /&gt;
	var VIEWER_TEMPLATE = `&amp;lt;div v-if=&amp;quot;shouldShowVariationToggle&amp;quot; class=&amp;quot;mw--lyrics-viewer-variation&amp;quot;&amp;gt;&amp;lt;cdx-toggle-button-group v-model=&amp;quot;toggleValue&amp;quot; :buttons=&amp;quot;variationToggleButtons&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw--lyrics-viewer-add&amp;quot;&amp;gt;&amp;lt;cdx-button @click=&amp;quot;dialogOpen = true&amp;quot; :aria-label=&amp;quot;messages.addButtonLabel&amp;quot; v-tooltip=&amp;quot;tooltipDirectiveAvailable ? messages.addButtonLabel : null&amp;quot;&amp;gt;&amp;lt;cdx-icon v-if=&amp;quot;iconAdd&amp;quot; :icon=&amp;quot;iconAdd&amp;quot;&amp;gt;&amp;lt;/cdx-icon&amp;gt; &amp;lt;span class=&amp;quot;mw--lyrics-viewer-add__label&amp;quot;&amp;gt;{{ messages.addButtonLabel }}&amp;lt;/span&amp;gt;&amp;lt;/cdx-button&amp;gt;&lt;br /&gt;
  &amp;lt;cdx-dialog v-model:open=&amp;quot;dialogOpen&amp;quot; :title=&amp;quot;messages.addDialogTitle&amp;quot; :use-close-button=&amp;quot;true&amp;quot; :primary-action=&amp;quot;primaryAction&amp;quot; :default-action=&amp;quot;defaultAction&amp;quot; @primary=&amp;quot;handlePrimaryAction&amp;quot; @default=&amp;quot;dialogOpen = false&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mw--lyrics-viewer-dialog-body&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;p class=&amp;quot;mw--lyrics-viewer-dialog-target&amp;quot;&amp;gt;{{ messages.targetDocumentLabel }} &amp;lt;strong&amp;gt;{{ songPageTitle }}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
      &amp;lt;div v-for=&amp;quot;lyricType in standardTypes&amp;quot; :key=&amp;quot;lyricType.value&amp;quot; class=&amp;quot;mw--lyrics-viewer-type-card&amp;quot; :style=&amp;quot;{ background: selectedType===lyricType.value ? &#039;var(--background-color-progressive-subtle)&#039; : &#039;&#039; }&amp;quot; @click=&amp;quot;selectedType=lyricType.value&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;label class=&amp;quot;mw--lyrics-viewer-type-card__header&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;radio&amp;quot; :name=&amp;quot;lyricTypeRadioGroupName&amp;quot; :value=&amp;quot;lyricType.value&amp;quot; v-model=&amp;quot;selectedType&amp;quot; /&amp;gt; &amp;lt;strong&amp;gt;{{ lyricType.label }}&amp;lt;/strong&amp;gt;&amp;lt;/label&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;mw--lyrics-viewer-type-card__description&amp;quot;&amp;gt;{{ lyricType.description }}&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div class=&amp;quot;mw--lyrics-viewer-type-card&amp;quot; :style=&amp;quot;{ background: selectedType===&#039;__custom&#039; ? &#039;var(--background-color-progressive-subtle)&#039; : &#039;&#039; }&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;label class=&amp;quot;mw--lyrics-viewer-type-card__header&amp;quot; @click=&amp;quot;selectedType=&#039;__custom&#039;&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;radio&amp;quot; :name=&amp;quot;lyricTypeRadioGroupName&amp;quot; value=&amp;quot;__custom&amp;quot; v-model=&amp;quot;selectedType&amp;quot; /&amp;gt; &amp;lt;strong&amp;gt;{{ messages.customVariationLabel }}&amp;lt;/strong&amp;gt;&amp;lt;/label&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;mw--lyrics-viewer-type-card__input-wrap&amp;quot;&amp;gt;&amp;lt;cdx-text-input v-model=&amp;quot;customVariationName&amp;quot; :placeholder=&amp;quot;messages.customVariationPlaceholder&amp;quot; @focus=&amp;quot;selectedType=&#039;__custom&#039;&amp;quot;&amp;gt;&amp;lt;/cdx-text-input&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div v-if=&amp;quot;previewDocumentTitle&amp;quot; class=&amp;quot;mw--lyrics-viewer-dialog-preview&amp;quot;&amp;gt;{{ messages.documentNameLabel }} &amp;lt;code&amp;gt;{{ previewDocumentTitle }}&amp;lt;/code&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/cdx-dialog&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw--lyrics-viewer-settings&amp;quot;&amp;gt;&amp;lt;cdx-button ref=&amp;quot;settingsButtonAnchorRef&amp;quot; :aria-label=&amp;quot;messages.displaySettingsAriaLabel&amp;quot; v-tooltip=&amp;quot;tooltipDirectiveAvailable ? messages.displaySettingsAriaLabel : null&amp;quot; @click=&amp;quot;popoverOpen = !popoverOpen&amp;quot;&amp;gt;&amp;lt;cdx-icon v-if=&amp;quot;iconSettings&amp;quot; :icon=&amp;quot;iconSettings&amp;quot;&amp;gt;&amp;lt;/cdx-icon&amp;gt;&amp;lt;/cdx-button&amp;gt;&lt;br /&gt;
  &amp;lt;cdx-popover v-model:open=&amp;quot;popoverOpen&amp;quot; :anchor=&amp;quot;settingsButtonAnchorEl&amp;quot; :title=&amp;quot;messages.displaySettingsTitle&amp;quot; :use-close-button=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-body&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-row&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;span class=&amp;quot;mw--lyrics-viewer-popover-row__label&amp;quot;&amp;gt;{{ messages.multiSelectLabel }}&amp;lt;/span&amp;gt;&lt;br /&gt;
        &amp;lt;cdx-toggle-switch v-if=&amp;quot;isToggleSwitchAvailable&amp;quot; :model-value=&amp;quot;displayState.isMultimodeEnabled&amp;quot; @update:model-value=&amp;quot;handleMultimodeChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-switch&amp;gt;&lt;br /&gt;
        &amp;lt;input v-else type=&amp;quot;checkbox&amp;quot; :checked=&amp;quot;displayState.isMultimodeEnabled&amp;quot; @change=&amp;quot;handleMultimodeChange($event.target.checked)&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.fontSizeLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.fontSize&amp;quot; :buttons=&amp;quot;fontSizeButtons&amp;quot; @update:model-value=&amp;quot;handleFontSizeChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.fontFamilyLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.fontFamily&amp;quot; :buttons=&amp;quot;fontFamilyButtons&amp;quot; @update:model-value=&amp;quot;handleFontFamilyChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.alignmentLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.textAlignment&amp;quot; :buttons=&amp;quot;alignmentButtons&amp;quot; @update:model-value=&amp;quot;handleAlignmentChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.lineHeightLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.lineHeight&amp;quot; :buttons=&amp;quot;lineHeightButtons&amp;quot; @update:model-value=&amp;quot;handleLineHeightChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.letterSpacingLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.letterSpacing&amp;quot; :buttons=&amp;quot;letterSpacingButtons&amp;quot; @update:model-value=&amp;quot;handleLetterSpacingChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/cdx-popover&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;cdx-dialog v-model:open=&amp;quot;editModalOpen&amp;quot; :title=&amp;quot;messages.editModalTitle&amp;quot; :use-close-button=&amp;quot;true&amp;quot; :primary-action=&amp;quot;saveAction&amp;quot; :default-action=&amp;quot;cancelAction&amp;quot; @primary=&amp;quot;handleEditSave&amp;quot; @default=&amp;quot;editModalOpen = false&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div class=&amp;quot;mw--lyrics-viewer-edit-dialog-body&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;p class=&amp;quot;mw--lyrics-viewer-dialog-target&amp;quot;&amp;gt;{{ messages.targetDocumentLabel }} &amp;lt;strong v-if=&amp;quot;editTargetUrl&amp;quot;&amp;gt;&amp;lt;a :href=&amp;quot;editTargetUrl&amp;quot;&amp;gt;{{ editTargetTitle }}&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong v-else&amp;gt;{{ editTargetTitle }}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;cdx-text-area v-model=&amp;quot;wikitext&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-textarea&amp;quot; :disabled=&amp;quot;isFetchingWikitext&amp;quot;&amp;gt;&amp;lt;/cdx-text-area&amp;gt;&lt;br /&gt;
    &amp;lt;cdx-text-input v-model=&amp;quot;editSummary&amp;quot; :placeholder=&amp;quot;messages.editSummaryPlaceholder&amp;quot; :disabled=&amp;quot;isSavingWikitext&amp;quot;&amp;gt;&amp;lt;/cdx-text-input&amp;gt;&lt;br /&gt;
    &amp;lt;div&amp;gt;&amp;lt;cdx-button size=&amp;quot;small&amp;quot; :disabled=&amp;quot;isDiffLoading&amp;quot; @click=&amp;quot;handleDiffRefresh&amp;quot;&amp;gt;{{ messages.diffLabel }}&amp;lt;/cdx-button&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;div v-if=&amp;quot;isDiffShown&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-diff&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;table v-if=&amp;quot;diffHtml&amp;quot; class=&amp;quot;diff&amp;quot;&amp;gt;&amp;lt;colgroup&amp;gt;&amp;lt;col class=&amp;quot;diff-marker&amp;quot;&amp;gt;&amp;lt;col class=&amp;quot;diff-content&amp;quot;&amp;gt;&amp;lt;col class=&amp;quot;diff-marker&amp;quot;&amp;gt;&amp;lt;col class=&amp;quot;diff-content&amp;quot;&amp;gt;&amp;lt;/colgroup&amp;gt;&amp;lt;tbody v-html=&amp;quot;diffHtml&amp;quot;&amp;gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;br /&gt;
      &amp;lt;p v-else-if=&amp;quot;diffErrorMessage&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-error&amp;quot;&amp;gt;{{ diffErrorMessage }}&amp;lt;/p&amp;gt;&lt;br /&gt;
      &amp;lt;p v-else class=&amp;quot;mw--lyrics-viewer-edit-diff-empty&amp;quot;&amp;gt;{{ messages.noDiffLabel }}&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;cdx-message v-if=&amp;quot;isMessageAvailable &amp;amp;&amp;amp; editErrorMessage&amp;quot; type=&amp;quot;error&amp;quot;&amp;gt;{{ editErrorMessage }}&amp;lt;/cdx-message&amp;gt;&lt;br /&gt;
    &amp;lt;p v-else-if=&amp;quot;editErrorMessage&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-error&amp;quot;&amp;gt;{{ editErrorMessage }}&amp;lt;/p&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/cdx-dialog&amp;gt;`;&lt;br /&gt;
&lt;br /&gt;
	// localStorage 읽기&lt;br /&gt;
	function readFromStorage( key ) {&lt;br /&gt;
		try { return window.localStorage.getItem( key ); } catch ( error ) { return null; }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// localStorage 쓰기&lt;br /&gt;
	function writeToStorage( key, value ) {&lt;br /&gt;
		try { window.localStorage.setItem( key, value ); } catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// API 에러를 사용자에게 표시할 메시지로 변환&lt;br /&gt;
	function formatApiError( apiError ) {&lt;br /&gt;
		var errorData = apiError &amp;amp;&amp;amp; apiError.error;&lt;br /&gt;
		// 표준 API 에러라면 코드와 설명을 함께 표시합니다.&lt;br /&gt;
		if ( errorData &amp;amp;&amp;amp; errorData.info ) { return ( errorData.code ? errorData.code + &#039;: &#039; : &#039;&#039; ) + errorData.info; }&lt;br /&gt;
		return String( ( apiError &amp;amp;&amp;amp; ( apiError.text || apiError.exception ) ) || apiError );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// mw.Api 실패는 (코드, 결과) 인자로 거부되지만 .then 체인에서는 코드 문자열만 전달되므로,&lt;br /&gt;
	// 모든 인자를 받는 .fail에서 결과 객체를 묶어 다시 거부합니다.&lt;br /&gt;
	function normalizeApiPromise( apiPromise ) {&lt;br /&gt;
		var wrapped = window.jQuery.Deferred();&lt;br /&gt;
		apiPromise.done( function ( responseData ) {&lt;br /&gt;
			wrapped.resolve( responseData );&lt;br /&gt;
		} );&lt;br /&gt;
		apiPromise.fail( function ( apiErrorCode, apiResult ) {&lt;br /&gt;
			var errorData = apiResult &amp;amp;&amp;amp; ( apiResult.error || ( apiResult.errors &amp;amp;&amp;amp; apiResult.errors[ 0 ] ) ) || null;&lt;br /&gt;
			wrapped.reject( {&lt;br /&gt;
				code: apiErrorCode,&lt;br /&gt;
				error: errorData,&lt;br /&gt;
				text: apiResult &amp;amp;&amp;amp; apiResult.text,&lt;br /&gt;
				exception: apiResult &amp;amp;&amp;amp; apiResult.exception&lt;br /&gt;
			} );&lt;br /&gt;
		} );&lt;br /&gt;
		return wrapped.promise();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// MediaWiki API가 받아들이는 ISO 8601 타임스탬프 생성&lt;br /&gt;
	function toApiTimestamp( date ) {&lt;br /&gt;
		return date.toISOString().replace( /\.\d+Z$/, &#039;Z&#039; );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 문서 위키텍스트 조회/저장 헬퍼 생성&lt;br /&gt;
	function createLyricsRepository( mwApi ) {&lt;br /&gt;
		return {&lt;br /&gt;
			fetchWikitext: function ( documentTitle ) {&lt;br /&gt;
				// 편집 시작 시각 — 저장 시 starttimestamp로 전달해 그 사이 문서 이동/삭제를 감지합니다.&lt;br /&gt;
				var fetchStartTimestamp = toApiTimestamp( new Date() );&lt;br /&gt;
				return normalizeApiPromise( mwApi.get( {&lt;br /&gt;
					action: &#039;query&#039;,&lt;br /&gt;
					prop: &#039;revisions&#039;,&lt;br /&gt;
					titles: documentTitle,&lt;br /&gt;
					rvslots: &#039;main&#039;,&lt;br /&gt;
					rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
					formatversion: 2&lt;br /&gt;
				} ) ).then( function ( responseData ) {&lt;br /&gt;
					var page = responseData &amp;amp;&amp;amp; responseData.query &amp;amp;&amp;amp; responseData.query.pages ? responseData.query.pages[ 0 ] : null;&lt;br /&gt;
&lt;br /&gt;
					// 문서가 존재하지 않으면 빈 내용의 새 문서로 취급합니다.&lt;br /&gt;
					if ( !page || page.missing ) { return { text: &#039;&#039;, baseTimestamp: &#039;&#039;, startTimestamp: fetchStartTimestamp }; }&lt;br /&gt;
					var revision = page.revisions &amp;amp;&amp;amp; page.revisions[ 0 ];&lt;br /&gt;
					return {&lt;br /&gt;
						text: revision &amp;amp;&amp;amp; revision.slots &amp;amp;&amp;amp; revision.slots.main ? revision.slots.main.content : &#039;&#039;,&lt;br /&gt;
						baseTimestamp: revision ? revision.timestamp : &#039;&#039;,&lt;br /&gt;
						startTimestamp: fetchStartTimestamp&lt;br /&gt;
					};&lt;br /&gt;
				} );&lt;br /&gt;
			},&lt;br /&gt;
			saveWikitext: function ( documentTitle, text, summary, baseTimestamp, startTimestamp ) {&lt;br /&gt;
				var editParams = {&lt;br /&gt;
					action: &#039;edit&#039;,&lt;br /&gt;
					title: documentTitle,&lt;br /&gt;
					text: text,&lt;br /&gt;
					summary: summary&lt;br /&gt;
				};&lt;br /&gt;
				// 불러온 판의 시각과 편집 시작 시각을 넘겨 편집 충돌을 감지합니다.&lt;br /&gt;
				if ( baseTimestamp ) { editParams.basetimestamp = baseTimestamp; }&lt;br /&gt;
				if ( startTimestamp ) { editParams.starttimestamp = startTimestamp; }&lt;br /&gt;
				return normalizeApiPromise( mwApi.postWithEditToken( editParams ) ).then( function ( responseData ) {&lt;br /&gt;
					// 무변경(null edit) 편집도 성공으로 처리합니다.&lt;br /&gt;
					return !!( responseData &amp;amp;&amp;amp; responseData.edit &amp;amp;&amp;amp; responseData.edit.result === &#039;Success&#039; );&lt;br /&gt;
				} );&lt;br /&gt;
			},&lt;br /&gt;
			purgePage: function ( documentTitle ) {&lt;br /&gt;
				return normalizeApiPromise( mwApi.post( {&lt;br /&gt;
					action: &#039;purge&#039;,&lt;br /&gt;
					titles: documentTitle,&lt;br /&gt;
					forcelinkupdate: 1&lt;br /&gt;
				} ) );&lt;br /&gt;
			},&lt;br /&gt;
			fetchWikitextDiff: function ( fromText, toText ) {&lt;br /&gt;
				return normalizeApiPromise( mwApi.get( {&lt;br /&gt;
					action: &#039;compare&#039;,&lt;br /&gt;
					fromslots: &#039;main&#039;,&lt;br /&gt;
					&#039;fromtext-main&#039;: fromText,&lt;br /&gt;
					toslots: &#039;main&#039;,&lt;br /&gt;
					&#039;totext-main&#039;: toText,&lt;br /&gt;
					difftype: &#039;table&#039;,&lt;br /&gt;
					formatversion: 2&lt;br /&gt;
				} ) ).then( function ( responseData ) {&lt;br /&gt;
					return responseData &amp;amp;&amp;amp; responseData.compare &amp;amp;&amp;amp; responseData.compare.body ? responseData.compare.body : &#039;&#039;;&lt;br /&gt;
				} );&lt;br /&gt;
			}&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 현재 문서 제목 반환 — wgPageName은 URL 형식이므로 밑줄을 공백으로 되돌립니다.&lt;br /&gt;
	function getCurrentPageTitle() {&lt;br /&gt;
		try { return String( mw.config.get( &#039;wgPageName&#039; ) || &#039;&#039; ).replace( /_/g, &#039; &#039; ); } catch ( error ) { return &#039;&#039;; }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 문서 제목 생성 — mw.Title로 정규화된 문서 제목을 만들어 반환합니다.&lt;br /&gt;
	// 곡 문서 제목이나 변형 이름에 제목으로 쓸 수 없는 문자가 포함되었다면 null을 반환합니다.&lt;br /&gt;
	function buildLyricsDocumentTitle( songPageTitle, variationName ) {&lt;br /&gt;
		var rawTitle = MESSAGES.documentNamespacePrefix + songPageTitle + ( variationName ? &#039;/&#039; + variationName : &#039;&#039; );&lt;br /&gt;
		try {&lt;br /&gt;
			var title = mw.Title.newFromText( rawTitle );&lt;br /&gt;
			return title ? title.getPrefixedText() : null;&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 전역 표시 상태 생성&lt;br /&gt;
	function createGlobalDisplayState( Vue ) {&lt;br /&gt;
		return Vue.reactive( {&lt;br /&gt;
			isMultimodeEnabled: readFromStorage( STORAGE_KEYS.multimode ) === &#039;true&#039;,&lt;br /&gt;
			fontSize: readFromStorage( STORAGE_KEYS.fontSize ) || &#039;medium&#039;,&lt;br /&gt;
			fontFamily: readFromStorage( STORAGE_KEYS.fontFamily ) || &#039;default&#039;,&lt;br /&gt;
			textAlignment: readFromStorage( STORAGE_KEYS.align ) || &#039;left&#039;,&lt;br /&gt;
			lineHeight: readFromStorage( STORAGE_KEYS.lineHeight ) || &#039;normal&#039;,&lt;br /&gt;
			letterSpacing: readFromStorage( STORAGE_KEYS.letterSpacing ) || &#039;normal&#039;&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 다른 탭의 저장소 변경을 전역 상태에 동기화&lt;br /&gt;
	function syncDisplayStateWithStorage( globalDisplayState ) {&lt;br /&gt;
		try {&lt;br /&gt;
			window.addEventListener( &#039;storage&#039;, function ( storageEvent ) {&lt;br /&gt;
				// 변경된 저장소 키에 따라 상태를 갱신합니다.&lt;br /&gt;
				if ( storageEvent.key === STORAGE_KEYS.multimode ) {&lt;br /&gt;
					globalDisplayState.isMultimodeEnabled = storageEvent.newValue === &#039;true&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.fontSize ) {&lt;br /&gt;
					globalDisplayState.fontSize = storageEvent.newValue || &#039;medium&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.fontFamily ) {&lt;br /&gt;
					globalDisplayState.fontFamily = storageEvent.newValue || &#039;default&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.align ) {&lt;br /&gt;
					globalDisplayState.textAlignment = storageEvent.newValue || &#039;left&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.lineHeight ) {&lt;br /&gt;
					globalDisplayState.lineHeight = storageEvent.newValue || &#039;normal&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.letterSpacing ) {&lt;br /&gt;
					globalDisplayState.letterSpacing = storageEvent.newValue || &#039;normal&#039;;&lt;br /&gt;
				}&lt;br /&gt;
			} );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 변형 데이터 수집&lt;br /&gt;
	function collectVariationData( viewerContainer ) {&lt;br /&gt;
		var variationElements = Array.from(&lt;br /&gt;
			viewerContainer.querySelectorAll( &#039;.mw--lyrics-variation[data-lyric-variation]&#039; )&lt;br /&gt;
		);&lt;br /&gt;
		var variationValues = variationElements.map( function ( element ) {&lt;br /&gt;
			return element.getAttribute( &#039;data-lyric-variation&#039; ) || &#039;&#039;;&lt;br /&gt;
		} );&lt;br /&gt;
		var hasNonDefaultVariation = variationValues.some( function ( value ) { return value !== &#039;&#039;; } );&lt;br /&gt;
		var existingVariationSet = new Set( variationValues );&lt;br /&gt;
		var variationToggleButtons = variationValues.map( function ( value ) {&lt;br /&gt;
			return { value: value, label: value === &#039;&#039; ? MESSAGES.defaultVariationLabel : value };&lt;br /&gt;
		} );&lt;br /&gt;
		var shouldShowVariationToggle = hasNonDefaultVariation &amp;amp;&amp;amp; variationValues.length &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			variationElements: variationElements,&lt;br /&gt;
			variationValues: variationValues,&lt;br /&gt;
			existingVariationSet: existingVariationSet,&lt;br /&gt;
			variationToggleButtons: variationToggleButtons,&lt;br /&gt;
			shouldShowVariationToggle: shouldShowVariationToggle&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 서버 렌더링된 뷰어 바 참조&lt;br /&gt;
	function findViewerBar( viewerContainer ) {&lt;br /&gt;
		var viewerBar = viewerContainer.querySelector( &#039;:scope &amp;gt; .mw--lyrics-viewer-bar&#039; );&lt;br /&gt;
&lt;br /&gt;
		// 뷰어 바가 없다면 경고를 남기고 null을 반환합니다.&lt;br /&gt;
		if ( !viewerBar ) {&lt;br /&gt;
			debugWarn( &#039;컨테이너 안에서 .mw--lyrics-viewer-bar를 찾을 수 없어 Viewer를 초기화하지 않습니다. 서버 마크업에 뷰어 바가 렌더링되어 있는지 확인하세요.&#039; );&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return viewerBar;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가로 넘침이 실제로 생겼는지 확인해 컨테이너 속성을 갱신&lt;br /&gt;
	function updateOverflowState( viewerContainer ) {&lt;br /&gt;
		// 변형 컨테이너가 없다면 경고를 남기고 판단하지 않습니다.&lt;br /&gt;
		var variationContainer = viewerContainer.querySelector( &#039;.mw--lyrics-variation-container&#039; );&lt;br /&gt;
		if ( !variationContainer ) {&lt;br /&gt;
			debugWarn( &#039;컨테이너 안에서 .mw--lyrics-variation-container를 찾을 수 없어 가로 넘침을 판정하지 않습니다.&#039; );&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// float 해제(clear)가 적용된 상태에서는 폭이 넓어져 판정이 뒤집히고, 해제→재적용이 무한 반복될 수 있습니다.&lt;br /&gt;
		// 속성을 잠시 지운 &#039;해제 상태&#039;에서 측정해 판정한 뒤 필요하면 다시 적용합니다 — 동기 실행이라 중간 상태는 화면에 그려지지 않습니다.&lt;br /&gt;
		if ( viewerContainer.getAttribute( &#039;data-lyrics-viewer-overflow&#039; ) === &#039;true&#039; ) {&lt;br /&gt;
			viewerContainer.removeAttribute( &#039;data-lyrics-viewer-overflow&#039; );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 변형 컨테이너 자신의 넘침은 overflow-X 값과 무관하게 검사합니다 —&lt;br /&gt;
		// Citizen 스킨처럼 컨테이너가 직접 스크롤하지 않고 넘친 내용을 상위 상자가 스크롤/클립하는 환경이 있기 때문입니다.&lt;br /&gt;
		var hasOverflow = variationContainer.scrollWidth &amp;gt; variationContainer.clientWidth;&lt;br /&gt;
&lt;br /&gt;
		if ( !hasOverflow ) {&lt;br /&gt;
			// 컨테이너에서 넘치지 않았다면 넘친 내용을 잘라내는 상위 상자가 있는지 확인합니다.&lt;br /&gt;
			var current = variationContainer.parentElement;&lt;br /&gt;
			while ( current &amp;amp;&amp;amp; current !== document.documentElement ) {&lt;br /&gt;
				if ( current.scrollWidth &amp;gt; current.clientWidth ) {&lt;br /&gt;
					var overflowX = window.getComputedStyle( current ).overflowX;&lt;br /&gt;
					if ( overflowX !== &#039;visible&#039; ) { hasOverflow = true; break; }&lt;br /&gt;
				}&lt;br /&gt;
				current = current.parentElement;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		debugLog( &#039;가로 넘침 판정:&#039;, variationContainer, &#039;컨테이너:&#039;, variationContainer.scrollWidth + &#039;/&#039; + variationContainer.clientWidth, &#039;→ 넘침:&#039;, hasOverflow );&lt;br /&gt;
&lt;br /&gt;
		if ( hasOverflow ) { viewerContainer.setAttribute( &#039;data-lyrics-viewer-overflow&#039;, &#039;true&#039; ); }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 표시/숨김 적용 함수 생성&lt;br /&gt;
	function createVariationApplier( viewerContainer, variationElements ) {&lt;br /&gt;
		// 창 크기 변경 등으로 컨테이너 폭이 변하면 스크롤 여부를 다시 판단합니다.&lt;br /&gt;
		try {&lt;br /&gt;
			var variationContainer = viewerContainer.querySelector( &#039;.mw--lyrics-variation-container&#039; );&lt;br /&gt;
			if ( variationContainer &amp;amp;&amp;amp; typeof window.ResizeObserver === &#039;function&#039; ) {&lt;br /&gt;
				new window.ResizeObserver( function () { updateOverflowState( viewerContainer ); } ).observe( variationContainer );&lt;br /&gt;
			}&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
		return function ( singleSelectedValue, multiSelectedValues, isMultimodeActive ) {&lt;br /&gt;
			// 다중 선택 여부에 따라 표시할 집합을 결정합니다.&lt;br /&gt;
			var visibleSet;&lt;br /&gt;
			if ( isMultimodeActive ) {&lt;br /&gt;
				visibleSet = new Set( multiSelectedValues.length ? multiSelectedValues : [ singleSelectedValue ] );&lt;br /&gt;
			} else {&lt;br /&gt;
				visibleSet = new Set( [ singleSelectedValue ] );&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// 각 변형 요소의 표시 여부 갱신&lt;br /&gt;
			variationElements.forEach( function ( element ) {&lt;br /&gt;
				var variationValue = element.getAttribute( &#039;data-lyric-variation&#039; ) || &#039;&#039;;&lt;br /&gt;
				element.hidden = !visibleSet.has( variationValue );&lt;br /&gt;
			} );&lt;br /&gt;
&lt;br /&gt;
			// 라벨 표시를 다중 선택 모드에 맞춰 갱신합니다.&lt;br /&gt;
			viewerContainer.querySelectorAll( &#039;.ts-lyric-label&#039; ).forEach( function ( lyricLabel ) { lyricLabel.hidden = !isMultimodeActive; } );&lt;br /&gt;
&lt;br /&gt;
			// 다중 선택 모드에 따라 컨테이너 속성을 갱신합니다.&lt;br /&gt;
			if ( isMultimodeActive ) {&lt;br /&gt;
				viewerContainer.setAttribute( &#039;data-lyrics-viewer-multimode&#039;, &#039;true&#039; );&lt;br /&gt;
			} else {&lt;br /&gt;
				viewerContainer.removeAttribute( &#039;data-lyrics-viewer-multimode&#039; );&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// 표시 변화로 가로 스크롤 생김 여부가 달라질 수 있으니 함께 갱신합니다.&lt;br /&gt;
			updateOverflowState( viewerContainer );&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 글자 크기/글꼴/정렬/줄 간격/글자 간격 적용 함수 생성&lt;br /&gt;
	function createStyleApplier( viewerContainer ) {&lt;br /&gt;
		return function ( fontSizeValue, fontFamilyValue, alignmentValue, lineHeightValue, letterSpacingValue ) {&lt;br /&gt;
			// 각 가사 본문에 스타일 적용&lt;br /&gt;
			viewerContainer.querySelectorAll( &#039;.ts-lyric-content&#039; ).forEach( function ( contentElement ) {&lt;br /&gt;
				contentElement.style.fontSize = FONT_SIZE_MAP[ fontSizeValue ] || &#039;&#039;;&lt;br /&gt;
				contentElement.style.fontFamily = FONT_FAMILY_MAP[ fontFamilyValue ] || &#039;&#039;;&lt;br /&gt;
				contentElement.style.lineHeight = LINE_HEIGHT_MAP[ lineHeightValue ] || &#039;&#039;;&lt;br /&gt;
				contentElement.style.letterSpacing = LETTER_SPACING_MAP[ letterSpacingValue ] || &#039;&#039;;&lt;br /&gt;
				// 정렬 값에 따라 텍스트 정렬을 갱신합니다.&lt;br /&gt;
				if ( alignmentValue === &#039;left&#039; || !alignmentValue ) {&lt;br /&gt;
					contentElement.style.textAlign = &#039;&#039;;&lt;br /&gt;
				} else {&lt;br /&gt;
					contentElement.style.textAlign = alignmentValue;&lt;br /&gt;
				}&lt;br /&gt;
			} );&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 대상 컨테이너 수집&lt;br /&gt;
	function collectViewerTargets( seenContainers ) {&lt;br /&gt;
		var allMainMarkers = document.querySelectorAll( &#039;[data-lyric-mainpage]&#039; );&lt;br /&gt;
		var targets = [];&lt;br /&gt;
&lt;br /&gt;
		allMainMarkers.forEach( function ( mainMarkerElement ) {&lt;br /&gt;
			var viewerContainer = mainMarkerElement.closest( &#039;.mw--lyrics-container&#039; );&lt;br /&gt;
&lt;br /&gt;
			// 유효하지 않은 컨테이너는 건너뜁니다.&lt;br /&gt;
			if ( !viewerContainer ) { return; }&lt;br /&gt;
&lt;br /&gt;
			// 이미 처리한 컨테이너는 건너뜁니다.&lt;br /&gt;
			if ( seenContainers.has( viewerContainer ) ) { return; }&lt;br /&gt;
			seenContainers.add( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
			// 마운트에 성공한 컨테이너는 건너뜁니다.&lt;br /&gt;
			if ( viewerContainer.dataset.lyricsViewerMounted === &#039;1&#039; ) { return; }&lt;br /&gt;
&lt;br /&gt;
			targets.push( { mainMarkerElement: mainMarkerElement, viewerContainer: viewerContainer } );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return targets;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Codex 아이콘 로드 및 디버그 상태 노출&lt;br /&gt;
	function setupCodexIcons( Codex, iconAddRef, iconEditRef, iconSettingsRef ) {&lt;br /&gt;
		var CdxIcon = Codex.CdxIcon;&lt;br /&gt;
&lt;br /&gt;
		var initialApiUrl = buildCodexIconsApiUrl();&lt;br /&gt;
		debugLog( &#039;list=codexicons API에서 아이콘 동적 로드:&#039;, initialApiUrl );&lt;br /&gt;
		fetchIconsFromApi( function ( apiIcons ) {&lt;br /&gt;
			// 받은 아이콘을 ref에 반영합니다.&lt;br /&gt;
			if ( apiIcons.cdxIconAdd ) { iconAddRef.value = apiIcons.cdxIconAdd; debugLog( &#039;API iconAdd 적용 완료&#039; ); }&lt;br /&gt;
			if ( apiIcons.cdxIconEdit ) { iconEditRef.value = apiIcons.cdxIconEdit; debugLog( &#039;API iconEdit 적용 완료&#039; ); }&lt;br /&gt;
			if ( apiIcons.cdxIconSettings ) { iconSettingsRef.value = apiIcons.cdxIconSettings; debugLog( &#039;API iconSettings 적용 완료&#039; ); }&lt;br /&gt;
			try {&lt;br /&gt;
				window.__lyricsViewerDebug.lastApiIcons = apiIcons;&lt;br /&gt;
				window.__lyricsViewerDebug.lastIconAdd = iconAddRef.value;&lt;br /&gt;
				window.__lyricsViewerDebug.lastIconEdit = iconEditRef.value;&lt;br /&gt;
				window.__lyricsViewerDebug.lastIconSettings = iconSettingsRef.value;&lt;br /&gt;
			} catch ( error2 ) {}&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		try {&lt;br /&gt;
			window.__lyricsViewerDebug.lastIconAdd = iconAddRef.value;&lt;br /&gt;
			window.__lyricsViewerDebug.lastIconEdit = iconEditRef.value;&lt;br /&gt;
			window.__lyricsViewerDebug.lastIconSettings = iconSettingsRef.value;&lt;br /&gt;
			window.__lyricsViewerDebug.getIconState = function () {&lt;br /&gt;
				return {&lt;br /&gt;
					hasIconAdd: !!iconAddRef.value,&lt;br /&gt;
					hasIconEdit: !!iconEditRef.value,&lt;br /&gt;
					hasIconSettings: !!iconSettingsRef.value,&lt;br /&gt;
					iconAddSource: iconAddRef.value ? &#039;api-list=codexicons&#039; : &#039;missing&#039;,&lt;br /&gt;
					iconEditSource: iconEditRef.value ? &#039;api-list=codexicons&#039; : &#039;missing&#039;,&lt;br /&gt;
					iconSettingsSource: iconSettingsRef.value ? &#039;api-list=codexicons&#039; : &#039;missing&#039;,&lt;br /&gt;
					hasCdxIcon: !!CdxIcon&lt;br /&gt;
				};&lt;br /&gt;
			};&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
		// CdxIcon이 없다면 경고를 남깁니다.&lt;br /&gt;
		if ( !CdxIcon ) { debugWarn( &#039;Codex CdxIcon 컴포넌트를 찾을 수 없습니다. Codex 버전이 오래되었거나 모듈이 잘못 로드되었을 수 있습니다. Codex.CdxIcon 존재 여부를 확인하세요.&#039; ); }&lt;br /&gt;
&lt;br /&gt;
		return CdxIcon;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 뷰어별 반응형 상태 생성&lt;br /&gt;
	function createViewerState( Vue ) {&lt;br /&gt;
		return Vue.reactive( {&lt;br /&gt;
			singleSelection: &#039;&#039;,&lt;br /&gt;
			multiSelection: [ &#039;&#039; ],&lt;br /&gt;
			isDialogOpen: false,&lt;br /&gt;
			isPopoverOpen: false,&lt;br /&gt;
			isEditModalOpen: false,&lt;br /&gt;
			editTargetTitle: &#039;&#039;,&lt;br /&gt;
			wikitext: &#039;&#039;,&lt;br /&gt;
			originalWikitext: &#039;&#039;,&lt;br /&gt;
			baseTimestamp: &#039;&#039;,&lt;br /&gt;
			startTimestamp: &#039;&#039;,&lt;br /&gt;
			editSummary: &#039;&#039;,&lt;br /&gt;
			isFetchingWikitext: false,&lt;br /&gt;
			isSavingWikitext: false,&lt;br /&gt;
			editErrorMessage: &#039;&#039;,&lt;br /&gt;
			isDiffShown: false,&lt;br /&gt;
			isDiffLoading: false,&lt;br /&gt;
			diffHtml: &#039;&#039;,&lt;br /&gt;
			diffErrorMessage: &#039;&#039;&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 초기 표시 상태 적용&lt;br /&gt;
	function applyInitialViewerDisplay( viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles ) {&lt;br /&gt;
		applyVariationVisibility( viewerState.singleSelection, viewerState.multiSelection, globalDisplayState.isMultimodeEnabled );&lt;br /&gt;
		applyDisplayStyles( globalDisplayState.fontSize, globalDisplayState.fontFamily, globalDisplayState.textAlignment, globalDisplayState.lineHeight, globalDisplayState.letterSpacing );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 상태 변경 감시&lt;br /&gt;
	function watchViewerDisplayChanges( Vue, viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles ) {&lt;br /&gt;
		// 다중 선택 모드 변경 감시&lt;br /&gt;
		Vue.watch( function () { return globalDisplayState.isMultimodeEnabled; }, function ( isActive ) {&lt;br /&gt;
			// 다중 선택 모드가 활성화되어 있나요?&lt;br /&gt;
			if ( isActive ) {&lt;br /&gt;
				// 활성화됨 — 다중 선택이 비어 있다면 단일 선택으로 채웁니다.&lt;br /&gt;
				if ( !viewerState.multiSelection.length ) { viewerState.multiSelection = [ viewerState.singleSelection || &#039;&#039; ]; }&lt;br /&gt;
			} else if ( viewerState.multiSelection.length ) {&lt;br /&gt;
				// 비활성화됨 — 첫 선택을 단일 선택으로 복원합니다.&lt;br /&gt;
				viewerState.singleSelection = viewerState.multiSelection[ 0 ];&lt;br /&gt;
			}&lt;br /&gt;
			applyVariationVisibility( viewerState.singleSelection, viewerState.multiSelection, isActive );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 글자/글꼴/정렬/줄 간격/글자 간격 변경 감시&lt;br /&gt;
		Vue.watch( function () { return [ globalDisplayState.fontSize, globalDisplayState.fontFamily, globalDisplayState.textAlignment, globalDisplayState.lineHeight, globalDisplayState.letterSpacing ]; }, function () {&lt;br /&gt;
			applyDisplayStyles( globalDisplayState.fontSize, globalDisplayState.fontFamily, globalDisplayState.textAlignment, globalDisplayState.lineHeight, globalDisplayState.letterSpacing );&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 토글 값 바인딩 생성&lt;br /&gt;
	function buildToggleBinding( Vue, viewerState, globalDisplayState, applyVariationVisibility ) {&lt;br /&gt;
		return Vue.computed( {&lt;br /&gt;
			get: function () {&lt;br /&gt;
				return globalDisplayState.isMultimodeEnabled ? viewerState.multiSelection : viewerState.singleSelection;&lt;br /&gt;
			},&lt;br /&gt;
			set: function ( newValue ) {&lt;br /&gt;
				// 배열 값인가요? (다중 선택)&lt;br /&gt;
				if ( Array.isArray( newValue ) ) {&lt;br /&gt;
					// 배열 — 빈 배열이라면 무시합니다.&lt;br /&gt;
					if ( !newValue.length ) { return; }&lt;br /&gt;
					// 배열 — 다중 선택 갱신&lt;br /&gt;
					viewerState.multiSelection = newValue.slice();&lt;br /&gt;
				} else {&lt;br /&gt;
					// 단일 값 — 단일 선택 갱신&lt;br /&gt;
					viewerState.singleSelection = newValue == null ? &#039;&#039; : String( newValue );&lt;br /&gt;
&lt;br /&gt;
					// 다중 선택이 단일 항목이라면 동기화합니다.&lt;br /&gt;
					if ( viewerState.multiSelection.length === 1 ) { viewerState.multiSelection = [ viewerState.singleSelection ]; }&lt;br /&gt;
				}&lt;br /&gt;
				applyVariationVisibility(&lt;br /&gt;
					viewerState.singleSelection,&lt;br /&gt;
					viewerState.multiSelection,&lt;br /&gt;
					globalDisplayState.isMultimodeEnabled&lt;br /&gt;
				);&lt;br /&gt;
			}&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Dialog 상태와 동작 생성&lt;br /&gt;
	function buildDialogBindings( Vue, viewerState, songPageTitle, existingVariationSet, openEditModal ) {&lt;br /&gt;
		var selectedType = Vue.ref( &#039;&#039; );&lt;br /&gt;
		var customVariationName = Vue.ref( &#039;&#039; );&lt;br /&gt;
&lt;br /&gt;
		// 라디오 그룹 이름 — 뷰어마다 독립된 그룹이 되도록 고유한 값을 만듭니다.&lt;br /&gt;
		var lyricTypeRadioGroupName = &#039;mw-lyrics-viewer-lyric-type-&#039; + ( ++radioGroupCounter );&lt;br /&gt;
&lt;br /&gt;
		var dialogOpen = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.isDialogOpen; },&lt;br /&gt;
			set: function ( value ) { viewerState.isDialogOpen = value; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 대상 문서 제목 계산 — 유효하지 않은 제목은 null이 되어 프리뷰가 비활성화됩니다.&lt;br /&gt;
		var previewDocumentTitle = Vue.computed( function () {&lt;br /&gt;
			// 표준 유형이 선택되었다면 해당 유형 이름으로 제목을 만듭니다.&lt;br /&gt;
			if ( selectedType.value &amp;amp;&amp;amp; selectedType.value !== &#039;__custom&#039; ) { return buildLyricsDocumentTitle( songPageTitle, selectedType.value ); }&lt;br /&gt;
&lt;br /&gt;
			// 표준 유형이 아니라면 입력된 사용자 지정 이름으로 제목을 만듭니다.&lt;br /&gt;
			var trimmedCustomName = customVariationName.value.trim();&lt;br /&gt;
			return trimmedCustomName ? buildLyricsDocumentTitle( songPageTitle, trimmedCustomName ) : &#039;&#039;;&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 이미 존재하는 문서인지 계산&lt;br /&gt;
		var alreadyExists = Vue.computed( function () {&lt;br /&gt;
			// 제목이 비어 있다면 존재하지 않습니다.&lt;br /&gt;
			if ( !previewDocumentTitle.value ) { return false; }&lt;br /&gt;
			var variationName = ( previewDocumentTitle.value.split( &#039;/&#039; ).pop() || &#039;&#039; );&lt;br /&gt;
			return existingVariationSet.has( variationName );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 주 동작 버튼 정의&lt;br /&gt;
		var primaryAction = Vue.computed( function () {&lt;br /&gt;
			return {&lt;br /&gt;
				label: alreadyExists.value ? MESSAGES.editLabel : MESSAGES.createLabel,&lt;br /&gt;
				actionType: &#039;progressive&#039;,&lt;br /&gt;
				disabled: !previewDocumentTitle.value&lt;br /&gt;
			};&lt;br /&gt;
		} );&lt;br /&gt;
		var defaultAction = { label: MESSAGES.closeLabel };&lt;br /&gt;
&lt;br /&gt;
		// 주 동작 실행 — 편집 모달 열기&lt;br /&gt;
		function handlePrimaryAction() {&lt;br /&gt;
			// 대상 문서 제목이 없다면 중단합니다.&lt;br /&gt;
			if ( !previewDocumentTitle.value ) { return; }&lt;br /&gt;
			viewerState.isDialogOpen = false;&lt;br /&gt;
			openEditModal( previewDocumentTitle.value );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Dialog 닫힘 감시&lt;br /&gt;
		Vue.watch( function () { return viewerState.isDialogOpen; }, function ( isOpen ) {&lt;br /&gt;
			// 닫혔다면 선택을 초기화합니다.&lt;br /&gt;
			if ( !isOpen ) { selectedType.value = &#039;&#039;; customVariationName.value = &#039;&#039;; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			selectedType: selectedType,&lt;br /&gt;
			customVariationName: customVariationName,&lt;br /&gt;
			dialogOpen: dialogOpen,&lt;br /&gt;
			previewDocumentTitle: previewDocumentTitle,&lt;br /&gt;
			lyricTypeRadioGroupName: lyricTypeRadioGroupName,&lt;br /&gt;
			primaryAction: primaryAction,&lt;br /&gt;
			defaultAction: defaultAction,&lt;br /&gt;
			handlePrimaryAction: handlePrimaryAction&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 편집 모달 상태와 동작 생성&lt;br /&gt;
	function buildEditModalBindings( Vue, viewerState, lyricsRepository, currentPageTitle ) {&lt;br /&gt;
		var editModalOpen = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.isEditModalOpen; },&lt;br /&gt;
			set: function ( value ) {&lt;br /&gt;
				// 닫기 시도인데 수정된 내용이 있다면 버릴지 브라우저 확인 대화상자로 묻습니다.&lt;br /&gt;
				if ( !value &amp;amp;&amp;amp; viewerState.wikitext !== viewerState.originalWikitext &amp;amp;&amp;amp; !window.confirm( MESSAGES.unsavedChangesMessage ) ) { return; }&lt;br /&gt;
				viewerState.isEditModalOpen = value;&lt;br /&gt;
			}&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 편집 모달에 저장하지 않은 변경 사항이 있을 때 탭 이동/새로고침을 경고합니다.&lt;br /&gt;
		function handleBeforeUnload( beforeUnloadEvent ) {&lt;br /&gt;
			if ( !viewerState.isEditModalOpen || viewerState.wikitext === viewerState.originalWikitext ) { return; }&lt;br /&gt;
			beforeUnloadEvent.preventDefault();&lt;br /&gt;
			// Chrome은 returnValue가 설정되어야 대화상자를 표시합니다.&lt;br /&gt;
			beforeUnloadEvent.returnValue = &#039;&#039;;&lt;br /&gt;
		}&lt;br /&gt;
		window.addEventListener( &#039;beforeunload&#039;, handleBeforeUnload );&lt;br /&gt;
&lt;br /&gt;
		// 모달 내부 입력 상태를 뷰어 상태와 연결합니다.&lt;br /&gt;
		var editTargetTitle = Vue.computed( function () { return viewerState.editTargetTitle; } );&lt;br /&gt;
		var wikitext = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.wikitext; },&lt;br /&gt;
			set: function ( value ) { viewerState.wikitext = value; }&lt;br /&gt;
		} );&lt;br /&gt;
		var editSummary = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.editSummary; },&lt;br /&gt;
			set: function ( value ) { viewerState.editSummary = value; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 저장 중에는 주 동작을 막습니다.&lt;br /&gt;
		var saveAction = Vue.computed( function () {&lt;br /&gt;
			return {&lt;br /&gt;
				label: MESSAGES.saveLabel,&lt;br /&gt;
				actionType: &#039;progressive&#039;,&lt;br /&gt;
				disabled: viewerState.isFetchingWikitext || viewerState.isSavingWikitext&lt;br /&gt;
			};&lt;br /&gt;
		} );&lt;br /&gt;
		var cancelAction = { label: MESSAGES.closeLabel };&lt;br /&gt;
&lt;br /&gt;
		// 모달 열기 — 대상 문서의 위키텍스트를 불러옵니다.&lt;br /&gt;
		var fetchSequence = 0;&lt;br /&gt;
		function openEditModal( documentTitle ) {&lt;br /&gt;
			var requestSequence = ++fetchSequence;&lt;br /&gt;
			viewerState.editTargetTitle = documentTitle;&lt;br /&gt;
			viewerState.wikitext = &#039;&#039;;&lt;br /&gt;
			viewerState.originalWikitext = &#039;&#039;;&lt;br /&gt;
			viewerState.baseTimestamp = &#039;&#039;;&lt;br /&gt;
			viewerState.startTimestamp = &#039;&#039;;&lt;br /&gt;
			viewerState.editSummary = &#039;&#039;;&lt;br /&gt;
			viewerState.editErrorMessage = &#039;&#039;;&lt;br /&gt;
			viewerState.diffHtml = &#039;&#039;;&lt;br /&gt;
			viewerState.diffErrorMessage = &#039;&#039;;&lt;br /&gt;
			viewerState.isDiffShown = false;&lt;br /&gt;
			viewerState.isEditModalOpen = true;&lt;br /&gt;
			viewerState.isFetchingWikitext = true;&lt;br /&gt;
			lyricsRepository.fetchWikitext( documentTitle ).then( function ( fetchedRevision ) {&lt;br /&gt;
				// 다른 문서 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== fetchSequence ) { return; }&lt;br /&gt;
				viewerState.wikitext = fetchedRevision.text;&lt;br /&gt;
				viewerState.originalWikitext = fetchedRevision.text;&lt;br /&gt;
				viewerState.baseTimestamp = fetchedRevision.baseTimestamp;&lt;br /&gt;
				viewerState.startTimestamp = fetchedRevision.startTimestamp;&lt;br /&gt;
			}, function ( fetchError ) {&lt;br /&gt;
				// 다른 문서 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== fetchSequence ) { return; }&lt;br /&gt;
				viewerState.editErrorMessage = formatApiError( fetchError );&lt;br /&gt;
			} ).then( function () {&lt;br /&gt;
				// 다른 문서 요청이 시작되었다면 이전 요청의 로딩 상태를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== fetchSequence ) { return; }&lt;br /&gt;
				viewerState.isFetchingWikitext = false;&lt;br /&gt;
			} );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 편집 저장 — 곡 문서 캐시를 정리한 뒤 페이지를 새로고칩니다.&lt;br /&gt;
		function handleEditSave() {&lt;br /&gt;
			// 저장이 이미 진행 중이라면 중단합니다.&lt;br /&gt;
			if ( viewerState.isSavingWikitext ) { return; }&lt;br /&gt;
			viewerState.isSavingWikitext = true;&lt;br /&gt;
			viewerState.editErrorMessage = &#039;&#039;;&lt;br /&gt;
			lyricsRepository.saveWikitext( viewerState.editTargetTitle, viewerState.wikitext, viewerState.editSummary, viewerState.baseTimestamp, viewerState.startTimestamp ).then( function ( saveSucceeded ) {&lt;br /&gt;
				// API 오류가 아니어도 저장이 성공하지 않았다면 새로고침하지 않고 안내합니다.&lt;br /&gt;
				if ( !saveSucceeded ) {&lt;br /&gt;
					viewerState.isSavingWikitext = false;&lt;br /&gt;
					viewerState.editErrorMessage = MESSAGES.saveFailedMessage;&lt;br /&gt;
					return;&lt;br /&gt;
				}&lt;br /&gt;
				// 트랜스클루전된 가사를 갱신하기 위해 현재 문서에 purge를 수행합니다.&lt;br /&gt;
				// 저장에 성공했으므로 현재 내용을 기준으로 삼아 나감 경고가 발동하지 않게 합니다.&lt;br /&gt;
				viewerState.originalWikitext = viewerState.wikitext;&lt;br /&gt;
				lyricsRepository.purgePage( currentPageTitle ).then( function ( purgeResponse ) {&lt;br /&gt;
					debugLog( &#039;현재 문서 purge 완료:&#039;, currentPageTitle, purgeResponse );&lt;br /&gt;
					window.location.reload();&lt;br /&gt;
				}, function ( purgeError ) {&lt;br /&gt;
					// 캐시 정리에 실패해도 저장은 완료된 상태이므로 새로고침합니다.&lt;br /&gt;
					debugWarn( &#039;곡 문서 캐시 정리에 실패했습니다: &#039; + formatApiError( purgeError ) );&lt;br /&gt;
					window.location.reload();&lt;br /&gt;
				} );&lt;br /&gt;
			}, function ( saveError ) {&lt;br /&gt;
				viewerState.isSavingWikitext = false;&lt;br /&gt;
				viewerState.editErrorMessage = formatApiError( saveError );&lt;br /&gt;
			} );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 대상 문서 링크 URL 생성&lt;br /&gt;
		var editTargetUrl = Vue.computed( function () {&lt;br /&gt;
			try { return mw.util.getUrl( viewerState.editTargetTitle ); } catch ( error ) { return &#039;&#039;; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 차이 보기 — 불러온 내용과 현재 내용의 차이를 요청합니다.&lt;br /&gt;
		var diffSequence = 0;&lt;br /&gt;
		function handleDiffRefresh() {&lt;br /&gt;
			var requestSequence = ++diffSequence;&lt;br /&gt;
			viewerState.isDiffShown = true;&lt;br /&gt;
			viewerState.diffHtml = &#039;&#039;;&lt;br /&gt;
			viewerState.diffErrorMessage = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
			// 변경 전후가 모두 비어 있다면 요청 없이 차이 없음으로 처리합니다.&lt;br /&gt;
			if ( !viewerState.originalWikitext &amp;amp;&amp;amp; !viewerState.wikitext ) { return; }&lt;br /&gt;
			viewerState.isDiffLoading = true;&lt;br /&gt;
&lt;br /&gt;
			// 차이 표 스타일은 mediawiki.diff.styles 모듈이 제공합니다.&lt;br /&gt;
			try {&lt;br /&gt;
				mw.loader.using( &#039;mediawiki.diff.styles&#039; ).catch( function () {&lt;br /&gt;
					debugWarn( &#039;mediawiki.diff.styles 모듈을 불러오지 못했습니다.&#039; );&lt;br /&gt;
				} );&lt;br /&gt;
			} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
			lyricsRepository.fetchWikitextDiff( viewerState.originalWikitext, viewerState.wikitext ).then( function ( diffHtml ) {&lt;br /&gt;
				// 다른 차이 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== diffSequence ) { return; }&lt;br /&gt;
				viewerState.diffHtml = diffHtml;&lt;br /&gt;
			}, function ( diffError ) {&lt;br /&gt;
				// 다른 차이 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== diffSequence ) { return; }&lt;br /&gt;
				viewerState.diffErrorMessage = formatApiError( diffError );&lt;br /&gt;
			} ).then( function () {&lt;br /&gt;
				// 다른 차이 요청이 시작되었다면 이전 요청의 로딩 상태를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== diffSequence ) { return; }&lt;br /&gt;
				viewerState.isDiffLoading = false;&lt;br /&gt;
			} );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			editModalOpen: editModalOpen,&lt;br /&gt;
			editTargetTitle: editTargetTitle,&lt;br /&gt;
			editTargetUrl: editTargetUrl,&lt;br /&gt;
			wikitext: wikitext,&lt;br /&gt;
			editSummary: editSummary,&lt;br /&gt;
			isFetchingWikitext: Vue.computed( function () { return viewerState.isFetchingWikitext; } ),&lt;br /&gt;
			isSavingWikitext: Vue.computed( function () { return viewerState.isSavingWikitext; } ),&lt;br /&gt;
			editErrorMessage: Vue.computed( function () { return viewerState.editErrorMessage; } ),&lt;br /&gt;
			saveAction: saveAction,&lt;br /&gt;
			cancelAction: cancelAction,&lt;br /&gt;
			openEditModal: openEditModal,&lt;br /&gt;
			handleEditSave: handleEditSave,&lt;br /&gt;
			isDiffShown: Vue.computed( function () { return viewerState.isDiffShown; } ),&lt;br /&gt;
			isDiffLoading: Vue.computed( function () { return viewerState.isDiffLoading; } ),&lt;br /&gt;
			diffHtml: Vue.computed( function () { return viewerState.diffHtml; } ),&lt;br /&gt;
			diffErrorMessage: Vue.computed( function () { return viewerState.diffErrorMessage; } ),&lt;br /&gt;
			handleDiffRefresh: handleDiffRefresh&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Popover 앵커 상태 생성&lt;br /&gt;
	function buildPopoverAnchor( Vue ) {&lt;br /&gt;
		var settingsButtonAnchorRef = Vue.ref( null );&lt;br /&gt;
		var settingsButtonAnchorEl = Vue.computed( function () {&lt;br /&gt;
			var raw = settingsButtonAnchorRef.value;&lt;br /&gt;
			// 컴포넌트 래퍼라면 내부 엘리먼트를 반환합니다.&lt;br /&gt;
			if ( raw &amp;amp;&amp;amp; raw.$el ) { return raw.$el; }&lt;br /&gt;
			// 일반 엘리먼트 — 그대로 반환합니다.&lt;br /&gt;
			return raw;&lt;br /&gt;
		} );&lt;br /&gt;
		return { settingsButtonAnchorRef: settingsButtonAnchorRef, settingsButtonAnchorEl: settingsButtonAnchorEl };&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 설정 버튼 목록 생성&lt;br /&gt;
	function buildDisplaySettingButtons() {&lt;br /&gt;
		return {&lt;br /&gt;
			fontSizeButtons: [&lt;br /&gt;
				{ value: &#039;small&#039;, label: MESSAGES.fontSizeSmall },&lt;br /&gt;
				{ value: &#039;medium&#039;, label: MESSAGES.fontSizeMedium },&lt;br /&gt;
				{ value: &#039;large&#039;, label: MESSAGES.fontSizeLarge }&lt;br /&gt;
			],&lt;br /&gt;
			fontFamilyButtons: [&lt;br /&gt;
				{ value: &#039;default&#039;, label: MESSAGES.fontFamilyDefault },&lt;br /&gt;
				{ value: &#039;serif&#039;, label: MESSAGES.fontFamilySerif },&lt;br /&gt;
				{ value: &#039;sans-serif&#039;, label: MESSAGES.fontFamilySans },&lt;br /&gt;
				{ value: &#039;monospace&#039;, label: MESSAGES.fontFamilyMono }&lt;br /&gt;
			],&lt;br /&gt;
			alignmentButtons: [&lt;br /&gt;
				{ value: &#039;left&#039;, label: MESSAGES.alignLeft },&lt;br /&gt;
				{ value: &#039;center&#039;, label: MESSAGES.alignCenter },&lt;br /&gt;
				{ value: &#039;right&#039;, label: MESSAGES.alignRight }&lt;br /&gt;
			],&lt;br /&gt;
			lineHeightButtons: [&lt;br /&gt;
				{ value: &#039;narrow&#039;, label: MESSAGES.lineHeightNarrow },&lt;br /&gt;
				{ value: &#039;normal&#039;, label: MESSAGES.lineHeightNormal },&lt;br /&gt;
				{ value: &#039;wide&#039;, label: MESSAGES.lineHeightWide }&lt;br /&gt;
			],&lt;br /&gt;
			letterSpacingButtons: [&lt;br /&gt;
				{ value: &#039;narrow&#039;, label: MESSAGES.letterSpacingNarrow },&lt;br /&gt;
				{ value: &#039;normal&#039;, label: MESSAGES.letterSpacingNormal },&lt;br /&gt;
				{ value: &#039;wide&#039;, label: MESSAGES.letterSpacingWide }&lt;br /&gt;
			]&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 설정 변경 핸들러 생성&lt;br /&gt;
	function buildDisplaySettingHandlers( globalDisplayState ) {&lt;br /&gt;
		return {&lt;br /&gt;
			handleMultimodeChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.isMultimodeEnabled = !!value;&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.multimode, String( globalDisplayState.isMultimodeEnabled ) );&lt;br /&gt;
			},&lt;br /&gt;
			handleFontSizeChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.fontSize = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.fontSize, globalDisplayState.fontSize );&lt;br /&gt;
			},&lt;br /&gt;
			handleFontFamilyChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.fontFamily = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.fontFamily, globalDisplayState.fontFamily );&lt;br /&gt;
			},&lt;br /&gt;
			handleAlignmentChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.textAlignment = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.align, globalDisplayState.textAlignment );&lt;br /&gt;
			},&lt;br /&gt;
			handleLineHeightChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.lineHeight = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.lineHeight, globalDisplayState.lineHeight );&lt;br /&gt;
			},&lt;br /&gt;
			handleLetterSpacingChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.letterSpacing = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.letterSpacing, globalDisplayState.letterSpacing );&lt;br /&gt;
			}&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Settings Popover 상태와 버튼 생성&lt;br /&gt;
	function buildSettingsBindings( Vue, viewerState, globalDisplayState, Codex ) {&lt;br /&gt;
		var anchor = buildPopoverAnchor( Vue );&lt;br /&gt;
		var buttons = buildDisplaySettingButtons();&lt;br /&gt;
		var handlers = buildDisplaySettingHandlers( globalDisplayState );&lt;br /&gt;
&lt;br /&gt;
		var popoverOpen = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.isPopoverOpen; },&lt;br /&gt;
			set: function ( value ) { viewerState.isPopoverOpen = value; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			settingsButtonAnchorRef: anchor.settingsButtonAnchorRef,&lt;br /&gt;
			settingsButtonAnchorEl: anchor.settingsButtonAnchorEl,&lt;br /&gt;
			popoverOpen: popoverOpen,&lt;br /&gt;
			fontSizeButtons: buttons.fontSizeButtons,&lt;br /&gt;
			fontFamilyButtons: buttons.fontFamilyButtons,&lt;br /&gt;
			alignmentButtons: buttons.alignmentButtons,&lt;br /&gt;
			lineHeightButtons: buttons.lineHeightButtons,&lt;br /&gt;
			letterSpacingButtons: buttons.letterSpacingButtons,&lt;br /&gt;
			handleMultimodeChange: handlers.handleMultimodeChange,&lt;br /&gt;
			handleFontSizeChange: handlers.handleFontSizeChange,&lt;br /&gt;
			handleFontFamilyChange: handlers.handleFontFamilyChange,&lt;br /&gt;
			handleAlignmentChange: handlers.handleAlignmentChange,&lt;br /&gt;
			handleLineHeightChange: handlers.handleLineHeightChange,&lt;br /&gt;
			handleLetterSpacingChange: handlers.handleLetterSpacingChange,&lt;br /&gt;
			isToggleSwitchAvailable: !!Codex.CdxToggleSwitch&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Codex 컴포넌트 등록&lt;br /&gt;
	function registerViewerComponents( viewerApp, Codex ) {&lt;br /&gt;
		viewerApp.component( &#039;cdx-button&#039;, Codex.CdxButton );&lt;br /&gt;
		viewerApp.component( &#039;cdx-toggle-button-group&#039;, Codex.CdxToggleButtonGroup );&lt;br /&gt;
		viewerApp.component( &#039;cdx-dialog&#039;, Codex.CdxDialog );&lt;br /&gt;
		viewerApp.component( &#039;cdx-popover&#039;, Codex.CdxPopover );&lt;br /&gt;
		viewerApp.component( &#039;cdx-icon&#039;, Codex.CdxIcon );&lt;br /&gt;
		viewerApp.component( &#039;cdx-text-input&#039;, Codex.CdxTextInput );&lt;br /&gt;
		viewerApp.component( &#039;cdx-text-area&#039;, Codex.CdxTextArea );&lt;br /&gt;
&lt;br /&gt;
		// Tooltip directive가 제공되면 등록합니다.&lt;br /&gt;
		if ( Codex.CdxTooltip ) { viewerApp.directive( &#039;tooltip&#039;, Codex.CdxTooltip ); }&lt;br /&gt;
&lt;br /&gt;
		// 선택적 Codex 컴포넌트가 제공되면 등록합니다.&lt;br /&gt;
		if ( Codex.CdxToggleSwitch ) { viewerApp.component( &#039;cdx-toggle-switch&#039;, Codex.CdxToggleSwitch ); }&lt;br /&gt;
		if ( Codex.CdxMessage ) { viewerApp.component( &#039;cdx-message&#039;, Codex.CdxMessage ); }&lt;br /&gt;
		if ( Codex.CdxField ) { viewerApp.component( &#039;cdx-field&#039;, Codex.CdxField ); }&lt;br /&gt;
		if ( Codex.CdxLabel ) { viewerApp.component( &#039;cdx-label&#039;, Codex.CdxLabel ); }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 단일 Viewer Vue 앱 생성 및 마운트&lt;br /&gt;
	function createViewerApp( viewerBar, songPageTitle, variationData, viewerState, globalDisplayState, editModalBindings, iconAddRef, iconEditRef, iconSettingsRef, applyVariationVisibility, Vue, Codex ) {&lt;br /&gt;
		// 토글 바인딩 생성&lt;br /&gt;
		var toggleValue = buildToggleBinding( Vue, viewerState, globalDisplayState, applyVariationVisibility );&lt;br /&gt;
&lt;br /&gt;
		// Dialog 상태 생성&lt;br /&gt;
		var dialogBindings = buildDialogBindings( Vue, viewerState, songPageTitle, variationData.existingVariationSet, editModalBindings.openEditModal );&lt;br /&gt;
&lt;br /&gt;
		// Settings 상태 생성&lt;br /&gt;
		var settingsBindings = buildSettingsBindings( Vue, viewerState, globalDisplayState, Codex );&lt;br /&gt;
&lt;br /&gt;
		// 툴팁 디렉티브 사용 가능 여부&lt;br /&gt;
		var tooltipDirectiveAvailable = !!Codex.CdxTooltip;&lt;br /&gt;
&lt;br /&gt;
		// Vue 앱 생성&lt;br /&gt;
		var viewerApp = Vue.createMwApp( {&lt;br /&gt;
			setup: function () {&lt;br /&gt;
				return {&lt;br /&gt;
					messages: MESSAGES,&lt;br /&gt;
					shouldShowVariationToggle: variationData.shouldShowVariationToggle,&lt;br /&gt;
					variationToggleButtons: variationData.variationToggleButtons,&lt;br /&gt;
					toggleValue: toggleValue,&lt;br /&gt;
					displayState: globalDisplayState,&lt;br /&gt;
					selectedType: dialogBindings.selectedType,&lt;br /&gt;
					customVariationName: dialogBindings.customVariationName,&lt;br /&gt;
					lyricTypeRadioGroupName: dialogBindings.lyricTypeRadioGroupName,&lt;br /&gt;
					dialogOpen: dialogBindings.dialogOpen,&lt;br /&gt;
					previewDocumentTitle: dialogBindings.previewDocumentTitle,&lt;br /&gt;
					primaryAction: dialogBindings.primaryAction,&lt;br /&gt;
					defaultAction: dialogBindings.defaultAction,&lt;br /&gt;
					handlePrimaryAction: dialogBindings.handlePrimaryAction,&lt;br /&gt;
					standardTypes: STANDARD_LYRICS_TYPES,&lt;br /&gt;
					songPageTitle: songPageTitle,&lt;br /&gt;
					iconAdd: iconAddRef,&lt;br /&gt;
					iconEdit: iconEditRef,&lt;br /&gt;
					iconSettings: iconSettingsRef,&lt;br /&gt;
					settingsButtonAnchorRef: settingsBindings.settingsButtonAnchorRef,&lt;br /&gt;
					settingsButtonAnchorEl: settingsBindings.settingsButtonAnchorEl,&lt;br /&gt;
					popoverOpen: settingsBindings.popoverOpen,&lt;br /&gt;
					fontSizeButtons: settingsBindings.fontSizeButtons,&lt;br /&gt;
					fontFamilyButtons: settingsBindings.fontFamilyButtons,&lt;br /&gt;
					alignmentButtons: settingsBindings.alignmentButtons,&lt;br /&gt;
					lineHeightButtons: settingsBindings.lineHeightButtons,&lt;br /&gt;
					letterSpacingButtons: settingsBindings.letterSpacingButtons,&lt;br /&gt;
					handleMultimodeChange: settingsBindings.handleMultimodeChange,&lt;br /&gt;
					handleFontSizeChange: settingsBindings.handleFontSizeChange,&lt;br /&gt;
					handleFontFamilyChange: settingsBindings.handleFontFamilyChange,&lt;br /&gt;
					handleAlignmentChange: settingsBindings.handleAlignmentChange,&lt;br /&gt;
					handleLineHeightChange: settingsBindings.handleLineHeightChange,&lt;br /&gt;
					handleLetterSpacingChange: settingsBindings.handleLetterSpacingChange,&lt;br /&gt;
					isToggleSwitchAvailable: settingsBindings.isToggleSwitchAvailable,&lt;br /&gt;
					tooltipDirectiveAvailable: tooltipDirectiveAvailable,&lt;br /&gt;
					editModalOpen: editModalBindings.editModalOpen,&lt;br /&gt;
					editTargetTitle: editModalBindings.editTargetTitle,&lt;br /&gt;
					wikitext: editModalBindings.wikitext,&lt;br /&gt;
					editSummary: editModalBindings.editSummary,&lt;br /&gt;
					isFetchingWikitext: editModalBindings.isFetchingWikitext,&lt;br /&gt;
					isSavingWikitext: editModalBindings.isSavingWikitext,&lt;br /&gt;
					editErrorMessage: editModalBindings.editErrorMessage,&lt;br /&gt;
					saveAction: editModalBindings.saveAction,&lt;br /&gt;
					cancelAction: editModalBindings.cancelAction,&lt;br /&gt;
					handleEditSave: editModalBindings.handleEditSave,&lt;br /&gt;
					editTargetUrl: editModalBindings.editTargetUrl,&lt;br /&gt;
					isDiffShown: editModalBindings.isDiffShown,&lt;br /&gt;
					isDiffLoading: editModalBindings.isDiffLoading,&lt;br /&gt;
					diffHtml: editModalBindings.diffHtml,&lt;br /&gt;
					diffErrorMessage: editModalBindings.diffErrorMessage,&lt;br /&gt;
					handleDiffRefresh: editModalBindings.handleDiffRefresh,&lt;br /&gt;
					isMessageAvailable: !!Codex.CdxMessage&lt;br /&gt;
				};&lt;br /&gt;
			},&lt;br /&gt;
			template: VIEWER_TEMPLATE&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// Codex 컴포넌트 등록&lt;br /&gt;
		registerViewerComponents( viewerApp, Codex );&lt;br /&gt;
&lt;br /&gt;
		// Viewer 마운트&lt;br /&gt;
		viewerApp.mount( viewerBar );&lt;br /&gt;
		return viewerApp;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 각 변형의 .ts-lyric-content 우상단에 편집/생성 버튼 주입&lt;br /&gt;
	function injectVariationEditButtons( songPageTitle, variationElements, Vue, Codex, iconAddRef, iconEditRef, openEditModal ) {&lt;br /&gt;
		var buttonSlots = [];&lt;br /&gt;
		variationElements.forEach( function ( variationElement ) {&lt;br /&gt;
			var variationValue = variationElement.getAttribute( &#039;data-lyric-variation&#039; ) || &#039;&#039;;&lt;br /&gt;
			var contentElement = variationElement.querySelector( &#039;.ts-lyric-content&#039; );&lt;br /&gt;
&lt;br /&gt;
			// 가사 본문이 없으면 건너뜁니다.&lt;br /&gt;
			if ( !contentElement ) { return; }&lt;br /&gt;
&lt;br /&gt;
			// 이전 초기화 시도에서 만든 마운트 지점이 있다면 재사용해 중복 생성을 막습니다.&lt;br /&gt;
			var mountPoint = contentElement.querySelector( &#039;:scope &amp;gt; .mw--lyrics-variation-edit&#039; );&lt;br /&gt;
			if ( !mountPoint ) {&lt;br /&gt;
				mountPoint = document.createElement( &#039;div&#039; );&lt;br /&gt;
				mountPoint.className = &#039;mw--lyrics-variation-edit&#039;;&lt;br /&gt;
				contentElement.appendChild( mountPoint );&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// 내용이 없는 변형이면 추가 버튼으로 표시합니다.&lt;br /&gt;
			buttonSlots.push( {&lt;br /&gt;
				target: mountPoint,&lt;br /&gt;
				variationValue: variationValue,&lt;br /&gt;
				isExistingDocument: !contentElement.hasAttribute( &#039;data-empty&#039; )&lt;br /&gt;
			} );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 주입할 버튼이 없다면 앱을 만들지 않습니다.&lt;br /&gt;
		if ( !buttonSlots.length ) { return; }&lt;br /&gt;
&lt;br /&gt;
		// 모든 버튼을 하나의 앱에서 렌더링해 각 마운트 지점으로 텔레포트합니다.&lt;br /&gt;
		var editApp = Vue.createMwApp( {&lt;br /&gt;
			setup: function () {&lt;br /&gt;
				return {&lt;br /&gt;
					iconAdd: iconAddRef,&lt;br /&gt;
					iconEdit: iconEditRef,&lt;br /&gt;
					messages: MESSAGES,&lt;br /&gt;
					tooltipAvailable: !!Codex.CdxTooltip,&lt;br /&gt;
					buttonSlots: buttonSlots,&lt;br /&gt;
					handleEdit: function ( buttonSlot ) {&lt;br /&gt;
						var documentTitle = buildLyricsDocumentTitle( songPageTitle, buttonSlot.variationValue );&lt;br /&gt;
&lt;br /&gt;
						// 제목을 만들 수 없는 변형이라면 모달을 열지 않습니다.&lt;br /&gt;
						if ( documentTitle == null ) { debugWarn( &#039;문서 제목을 만들 수 없어 편집을 시작하지 않습니다:&#039;, buttonSlot.variationValue ); return; }&lt;br /&gt;
						openEditModal( documentTitle );&lt;br /&gt;
					}&lt;br /&gt;
				};&lt;br /&gt;
			},&lt;br /&gt;
			template: &#039;&amp;lt;teleport v-for=&amp;quot;(buttonSlot, index) in buttonSlots&amp;quot; :key=&amp;quot;index&amp;quot; :to=&amp;quot;buttonSlot.target&amp;quot;&amp;gt;&amp;lt;cdx-button weight=&amp;quot;quiet&amp;quot; size=&amp;quot;small&amp;quot; :aria-label=&amp;quot;buttonSlot.isExistingDocument ? messages.editButtonAriaLabel : messages.addVariationAriaLabel&amp;quot; v-tooltip=&amp;quot;tooltipAvailable ? (buttonSlot.isExistingDocument ? messages.editButtonTooltip : messages.addVariationTooltip) : null&amp;quot; @click=&amp;quot;handleEdit(buttonSlot)&amp;quot;&amp;gt;&amp;lt;cdx-icon v-if=&amp;quot;buttonSlot.isExistingDocument ? iconEdit : iconAdd&amp;quot; :icon=&amp;quot;buttonSlot.isExistingDocument ? iconEdit : iconAdd&amp;quot; size=&amp;quot;small&amp;quot;&amp;gt;&amp;lt;/cdx-icon&amp;gt;&amp;lt;/cdx-button&amp;gt;&amp;lt;/teleport&amp;gt;&#039;&lt;br /&gt;
		} );&lt;br /&gt;
		editApp.component( &#039;cdx-button&#039;, Codex.CdxButton );&lt;br /&gt;
		editApp.component( &#039;cdx-icon&#039;, Codex.CdxIcon );&lt;br /&gt;
		if ( Codex.CdxTooltip ) { editApp.directive( &#039;tooltip&#039;, Codex.CdxTooltip ); }&lt;br /&gt;
&lt;br /&gt;
		// 앱 루트에는 텔레포트 앵커만 남으므로 화면에 표시되는 내용은 없습니다.&lt;br /&gt;
		var appRoot = document.createElement( &#039;div&#039; );&lt;br /&gt;
		document.body.appendChild( appRoot );&lt;br /&gt;
		editApp.mount( appRoot );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 단일 Viewer 인스턴스 마운트&lt;br /&gt;
	function mountSingleViewer( viewerContainer, mainMarkerElement, globalDisplayState, Vue, Codex, iconAddRef, iconEditRef, iconSettingsRef ) {&lt;br /&gt;
		var songPageTitle = ( mainMarkerElement.getAttribute( &#039;data-lyric-mainpage&#039; ) || &#039;&#039; ).trim();&lt;br /&gt;
&lt;br /&gt;
		// 변형 데이터 수집&lt;br /&gt;
		var variationData = collectVariationData( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
		// 변형 요소가 하나도 없다면 중단합니다.&lt;br /&gt;
		if ( !variationData.variationElements.length ) { return null; }&lt;br /&gt;
&lt;br /&gt;
		// 서버 렌더링된 뷰어 바 참조&lt;br /&gt;
		var viewerBar = findViewerBar( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
		// 뷰어 바가 없다면 뷰어를 초기화하지 않습니다.&lt;br /&gt;
		if ( !viewerBar ) { return null; }&lt;br /&gt;
&lt;br /&gt;
		// 기본 가사만 존재할 때 spacer를 앞으로 옮겨 추가/설정 버튼을 우측에 모읍니다.&lt;br /&gt;
		viewerBar.classList.toggle( &#039;mw--lyrics-viewer-bar--no-variation-toggle&#039;, !variationData.shouldShowVariationToggle );&lt;br /&gt;
&lt;br /&gt;
		var applyVariationVisibility = createVariationApplier(&lt;br /&gt;
			viewerContainer, variationData.variationElements&lt;br /&gt;
		);&lt;br /&gt;
		var applyDisplayStyles = createStyleApplier( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
		// 뷰어 상태 생성&lt;br /&gt;
		var viewerState = createViewerState( Vue );&lt;br /&gt;
&lt;br /&gt;
		// 편집 모달 상태 생성 — 에러 메시지를 화면 언어로 받기 위해 errorlang을 지정합니다.&lt;br /&gt;
		var editModalBindings = buildEditModalBindings( Vue, viewerState, createLyricsRepository( new mw.Api( {&lt;br /&gt;
			parameters: { errorlang: mw.config.get( &#039;wgUserLanguage&#039; ) }&lt;br /&gt;
		} ) ), getCurrentPageTitle() );&lt;br /&gt;
&lt;br /&gt;
		// 변형 편집/생성 버튼 주입&lt;br /&gt;
		injectVariationEditButtons( songPageTitle, variationData.variationElements, Vue, Codex, iconAddRef, iconEditRef, editModalBindings.openEditModal );&lt;br /&gt;
&lt;br /&gt;
		// 초기 표시 반영&lt;br /&gt;
		applyInitialViewerDisplay( viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles );&lt;br /&gt;
&lt;br /&gt;
		// 표시 변경 감시&lt;br /&gt;
		watchViewerDisplayChanges( Vue, viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles );&lt;br /&gt;
&lt;br /&gt;
		// Vue 앱 생성 및 마운트&lt;br /&gt;
		var viewerApp = createViewerApp(&lt;br /&gt;
			viewerBar,&lt;br /&gt;
			songPageTitle,&lt;br /&gt;
			variationData,&lt;br /&gt;
			viewerState,&lt;br /&gt;
			globalDisplayState,&lt;br /&gt;
			editModalBindings,&lt;br /&gt;
			iconAddRef,&lt;br /&gt;
			iconEditRef,&lt;br /&gt;
			iconSettingsRef,&lt;br /&gt;
			applyVariationVisibility,&lt;br /&gt;
			Vue,&lt;br /&gt;
			Codex&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		// 마운트에 성공했으므로 재초기화되지 않게 표시합니다. 실패한 컨테이너는 다음 초기화 때 재시도됩니다.&lt;br /&gt;
		viewerContainer.dataset.lyricsViewerMounted = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
		return viewerApp;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// ResourceLoader 모듈에서 라이브러리 추출&lt;br /&gt;
	function extractViewerLibraries( requireFunction ) {&lt;br /&gt;
		var vueLibrary = null;&lt;br /&gt;
		var codexLibrary = null;&lt;br /&gt;
		try { vueLibrary = requireFunction( &#039;vue&#039; ); } catch ( error ) { vueLibrary = window.Vue; }&lt;br /&gt;
		try { codexLibrary = requireFunction( &#039;@wikimedia/codex&#039; ); } catch ( error ) {&lt;br /&gt;
			try { codexLibrary = requireFunction( &#039;codex&#039; ); } catch ( innerError ) {}&lt;br /&gt;
		}&lt;br /&gt;
		return { vueLibrary: vueLibrary, codexLibrary: codexLibrary };&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 라이브러리 확보 후 마운트 시도 — 마운트 성공 여부를 반환합니다.&lt;br /&gt;
	function tryMountViewers( requireFunction ) {&lt;br /&gt;
		var libs = extractViewerLibraries( requireFunction );&lt;br /&gt;
		debugLog( &#039;tryMount — vue:&#039;, !!libs.vueLibrary, &#039;codex:&#039;, !!libs.codexLibrary );&lt;br /&gt;
&lt;br /&gt;
		// Vue 또는 Codex를 찾지 못했다면 경고를 남기고 실패를 반환합니다.&lt;br /&gt;
		if ( !libs.vueLibrary || !libs.codexLibrary ) {&lt;br /&gt;
			debugWarn( &#039;Vue 또는 Codex 모듈을 찾을 수 없습니다. ResourceLoader 모듈명이 다른지 확인하세요. mw.loader.getState 확인 필요.&#039; );&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		mountViewers( libs.vueLibrary, libs.codexLibrary );&lt;br /&gt;
		return true;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// &#039;codex&#039; 모듈명으로 재시도 — 오래된 MediaWiki/Codex 설치를 위한 대체 경로입니다.&lt;br /&gt;
	function retryMountWithLegacyCodex() {&lt;br /&gt;
		try {&lt;br /&gt;
			mw.loader.using( [ &#039;vue&#039;, &#039;codex&#039;, &#039;mediawiki.api&#039;, &#039;mediawiki.Title&#039; ] ).then( tryMountViewers ).catch( function () {&lt;br /&gt;
				debugWarn( &#039;vue와 codex 모듈을 모두 불러오지 못해 Viewer를 초기화할 수 없습니다.&#039; );&lt;br /&gt;
			} );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 진입점 — ResourceLoader로 Vue/Codex를 불러온 뒤 Viewer를 마운트&lt;br /&gt;
	function initializeViewers() {&lt;br /&gt;
		// 새로고침 전 세션의 디버그 로그를 출력합니다.&lt;br /&gt;
		if ( isDebugEnabled() ) { dumpBufferedDebugLogs(); }&lt;br /&gt;
&lt;br /&gt;
		// ResourceLoader로 Vue/Codex 로드&lt;br /&gt;
		var basePromise;&lt;br /&gt;
		try {&lt;br /&gt;
			basePromise = mw.loader.using( [ &#039;vue&#039;, &#039;@wikimedia/codex&#039;, &#039;mediawiki.api&#039;, &#039;mediawiki.Title&#039; ] );&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			basePromise = null;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Promise가 유효하지 않다면 구식 모듈명으로 재시도합니다.&lt;br /&gt;
		if ( !basePromise || typeof basePromise.then !== &#039;function&#039; ) {&lt;br /&gt;
			retryMountWithLegacyCodex();&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 로드 성공 시 마운트, 마운트에 실패했다면 구식 모듈명으로 재시도합니다.&lt;br /&gt;
		basePromise.then( function ( requireFunction ) {&lt;br /&gt;
			if ( !tryMountViewers( requireFunction ) ) { retryMountWithLegacyCodex(); }&lt;br /&gt;
		} ).catch( retryMountWithLegacyCodex );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 전체 Viewer 마운트&lt;br /&gt;
	function mountViewers( Vue, Codex ) {&lt;br /&gt;
		// 표시 대상 수집&lt;br /&gt;
		var seenContainers = new Set();&lt;br /&gt;
		var viewerTargets = collectViewerTargets( seenContainers );&lt;br /&gt;
&lt;br /&gt;
		// 표시 대상이 하나도 없다면 아이콘 요청 없이 중단합니다.&lt;br /&gt;
		if ( !viewerTargets.length ) { return; }&lt;br /&gt;
&lt;br /&gt;
		// Codex 아이콘 준비&lt;br /&gt;
		var iconAddRef = Vue.ref( null );&lt;br /&gt;
		var iconEditRef = Vue.ref( null );&lt;br /&gt;
		var iconSettingsRef = Vue.ref( null );&lt;br /&gt;
		setupCodexIcons( Codex, iconAddRef, iconEditRef, iconSettingsRef );&lt;br /&gt;
&lt;br /&gt;
		// 전역 표시 상태 생성 및 동기화&lt;br /&gt;
		var globalDisplayState = createGlobalDisplayState( Vue );&lt;br /&gt;
		syncDisplayStateWithStorage( globalDisplayState );&lt;br /&gt;
&lt;br /&gt;
		// 각 대상에 Viewer 마운트&lt;br /&gt;
		viewerTargets.forEach( function ( target ) {&lt;br /&gt;
			mountSingleViewer(&lt;br /&gt;
				target.viewerContainer,&lt;br /&gt;
				target.mainMarkerElement,&lt;br /&gt;
				globalDisplayState,&lt;br /&gt;
				Vue,&lt;br /&gt;
				Codex,&lt;br /&gt;
				iconAddRef,&lt;br /&gt;
				iconEditRef,&lt;br /&gt;
				iconSettingsRef&lt;br /&gt;
			);&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// DOM 준비 상태에 따라 초기화를 분기합니다.&lt;br /&gt;
	if ( document.readyState === &#039;loading&#039; ) {&lt;br /&gt;
		document.addEventListener( &#039;DOMContentLoaded&#039;, initializeViewers );&lt;br /&gt;
	} else {&lt;br /&gt;
		initializeViewers();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 편집 미리보기, AJAX 치환 등으로 나중에 삽입되는 콘텐츠도 초기화합니다.&lt;br /&gt;
	// 훅은 초기 로딩에도 발화하지만 마운트 가드 덕분에 이미 마운트된 컨테이너는 중복 초기화되지 않습니다.&lt;br /&gt;
	try {&lt;br /&gt;
		mw.hook( &#039;wikipage.content&#039; ).add( initializeViewers );&lt;br /&gt;
	} catch ( error ) {}&lt;br /&gt;
}() );&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:Tabber_%EA%B5%AC%EB%AC%B8_%EB%B6%84%EC%84%9D_%ED%83%9C%EA%B7%B8%EB%A5%BC_%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94_%EB%AC%B8%EC%84%9C&amp;diff=2840</id>
		<title>분류:Tabber 구문 분석 태그를 사용하는 문서</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:Tabber_%EA%B5%AC%EB%AC%B8_%EB%B6%84%EC%84%9D_%ED%83%9C%EA%B7%B8%EB%A5%BC_%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94_%EB%AC%B8%EC%84%9C&amp;diff=2840"/>
		<updated>2026-08-31T14:07:22Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: __숨은분류__ 분류:추적용 분류&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__숨은분류__&lt;br /&gt;
[[분류:추적용 분류]]&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:ArrayFunctions_%ED%99%95%EC%9E%A5_%EA%B8%B0%EB%8A%A5%EC%9D%84_%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94_%EB%AC%B8%EC%84%9C&amp;diff=2839</id>
		<title>분류:ArrayFunctions 확장 기능을 사용하는 문서</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:ArrayFunctions_%ED%99%95%EC%9E%A5_%EA%B8%B0%EB%8A%A5%EC%9D%84_%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94_%EB%AC%B8%EC%84%9C&amp;diff=2839"/>
		<updated>2026-08-31T14:07:01Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: __숨은분류__ 분류:추적용 분류&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__숨은분류__&lt;br /&gt;
[[분류:추적용 분류]]&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC&amp;diff=2838</id>
		<title>틀:가사</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC&amp;diff=2838"/>
		<updated>2026-08-30T16:55:16Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!------------------------------&amp;gt;&amp;lt;includeonly&amp;gt;&amp;lt;!--------------------------------&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{#vardefine: 곡문서이름&lt;br /&gt;
| {{#if: {{#or: {{{1|}}} | {{{문서|}}} }} &lt;br /&gt;
  | {{#or: {{{1|}}} | {{{문서|}}} }} &lt;br /&gt;
  | {{전체문서이름}} &lt;br /&gt;
  }}&lt;br /&gt;
}}&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
 --&amp;gt;&amp;lt;usescript src=&amp;quot;가사.js&amp;quot; /&amp;gt;&amp;lt;!-- &lt;br /&gt;
 --&amp;gt;&amp;lt;usestyle src=&amp;quot;가사.css&amp;quot; /&amp;gt;&amp;lt;!-- &lt;br /&gt;
 &lt;br /&gt;
 --&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-container-before&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!-- &lt;br /&gt;
 --&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-container citizen-overflow&amp;quot; data-lyric-mainpage=&amp;quot;{{#var: 곡문서이름}}&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;div class=&amp;quot;mw--lyrics-viewer-bar citizen-overflow-sticky-header&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
		&amp;lt;div class=&amp;quot;mw--lyrics-variation-container citizen-overflow&amp;quot;&amp;gt;&lt;br /&gt;
			&amp;lt;!-- 기본 가사 문서는 항상 표시하기 --&amp;gt;&lt;br /&gt;
			&amp;lt;div class=&amp;quot;mw--lyrics-variation&amp;quot; data-lyric-variation=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
				{{가사/상자|가사문서=가사:{{#var: 곡문서이름}}|레이블=가사}}&lt;br /&gt;
			&amp;lt;/div&amp;gt;&lt;br /&gt;
			&amp;lt;!-- 그 외 가사 문자 가져오기 --&amp;gt;&lt;br /&gt;
			{{#af_stringmap:&lt;br /&gt;
				&amp;lt;!-- 참고: {{#가사문서목록}} 파서 함수는 로컬 설정에 존재하는 커스텀 구현입니다. --&amp;gt;&lt;br /&gt;
				{{#가사문서목록: {{#var: 곡문서이름}} }}&lt;br /&gt;
				| ;;&lt;br /&gt;
				| 가사문서&lt;br /&gt;
				| &amp;lt;div class=&amp;quot;mw--lyrics-variation&amp;quot; data-lyric-variation=&amp;quot;{{#titleparts:{{{가사문서}}}|0|2}}&amp;quot;&amp;gt;&lt;br /&gt;
					{{가사/상자&lt;br /&gt;
					| 가사문서 = {{{가사문서}}}&lt;br /&gt;
					| 레이블 = {{#titleparts:{{{가사문서}}}|0|2}}&lt;br /&gt;
					}}&lt;br /&gt;
				 &amp;lt;/div&amp;gt;&lt;br /&gt;
				|&lt;br /&gt;
			}}&lt;br /&gt;
		&amp;lt;/div&amp;gt;&amp;lt;!-- &lt;br /&gt;
 --&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;/includeonly&amp;gt;&amp;lt;!-------------------------------&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;noinclude&amp;gt;&amp;lt;!----------------------------------&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
[[분류:틀]]&lt;br /&gt;
{{설명문서}}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;/noinclude&amp;gt;&amp;lt;!---------------------------------&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Editnotice-3200&amp;diff=2834</id>
		<title>미디어위키:Editnotice-3200</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Editnotice-3200&amp;diff=2834"/>
		<updated>2026-08-30T10:34:30Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 다른 유형 가사 지원에 따라 안내 문구 삭제&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%B0%BD%ED%8C%9D%EC%9C%84%ED%82%A4:%EC%84%A0%EC%B0%A9%EC%9E%A5&amp;diff=2833</id>
		<title>창팝위키:선착장</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%B0%BD%ED%8C%9D%EC%9C%84%ED%82%A4:%EC%84%A0%EC%B0%A9%EC%9E%A5&amp;diff=2833"/>
		<updated>2026-08-30T10:19:32Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: /* 창팝위키 베타 오픈 */ 답변&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{SHORTDESC:창팝위키의 소통 공간}}&lt;br /&gt;
__새문단쓰기__&lt;br /&gt;
[[분류:창팝위키]]&lt;br /&gt;
{{창팝위키 둘러보기}}&lt;br /&gt;
선착장에 오신 것을 환영합니다! 창팝위키에 대해 자유롭게 의견과 질문을 남길 수 있는 소통 공간입니다. (단쌀제)&lt;br /&gt;
&lt;br /&gt;
상단의(모바일에서는 하단) &#039;&#039;&#039;주제 추가&#039;&#039;&#039; 버튼을 눌러 새 주제를 추가할 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
== 창팝위키 베타 오픈 ==&lt;br /&gt;
&lt;br /&gt;
창팝위키가 베타 오픈했습니다! 🎉&lt;br /&gt;
&lt;br /&gt;
창팝, 창드컵 문서를 만드실 수 있습니다. 자세한 내용은 [[창팝위키:편집지침|편집지침]]을 참고해주세요!&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 6월 15일 (일) 22:47 (KST)&lt;br /&gt;
&lt;br /&gt;
:업데이트:&lt;br /&gt;
:창팝 문서에서 &amp;lt;code&amp;gt;공개일&amp;lt;/code&amp;gt;은 이제 유튜브 영상이 있는 경우 영상 업로드 날짜로 설정됩니다. 대표 영상과 첫 공개일이 다르지 않다면 비워도 됩니다.&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 6월 23일 (월) 17:38 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:창팝 문서에 &amp;lt;code&amp;gt;별명&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;화자&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;BPM&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;제작 방법&amp;lt;/code&amp;gt; 필드가 새롭게 추가되었습니다. &amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 7월 6일 (일) 18:11 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:양식으로 편집할 때 더 이상 문서별 편집지침을 왔다갔다 하지 않아도 되도록 편집 창에 필드 설명이 나타납니다. 문서별 편집지침은 곧 폐지할 예정입니다.&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 8월 6일 (수) 21:17 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:개별 문서 편집지침 삭제 및 간소화, 대문에 업그레이드된 최공릴레이 정보 삽입&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 8월 31일 (일) 18:14 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:임시 계정이 활성화되었습니다. 비로그인 편집 시 IP 표시 대신 임시 계정으로 기록됩니다.&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 10월 30일 (목) 21:56 (KST)&lt;br /&gt;
::테스트&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[특수:기여/ㅇㅇ1|ㅇㅇ1]] ([[사용자토론:ㅇㅇ1|토론]])&amp;lt;/span&amp;gt; 2025년 10월 30일 (목) 21:57 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:* 제작자 문서가 양식으로 지원됩니다. [[창팝위키:기여하기|기여하기]]에서 제작자 문서를 만드실 수 있습니다.&lt;br /&gt;
:* 제작자 문서와 더불어 창팝 문서 정보상자에 링크 칸이 추가되었습니다. 곡과 관련된 여러 링크를 추가하는데 쓸 수 있습니다.&lt;br /&gt;
:&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 11월 12일 (수) 21:30 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:공동제작자 필드가 추가되었습니다. 적용 예시: [[RED]]&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 11월 30일 (일) 01:12 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:기존의 월별 [[최초공개 릴레이/아카이브]] 문서에는 로딩이 오래걸리거나 서버를 터트리는 문제가 있었습니다. 무거운 일별 결과 합치기 대신 새 월별 가져오기 API를 도입했고 이를 이용하는 틀을 사용하도록 모두 수정했습니다.&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2026년 3월 16일 (월) 19:34 (KST)&lt;br /&gt;
:업데이트:&lt;br /&gt;
:한 곡에 여러 가사를 추가할 수 있게 되었습니다. 적용 예시: [[거짓말 속이기]], [[Spider Hotel]]&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2026년 8월 30일 (일) 19:19 (KST)&lt;br /&gt;
&lt;br /&gt;
== 건?의?사항 ==&lt;br /&gt;
&lt;br /&gt;
항상 수고하십니다.&lt;br /&gt;
&lt;br /&gt;
사용자 설정을 통해 가사 문서 편집 시에는 원하는 글꼴로 보는 것이 가능하지만, 편집을 마친 가사 문서는 항상 고정폭 글꼴로 고정되어 있습니다.&lt;br /&gt;
&lt;br /&gt;
가독성 및 UI 통일성 측면에서 볼 때 가사 문서의 디폴트 글꼴이 산세리프 등이면 좋을 것 같은데, 위키 기능 상 가능한 부분이라면 추후 패치에 참조하여 주십사 부탁드립니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
일단은 선착장 항목이 일종의 자유 게시판(나무위키의 그루터기) 비슷한 역할이라고 보아 건의도 여기 남겼는데, 혹시 다른 창구가 있다면 알려주시면 감사하겠습니다.&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:리부트는벌레가돼집안에갇혔어|리부트는벌레가돼집안에갇혔어]] ([[사용자토론:리부트는벌레가돼집안에갇혔어|토론]])&amp;lt;/span&amp;gt; 2025년 6월 19일 (목) 19:43 (KST)&lt;br /&gt;
&lt;br /&gt;
:지금은 선착장이 통합 창구 맞아요. 잘 찾아오셨습니다.&lt;br /&gt;
:단독 가사 문서를 직접 볼 경우는 따로 생각하지 않았는데 일단 바꿀 수 있게 소도구에 추가했습니다. (&#039;환경 설정 → 소도구&#039;에서 활성화 가능)&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2025년 6월 20일 (금) 01:27 (KST)&lt;br /&gt;
&lt;br /&gt;
== 7월 27일 오전 1:15 서버 이전 완료 ==&lt;br /&gt;
&lt;br /&gt;
창팝위키 서버 이전 및 미디어위키 버전 업그레이드가 완료되었습니다.&lt;br /&gt;
&lt;br /&gt;
전에 없던 문제가 발생한 경우 답글로 제보해주세요.&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[사용자:창팝위키|창팝위키]] ([[사용자토론:창팝위키|토론]])&amp;lt;/span&amp;gt; 2026년 7월 27일 (월) 01:19 (KST)&lt;br /&gt;
&lt;br /&gt;
:비로그인 답글 테스트&amp;lt;br&amp;gt;&amp;lt;span class=&amp;quot;signature-message&amp;quot;&amp;gt;— [[특수:기여/ㅇㅇ53|ㅇㅇ53]] ([[사용자토론:ㅇㅇ53|토론]])&amp;lt;/span&amp;gt; 2026년 7월 27일 (월) 01:22 (KST)&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:Spider_Hotel/%EB%B2%88%EC%97%AD&amp;diff=2832</id>
		<title>가사:Spider Hotel/번역</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:Spider_Hotel/%EB%B2%88%EC%97%AD&amp;diff=2832"/>
		<updated>2026-08-30T10:16:29Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: 소중한 고객님, 어째 길을 잃으신 것 같습니다. 저희 연회에 오셔서 함께 춤 추시죠 불평하실 일도, 대가를 지불할 일도 없습니다 다만 하나 경고 드리건대, 모든 것은 진실입니다 이제 모든 것을 엉키게 해볼까요 매혹적이지 않습니까 저를 더욱 사악하게 만들, 모호한 계획을 세우시길 저희를 해친다고 한들 어떤 불이익도 없을 겁니다 고객님께선 제 어둡고 병적...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;소중한 고객님, 어째 길을 잃으신 것 같습니다.&lt;br /&gt;
저희 연회에 오셔서 함께 춤 추시죠&lt;br /&gt;
불평하실 일도, 대가를 지불할 일도 없습니다&lt;br /&gt;
다만 하나 경고 드리건대, 모든 것은 진실입니다&lt;br /&gt;
이제 모든 것을 엉키게 해볼까요 매혹적이지 않습니까&lt;br /&gt;
저를 더욱 사악하게 만들, 모호한 계획을 세우시길&lt;br /&gt;
저희를 해친다고 한들 어떤 불이익도 없을 겁니다&lt;br /&gt;
고객님께선 제 어둡고 병적인 만족감에 연료를 공급하시니까요&lt;br /&gt;
&lt;br /&gt;
출구를 발견하셨다니, 어쩌면 벗어날 기회일지도 모르겠습니다&lt;br /&gt;
끝에 끝까지 고민하고 계시더군요&lt;br /&gt;
하지만 고객님께서 너무나 빠르게 몰아보시는 것을 보니&lt;br /&gt;
아무래도 비참한 운명을&lt;br /&gt;
받아들이셔야 할 것 같습니다&lt;br /&gt;
이제서야 주변이 신경 쓰이시는군요&lt;br /&gt;
아무도 없지만 고객님은 신경이나 쓰십니까?&lt;br /&gt;
마스카라가 줄줄 흐를 때까지 우실지도 모릅니다.&lt;br /&gt;
그렇다면 지금이 진정 고객님이 꿈꾸는 이상이십니까?&lt;br /&gt;
&lt;br /&gt;
This is The Spider Hotel&lt;br /&gt;
고객님께서 스며들 5성급 슬픔을 제공합니다&lt;br /&gt;
왜 여길 나가지 않으시는 건가요&lt;br /&gt;
이곳은 Spider Hotel, 두려워 마세요 고객님&lt;br /&gt;
미치광이 살인마처럼 고객님께서는 환상에 집착하셨고&lt;br /&gt;
만약 더 머무르신다면 굶주림으로 고통받을텐데&lt;br /&gt;
대체 왜 벗어나지 않으시는지 모르겠지만&lt;br /&gt;
여긴 Spider Hotel, 작은 응원만을 보내겠습니다&lt;br /&gt;
&lt;br /&gt;
고객님을 위해 새로 배치한 가구들은 마음에 드십니까?&lt;br /&gt;
거미줄 사이로 보이는 풍경은 진정 새롭게 느껴지십니까?&lt;br /&gt;
은은한 조명이 너무 마음에 드신다고요&lt;br /&gt;
아직 놀라긴 이릅니다&lt;br /&gt;
눈을 뜨십시오&lt;br /&gt;
외부와 연락하는 건 무의미합니다&lt;br /&gt;
어떤 것이든 알려드릴 테니 저희와 상담하세요&lt;br /&gt;
모든 기분 나쁜 서비스가 처음부터 끝까지 포함되어 있습니다&lt;br /&gt;
진정하세요&lt;br /&gt;
고객님을 어떻게 하려는 것은 아닙니다&lt;br /&gt;
&lt;br /&gt;
출구를 발견하셨으니 이제 나가셔도 됩니다&lt;br /&gt;
그럼에도 여전히 고민하시는 듯 합니다&lt;br /&gt;
끔찍한 것들을 이렇게 늦게 몰아보셨으니&lt;br /&gt;
그간의 비참했던 거짓말들을 받아들이셔야겠습니다&lt;br /&gt;
저희가 뱉는 한숨에서 무언가 느껴지셨는지&lt;br /&gt;
미동도 없으신데 신경이나 쓰시는지요&lt;br /&gt;
전 괜찮습니다 &lt;br /&gt;
고객님의 아이라인이 강물처럼 흐를 때까지도&lt;br /&gt;
그러니 말씀해 주십시오&lt;br /&gt;
이것이 고객님께서 가장 바라던 것입니까?&lt;br /&gt;
&lt;br /&gt;
This is The Spider Hotel&lt;br /&gt;
고객님께서 스며들 5성급 슬픔을 제공합니다&lt;br /&gt;
정말 체크아웃 하지 않으셔도 괜찮으신가요&lt;br /&gt;
이곳은 Spider Hotel 이지만 두려워하지 않으셔도 됩니다&lt;br /&gt;
미치광이 살인마처럼 고객님께서는 환상에 집착하셨고&lt;br /&gt;
만약 여기서 더 머무르신다면 굶주림에 고통받을텐데&lt;br /&gt;
어째 나가실 생각은 없으신 것 같으니&lt;br /&gt;
이 Spider Hotel, 작은 환호를 보내겠습니다&lt;br /&gt;
&lt;br /&gt;
저희의 역할은 여기까지 입니다&lt;br /&gt;
이상한 상황들이 계속 일어날 거에요&lt;br /&gt;
저희는 그 잔혹한 끝을 알고 있지만&lt;br /&gt;
안타깝게도 고객님을 혼자 둘 수는 없습니다&lt;br /&gt;
제가 정말 꼼꼼하게 준비한 거미줄&lt;br /&gt;
고객님을 떠나 보낼 엄두는 나지 않지만&lt;br /&gt;
체크아웃을 위해 거짓말처럼 가려두겠습니다&lt;br /&gt;
그러니 이후 모든 것은 고객님 선택의 결과입니다&lt;br /&gt;
&lt;br /&gt;
This is The Spider Hotel&lt;br /&gt;
고객님께서 스며들 5성급 슬픔을 제공하겠습니다&lt;br /&gt;
왜 벗어나지 않으셨을까요&lt;br /&gt;
고객님, 두려워 마세요&lt;br /&gt;
이렇게      같은 짓을, 환상에 집착하지 마세요&lt;br /&gt;
더 머무르신다간 굶주림에 고통받겠지만&lt;br /&gt;
고객님께선 나가실 생각이 없으니&lt;br /&gt;
Spider Hotel은 당신을 응원합니다&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EA%B1%B0%EC%A7%93%EB%A7%90_%EC%86%8D%EC%9D%B4%EA%B8%B0/%EB%B0%9C%EC%9D%8C&amp;diff=2831</id>
		<title>가사:거짓말 속이기/발음</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EA%B1%B0%EC%A7%93%EB%A7%90_%EC%86%8D%EC%9D%B4%EA%B8%B0/%EB%B0%9C%EC%9D%8C&amp;diff=2831"/>
		<updated>2026-08-30T10:16:03Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: 도우시요-모나이 콘나 세카이 아자무이테 다마스 손나 세카이다 히에타 시센노 센토-인쇼- 후자케나이데 타다시사 난테 도코니모 카이테나이  오나지닷테 후리오 시테모 스케테루 기만노 론리 베쓰니 토쿠베쓰 와케자나이 고센-떼 아이마이가 코와이 다케나노  쇼-도-테키나 인쇼-소-사 와스레루코토모 데키나이 우소노 야이바데 무네오 사스노 하메쓰테키나 센토-...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;도우시요-모나이 콘나 세카이&lt;br /&gt;
아자무이테 다마스 손나 세카이다&lt;br /&gt;
히에타 시센노 센토-인쇼-&lt;br /&gt;
후자케나이데&lt;br /&gt;
타다시사 난테 도코니모 카이테나이&lt;br /&gt;
&lt;br /&gt;
오나지닷테 후리오 시테모&lt;br /&gt;
스케테루 기만노 론리&lt;br /&gt;
베쓰니 토쿠베쓰 와케자나이&lt;br /&gt;
고센-떼 아이마이가 코와이&lt;br /&gt;
다케나노&lt;br /&gt;
&lt;br /&gt;
쇼-도-테키나 인쇼-소-사&lt;br /&gt;
와스레루코토모 데키나이&lt;br /&gt;
우소노 야이바데 무네오 사스노&lt;br /&gt;
하메쓰테키나 센토-인쇼-&lt;br /&gt;
니와 우라기라레테&lt;br /&gt;
즈레브레 바레바레 바레테이루&lt;br /&gt;
&lt;br /&gt;
카쿠세 카쿠세&lt;br /&gt;
잇소 카부세테호시-&lt;br /&gt;
코로세&lt;br /&gt;
무지쓰노 코노 슈한모&lt;br /&gt;
기만테키나 페이크스토-리-&lt;br /&gt;
모도루코토와 데키나이&lt;br /&gt;
즈레브레 바레바레 바레테이루&lt;br /&gt;
즈레브레 바레바레 바레테이루&lt;br /&gt;
&lt;br /&gt;
나아&lt;br /&gt;
후라후라나 보쿠 하랏테&lt;br /&gt;
세이사이 소쿠 타이마 타이무 하랏테&lt;br /&gt;
카차카찻테 가샤도쿠로 칸세이데스&lt;br /&gt;
데스! 데못테 세이기데슷테&lt;br /&gt;
웃테킷타 켄오노 crystal night&lt;br /&gt;
하이보쿠 코지키야로-니 바이 바이&lt;br /&gt;
바이타루나 바케쓰 바이바잇테&lt;br /&gt;
...소- 오못테이타&lt;br /&gt;
노로-요-니 후카이나 우타모&lt;br /&gt;
와랏테 나가스시카나이&lt;br /&gt;
사다메난다로-&lt;br /&gt;
...킷토&lt;br /&gt;
&lt;br /&gt;
후사이데 모야시테&lt;br /&gt;
히게키붓타 챠반모&lt;br /&gt;
쇼센, 아라시데스&lt;br /&gt;
도-리도-리노 후쥰나 스토-리-&lt;br /&gt;
스토-리-도-리노 쥰나 도-리&lt;br /&gt;
키요쿠, 타다시쿠, 코-묘-니&lt;br /&gt;
이츠왓테&lt;br /&gt;
지분노 미니쿠사오 바카스&lt;br /&gt;
다케나노&lt;br /&gt;
&lt;br /&gt;
쇼-도-테키나 인쇼-소-사&lt;br /&gt;
와스레루코토모 데키나이&lt;br /&gt;
우소노 야이바데 무네오 사스노&lt;br /&gt;
하메쓰테키나 센토-인쇼-&lt;br /&gt;
니와 우라기라레테&lt;br /&gt;
즈레브레 바레바레 바레테이루&lt;br /&gt;
&lt;br /&gt;
카쿠세 카쿠세&lt;br /&gt;
잇소 카부세테호시-&lt;br /&gt;
카부세&lt;br /&gt;
신지쓰-떼 시라나이데&lt;br /&gt;
기만테키나 페이크스토-리-&lt;br /&gt;
모도루코토와 데키나이&lt;br /&gt;
즈레브레 바레바레 바레테이루&lt;br /&gt;
즈레브레 바레바레 바레테이루&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EA%B1%B0%EC%A7%93%EB%A7%90_%EC%86%8D%EC%9D%B4%EA%B8%B0/%EB%B2%88%EC%97%AD&amp;diff=2830</id>
		<title>가사:거짓말 속이기/번역</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EA%B1%B0%EC%A7%93%EB%A7%90_%EC%86%8D%EC%9D%B4%EA%B8%B0/%EB%B2%88%EC%97%AD&amp;diff=2830"/>
		<updated>2026-08-30T10:15:49Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: 어찌 할 수도 없는 이 세상 속이고 속이는, 그런 세상이야 차가워진 시선의 전투인장? 웃기지마 &amp;#039;올바름&amp;#039; 같은건 어디에도 써 있지 않아  똑같다며 흉내를 내 봐도 훤히 보이는 기만의 논리 딱히 특별한건 아니야 &amp;quot;오천&amp;quot; 이라는 애매함이 무서울 뿐이야  충동적인 인장주작 잊혀지는 것도 불가능해 거짓말의 날로 가슴을 찔러 파멸적인 전투인장 에게는 배신당해서 어...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;어찌 할 수도 없는 이 세상&lt;br /&gt;
속이고 속이는, 그런 세상이야&lt;br /&gt;
차가워진 시선의 전투인장?&lt;br /&gt;
웃기지마&lt;br /&gt;
&#039;올바름&#039; 같은건 어디에도 써 있지 않아&lt;br /&gt;
&lt;br /&gt;
똑같다며 흉내를 내 봐도&lt;br /&gt;
훤히 보이는 기만의 논리&lt;br /&gt;
딱히 특별한건 아니야&lt;br /&gt;
&amp;quot;오천&amp;quot; 이라는 애매함이 무서울&lt;br /&gt;
뿐이야&lt;br /&gt;
&lt;br /&gt;
충동적인 인장주작&lt;br /&gt;
잊혀지는 것도 불가능해&lt;br /&gt;
거짓말의 날로 가슴을 찔러&lt;br /&gt;
파멸적인 전투인장&lt;br /&gt;
에게는 배신당해서&lt;br /&gt;
어긋나고 흔들리고 들켜버렸어&lt;br /&gt;
&lt;br /&gt;
숨겨라 숨겨&lt;br /&gt;
차라리 덮어씌워줬으면 좋겠어&lt;br /&gt;
죽여라&lt;br /&gt;
무고한 이 주범도&lt;br /&gt;
기만적인 페이크 스토리&lt;br /&gt;
되돌아 가는건 불가능해&lt;br /&gt;
어긋나고 흔들리고 들켜버렸어&lt;br /&gt;
어긋나고 흔들리고 들켜버렸어&lt;br /&gt;
&lt;br /&gt;
있잖아&lt;br /&gt;
비틀비틀거리는 나를 씻어내 줘&lt;br /&gt;
제재 즉시 퇴마 시간을 바쳐서&lt;br /&gt;
딸깍딸깍으로 가샤도쿠로 완성입니다.&lt;br /&gt;
죽어! 그걸로 정의라던데?&lt;br /&gt;
때리고 자른 혐오의 crystal night&lt;br /&gt;
패배한 거지자식에게 BYE BYE&lt;br /&gt;
Vital인 물통 매매라고&lt;br /&gt;
....생각하고 있었는데&lt;br /&gt;
저주하듯 불쾌한 노래도&lt;br /&gt;
웃고 넘어가야하는&lt;br /&gt;
숙명인거겠지&lt;br /&gt;
...분명&lt;br /&gt;
&lt;br /&gt;
틀어막고 불태우고&lt;br /&gt;
비극인척하는 짜고치는 연극도&lt;br /&gt;
결국, 분탕이에요.&lt;br /&gt;
도리대로 불순한 스토리&lt;br /&gt;
스토리대로 순수한 도리&lt;br /&gt;
맑고 올바르고 교묘하게&lt;br /&gt;
거짓을 고하며&lt;br /&gt;
자신의 추악함을 속일&lt;br /&gt;
뿐이야&lt;br /&gt;
&lt;br /&gt;
충동적인 인장주작&lt;br /&gt;
잊혀지는 것도 불가능해&lt;br /&gt;
거짓말의 날로 가슴을 찔러&lt;br /&gt;
파멸적인 전투인장&lt;br /&gt;
에게는 배신당해서&lt;br /&gt;
어긋나고 흔들리고 들켜버렸어&lt;br /&gt;
&lt;br /&gt;
숨겨라 숨겨&lt;br /&gt;
차라리 덮어씌워줬으면 좋겠어&lt;br /&gt;
덮어줘&lt;br /&gt;
진실이라고 알지 말아줘&lt;br /&gt;
기만적인 페이크 스토리&lt;br /&gt;
되돌아 가는건 불가능해&lt;br /&gt;
어긋나고 흔들리고 들켜버렸어&lt;br /&gt;
어긋나고 흔들리고 들켜버렸어&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EA%B1%B0%EC%A7%93%EB%A7%90_%EC%86%8D%EC%9D%B4%EA%B8%B0&amp;diff=2829</id>
		<title>가사:거짓말 속이기</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EA%B0%80%EC%82%AC:%EA%B1%B0%EC%A7%93%EB%A7%90_%EC%86%8D%EC%9D%B4%EA%B8%B0&amp;diff=2829"/>
		<updated>2026-08-30T10:15:37Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 가사 변형으로 나누기&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;どうしようもないのこんな世界&lt;br /&gt;
欺いて騙すそんな世界だ&lt;br /&gt;
冷えた視線の戦闘印章？&lt;br /&gt;
ふざけないで&lt;br /&gt;
正しさなんてどこにも書いてない&lt;br /&gt;
&lt;br /&gt;
同じだってふりをしても&lt;br /&gt;
透けてる欺瞞の論理&lt;br /&gt;
別に特別わけじゃない&lt;br /&gt;
五千って曖昧が怖い&lt;br /&gt;
だけなの&lt;br /&gt;
&lt;br /&gt;
衝動的な印章操作&lt;br /&gt;
忘れることもできない&lt;br /&gt;
嘘の刃で胸を刺すの&lt;br /&gt;
破滅的な戦闘印章&lt;br /&gt;
には裏切られて&lt;br /&gt;
ズレブレバレバレバレている&lt;br /&gt;
&lt;br /&gt;
隠せ隠せ&lt;br /&gt;
いっそ被せてほしい&lt;br /&gt;
殺せ&lt;br /&gt;
無実のこの主犯も&lt;br /&gt;
欺瞞的なFAKEストーリー&lt;br /&gt;
戻ることはできない&lt;br /&gt;
ズレブレバレバレバレている&lt;br /&gt;
ズレブレバレバレバレている&lt;br /&gt;
&lt;br /&gt;
なあ&lt;br /&gt;
ふらふらな僕 祓って&lt;br /&gt;
制裁即退魔タイム払って&lt;br /&gt;
カチャカチャってガシャドクロ完成です&lt;br /&gt;
Death!でもって正義ですって&lt;br /&gt;
打つて切った嫌悪のcrystal night&lt;br /&gt;
敗北こじきやろうにBYE☆BYE&lt;br /&gt;
バイタルな バケツ売買って&lt;br /&gt;
....そう思っていた&lt;br /&gt;
呪うように不快な歌も&lt;br /&gt;
笑って流すしかない&lt;br /&gt;
宿命なんだろう&lt;br /&gt;
…きっと&lt;br /&gt;
&lt;br /&gt;
ふさいでもやして&lt;br /&gt;
悲劇ぶった茶番も&lt;br /&gt;
所詮、荒らしです&lt;br /&gt;
道理通りの 不純なストーリー&lt;br /&gt;
ストーリー通りの 純な道理&lt;br /&gt;
清く正しく巧妙に&lt;br /&gt;
偽って&lt;br /&gt;
自分の醜さを化かす&lt;br /&gt;
だけなの&lt;br /&gt;
&lt;br /&gt;
衝動的な印章操作&lt;br /&gt;
忘れることもできない&lt;br /&gt;
嘘の刃で胸を刺すの&lt;br /&gt;
破滅的な戦闘印章&lt;br /&gt;
には裏切られて&lt;br /&gt;
ズレブレバレバレバレている&lt;br /&gt;
&lt;br /&gt;
隠せ隠せ&lt;br /&gt;
いっそ被せてほしい&lt;br /&gt;
被せ&lt;br /&gt;
真実って知らないで&lt;br /&gt;
欺瞞的なFAKEストーリー&lt;br /&gt;
戻ることはできない&lt;br /&gt;
ズレブレバレバレバレている&lt;br /&gt;
ズレブレバレバレバレている&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC&amp;diff=2828</id>
		<title>틀:가사</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC&amp;diff=2828"/>
		<updated>2026-08-30T10:13:58Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!------------------------------&amp;gt;&amp;lt;includeonly&amp;gt;&amp;lt;!--------------------------------&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{#vardefine: 곡문서이름&lt;br /&gt;
| {{#if: {{#or: {{{1|}}} | {{{문서|}}} }} &lt;br /&gt;
  | {{#or: {{{1|}}} | {{{문서|}}} }} &lt;br /&gt;
  | {{전체문서이름}} &lt;br /&gt;
  }}&lt;br /&gt;
}}&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
 --&amp;gt;&amp;lt;usescript src=&amp;quot;가사.js&amp;quot; /&amp;gt;&amp;lt;!-- &lt;br /&gt;
 --&amp;gt;&amp;lt;usestyle src=&amp;quot;가사.css&amp;quot; /&amp;gt;&amp;lt;!-- &lt;br /&gt;
 &lt;br /&gt;
 --&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-container-before&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!-- &lt;br /&gt;
 --&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-container citizen-overflow&amp;quot; data-lyric-mainpage=&amp;quot;{{#var: 곡문서이름}}&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;div class=&amp;quot;mw--lyrics-viewer-bar citizen-overflow-sticky-header&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
		&amp;lt;div class=&amp;quot;mw--lyrics-variation-container citizen-overflow&amp;quot;&amp;gt;&lt;br /&gt;
			&amp;lt;!-- 기본 가사 문서는 항상 표시하기 --&amp;gt;&lt;br /&gt;
			&amp;lt;div class=&amp;quot;mw--lyrics-variation&amp;quot; data-lyric-variation=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
				{{가사/상자|가사문서=가사:{{#var: 곡문서이름}}|레이블=가사}}&lt;br /&gt;
			&amp;lt;/div&amp;gt;&lt;br /&gt;
			&amp;lt;!-- 그 외 가사 문자 가져오기 --&amp;gt;&lt;br /&gt;
			{{#for_external_table:|&lt;br /&gt;
 				&amp;lt;div class=&amp;quot;mw--lyrics-variation&amp;quot; data-lyric-variation=&amp;quot;{{#titleparts:{{{가사문서}}}|0|2}}&amp;quot;&amp;gt;&lt;br /&gt;
					{{가사/상자&lt;br /&gt;
					| 가사문서 = {{{가사문서}}} &lt;br /&gt;
					| 레이블 = {{#titleparts:{{{가사문서}}}|0|2}}&lt;br /&gt;
					}}&lt;br /&gt;
				&amp;lt;/div&amp;gt;&lt;br /&gt;
			| source = 가사목록&lt;br /&gt;
			| 문서 = {{#var: 곡문서이름}}/&lt;br /&gt;
			| data = 가사문서=$.query.allpages[*].title&lt;br /&gt;
			}}&lt;br /&gt;
		&amp;lt;/div&amp;gt;&amp;lt;!-- &lt;br /&gt;
 --&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;/includeonly&amp;gt;&amp;lt;!-------------------------------&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;noinclude&amp;gt;&amp;lt;!----------------------------------&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
[[분류:틀]]&lt;br /&gt;
{{설명문서}}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;/noinclude&amp;gt;&amp;lt;!---------------------------------&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC/%EC%83%81%EC%9E%90/%EC%8A%A4%ED%83%80%EC%9D%BC.css&amp;diff=2827</id>
		<title>틀:가사/상자/스타일.css</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC/%EC%83%81%EC%9E%90/%EC%8A%A4%ED%83%80%EC%9D%BC.css&amp;diff=2827"/>
		<updated>2026-08-30T10:13:54Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 창팝위키님이 틀:가사/스타일.css 문서를 넘겨주기를 만들지 않고 틀:가사/상자/스타일.css 문서로 이동했습니다&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;.ts-lyric-container {&lt;br /&gt;
	display: flow-root;&lt;br /&gt;
	width: max-content;&lt;br /&gt;
	max-width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-header {&lt;br /&gt;
	display: flex;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-edit {&lt;br /&gt;
	margin-left: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-edit .ts-tlinks-tlinks a {&lt;br /&gt;
	color: var(--color-progressive, #36c);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-edit .ts-tlinks-tlinks a:active {&lt;br /&gt;
	color: var(--color-progressive--active, #233566);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-edit .ts-tlinks-tlinks a:visited {&lt;br /&gt;
	color: var(--color-visited, #6a60b0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-edit .ts-tlinks-tlinks a:visited:active {&lt;br /&gt;
	color: var(--color-visited--active, #233566);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ts-lyric-content {&lt;br /&gt;
	border-width: 3px;&lt;br /&gt;
	padding: 16px;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC/%EC%83%81%EC%9E%90&amp;diff=2826</id>
		<title>틀:가사/상자</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%ED%8B%80:%EA%B0%80%EC%82%AC/%EC%83%81%EC%9E%90&amp;diff=2826"/>
		<updated>2026-08-30T10:13:08Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: &amp;lt;!------------------------------&amp;gt;&amp;lt;includeonly&amp;gt;&amp;lt;!--------------------------------  --&amp;gt;&amp;lt;templatestyles src=&amp;quot;틀:가사/상자/스타일.css&amp;quot; /&amp;gt;&amp;lt;!--   --&amp;gt;&amp;lt;div class=&amp;quot;ts-lyric-container&amp;quot;&amp;gt;&amp;lt;!--   --&amp;gt;&amp;lt;div class=&amp;quot;ts-lyric-header&amp;quot;&amp;gt;&amp;lt;!--   	--&amp;gt;&amp;lt;span class=&amp;quot;ts-lyric-label&amp;quot;&amp;gt;{{{레이블|}}}&amp;lt;/span&amp;gt;&amp;lt;!--  --&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!--   --&amp;gt;&amp;lt;div class=&amp;quot;ts-lyric-content&amp;quot; {{#ifexist:{{{가사문서}}}||data-empty}}&amp;gt;&amp;lt;!--  --&amp;gt; {{#ifexist: {{{가사문서}}} 	| {{#tag: poem  	  |{{ :{{{가사문서}}} }} 	  }} 	| &amp;lt;p...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!------------------------------&amp;gt;&amp;lt;includeonly&amp;gt;&amp;lt;!--------------------------------&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&amp;lt;templatestyles src=&amp;quot;틀:가사/상자/스타일.css&amp;quot; /&amp;gt;&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&amp;lt;div class=&amp;quot;ts-lyric-container&amp;quot;&amp;gt;&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&amp;lt;div class=&amp;quot;ts-lyric-header&amp;quot;&amp;gt;&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
	--&amp;gt;&amp;lt;span class=&amp;quot;ts-lyric-label&amp;quot;&amp;gt;{{{레이블|}}}&amp;lt;/span&amp;gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&amp;lt;div class=&amp;quot;ts-lyric-content&amp;quot; {{#ifexist:{{{가사문서}}}||data-empty}}&amp;gt;&amp;lt;!-- &lt;br /&gt;
--&amp;gt; {{#ifexist: {{{가사문서}}}&lt;br /&gt;
	| {{#tag: poem &lt;br /&gt;
	  |{{ :{{{가사문서}}} }}&lt;br /&gt;
	  }}&lt;br /&gt;
	| &amp;lt;p&amp;gt;&#039;&#039;가사가 없습니다.&#039;&#039;&amp;lt;/p&amp;gt;&lt;br /&gt;
	}}&amp;lt;!-- &lt;br /&gt;
--&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!-- &lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;/includeonly&amp;gt;&amp;lt;!-------------------------------&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;noinclude&amp;gt;&amp;lt;!----------------------------------&lt;br /&gt;
&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
[[분류:틀]]&lt;br /&gt;
{{설명문서}}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
--------------------------------&amp;gt;&amp;lt;/noinclude&amp;gt;&amp;lt;!---------------------------------&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%84%A4%EB%AA%85%EB%AC%B8%EC%84%9C:%ED%8B%80:%EA%B0%80%EC%82%AC/%EC%83%81%EC%9E%90&amp;diff=2825</id>
		<title>설명문서:틀:가사/상자</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%84%A4%EB%AA%85%EB%AC%B8%EC%84%9C:%ED%8B%80:%EA%B0%80%EC%82%AC/%EC%83%81%EC%9E%90&amp;diff=2825"/>
		<updated>2026-08-30T10:13:05Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: 가사 문서 하나에 대한 가사 상자를 담당합니다.  == 모습 == &amp;lt;pre&amp;gt;{{가사/상자|가사문서=가사:다 해줬잖아|레이블=레이블 텍스트}}&amp;lt;/pre&amp;gt; {{가사/상자|가사문서=가사:다 해줬잖아|레이블=레이블 텍스트}}  == 원본 == {{원본보여주기}}  &amp;lt;noinclude&amp;gt; {{문서:설명문서}} &amp;lt;/noinclude&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;가사 문서 하나에 대한 가사 상자를 담당합니다.&lt;br /&gt;
&lt;br /&gt;
== 모습 ==&lt;br /&gt;
&amp;lt;pre&amp;gt;{{가사/상자|가사문서=가사:다 해줬잖아|레이블=레이블 텍스트}}&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{가사/상자|가사문서=가사:다 해줬잖아|레이블=레이블 텍스트}}&lt;br /&gt;
&lt;br /&gt;
== 원본 ==&lt;br /&gt;
{{원본보여주기}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
{{문서:설명문서}}&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.css&amp;diff=2824</id>
		<title>미디어위키:가사.css</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.css&amp;diff=2824"/>
		<updated>2026-08-30T10:12:20Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: .mw--lyrics-container-before { 	margin-block: var(--space-sm); }  .mw--lyrics-container[data-lyrics-viewer-multimode=&amp;quot;true&amp;quot;] .mw--lyrics-variation-container { 	display: flex; 	gap: var(--space-md); 	overflow-x: scroll; }  /* 가로 스크롤이 실제로 생겼을 때만 위 요소의 부유를 무시하고 새 줄에서 시작합니다.    .mw--lyrics-container-before는 뷰어 컨테이너보다 앞에 나오는 요소이므로 :has()로 뒤쪽 형제의 하위 컨테이너를...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;.mw--lyrics-container-before {&lt;br /&gt;
	margin-block: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-container[data-lyrics-viewer-multimode=&amp;quot;true&amp;quot;] .mw--lyrics-variation-container {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	gap: var(--space-md);&lt;br /&gt;
	overflow-x: scroll;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 가로 스크롤이 실제로 생겼을 때만 위 요소의 부유를 무시하고 새 줄에서 시작합니다.&lt;br /&gt;
   .mw--lyrics-container-before는 뷰어 컨테이너보다 앞에 나오는 요소이므로 :has()로 뒤쪽 형제의 하위 컨테이너를 역참조하고,&lt;br /&gt;
   스크롤 생김 여부는 스크립트가 갱신하는 data-lyrics-viewer-overflow 속성으로 판단합니다. */&lt;br /&gt;
.mw--lyrics-container-before:has(~ .mw--lyrics-container[data-lyrics-viewer-overflow=&amp;quot;true&amp;quot;]),&lt;br /&gt;
.mw--lyrics-container-before:has(~ * .mw--lyrics-container[data-lyrics-viewer-overflow=&amp;quot;true&amp;quot;]) {&lt;br /&gt;
	clear: both;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 클래스를 반복해 특이성을 높여 멀티모드 규칙(같은 특이성, 더 앞에 선언됨)보다 우선합니다 */&lt;br /&gt;
.citizen-overflow-content.citizen-overflow-content &amp;gt; .mw--lyrics-variation-container {&lt;br /&gt;
	overflow-x: initial;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 가사만 존재할 때 spacer를 맨 앞으로 옮겨 추가/설정 버튼을 우측에 모음 */&lt;br /&gt;
.mw--lyrics-viewer-bar--no-variation-toggle .mw--lyrics-viewer-bar__spacer {&lt;br /&gt;
	order: -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Citizen 스티키 헤더는 다중 선택 모드이고 변형 토글이 존재할 때만 동작하도록 고정합니다.&lt;br /&gt;
   Citizen이 밴드 클래스(citizen-overflow-sticky-row)를 사후에 부여하고 wikipage.content마다 되살리므로&lt;br /&gt;
   클래스를 제거하는 대신 스티키 이동의 근원인 transform/animation을 무효화합니다. */&lt;br /&gt;
.mw--lyrics-container:not( [data-lyrics-viewer-multimode=&amp;quot;true&amp;quot;] ) &amp;gt; .citizen-overflow-sticky-row,&lt;br /&gt;
.mw--lyrics-container:has( &amp;gt; .citizen-overflow-sticky-row.mw--lyrics-viewer-bar--no-variation-toggle ) &amp;gt; .citizen-overflow-sticky-row {&lt;br /&gt;
	transform: none !important;&lt;br /&gt;
	animation: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 뷰어 바 기본 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-bar {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	gap: var(--space-xs);&lt;br /&gt;
	flex-wrap: wrap;&lt;br /&gt;
	margin-bottom: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-bar__spacer {&lt;br /&gt;
	flex: 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-variation {&lt;br /&gt;
	max-width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Dialog 내부 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-dialog-body {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	flex-direction: column;&lt;br /&gt;
	gap: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-dialog-target {&lt;br /&gt;
	margin: 0;&lt;br /&gt;
	color: var(--color-subtle);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card {&lt;br /&gt;
	border: var(--border-subtle);&lt;br /&gt;
	border-radius: var(--border-radius-base);&lt;br /&gt;
	padding: var(--space-xs) var(--space-sm);&lt;br /&gt;
	cursor: pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card:hover {&lt;br /&gt;
	background-color: var(--background-color-button-quiet--hover);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card__header {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	gap: var(--space-xs);&lt;br /&gt;
	cursor: pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card__description {&lt;br /&gt;
	margin: var(--space-xxs) 0 0 var(--space-lg);&lt;br /&gt;
	color: var(--color-subtle);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-type-card__input-wrap {&lt;br /&gt;
	margin: var(--space-xs) 0 0 var(--space-lg);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-dialog-preview {&lt;br /&gt;
	padding: var(--space-xs);&lt;br /&gt;
	background: var(--background-color-neutral-subtle);&lt;br /&gt;
	border-radius: var(--border-radius-base);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 편집 모달 내부 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-edit-dialog-body {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	flex-direction: column;&lt;br /&gt;
	gap: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-textarea .cdx-text-area__textarea {&lt;br /&gt;
	min-height: 50vh;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-diff {&lt;br /&gt;
	border: var(--border-subtle);&lt;br /&gt;
	border-radius: var(--border-radius-base);&lt;br /&gt;
	padding: var(--space-xs);&lt;br /&gt;
	overflow-x: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-diff-empty {&lt;br /&gt;
	margin: 0;&lt;br /&gt;
	color: var(--color-subtle);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-edit-error {&lt;br /&gt;
	margin: 0;&lt;br /&gt;
	color: var(--color-error);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Popover 내부 레이아웃 */&lt;br /&gt;
.mw--lyrics-viewer-popover-body {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	flex-direction: column;&lt;br /&gt;
	gap: var(--space-md);&lt;br /&gt;
	width: max-content;&lt;br /&gt;
	max-width: calc(100vw - 32px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-popover-row {&lt;br /&gt;
	display: flex;&lt;br /&gt;
	align-items: center;&lt;br /&gt;
	justify-content: space-between;&lt;br /&gt;
	gap: var(--space-sm);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-popover-row__label {&lt;br /&gt;
	font-weight: var(--font-weight-medium);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-viewer-popover-section-title {&lt;br /&gt;
	margin-bottom: var(--space-xxs);&lt;br /&gt;
	font-weight: var(--font-weight-medium);&lt;br /&gt;
	font-size: var(--font-size-small);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 가사 본문 편집 버튼 — 우상단 절대 위치 */&lt;br /&gt;
.mw--lyrics-variation .ts-lyric-content {&lt;br /&gt;
	position: relative;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.mw--lyrics-variation-edit {&lt;br /&gt;
	position: absolute;&lt;br /&gt;
	top: var(--space-xs);&lt;br /&gt;
	right: var(--space-xs);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 작은 화면에서 다른 가사 추가 버튼은 아이콘만 표시 */&lt;br /&gt;
@media (max-width: 639px) {&lt;br /&gt;
	.mw--lyrics-viewer-add__label {&lt;br /&gt;
		display: none;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.js&amp;diff=2823</id>
		<title>미디어위키:가사.js</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:%EA%B0%80%EC%82%AC.js&amp;diff=2823"/>
		<updated>2026-08-30T10:12:05Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: /* eslint-disable mediawiki/class-doc */ /**  * MediaWiki 가사 Viewer — MediaWiki: 네임스페이스 JS  * UseResource 확장으로 직접 로드되는 클라이언트 스크립트입니다.  * 리소스 모듈 등록 없이 MediaWiki: 네임스페이스의 JS 페이지로 동작합니다.  * 의존성: MediaWiki 1.45+ ResourceLoader가 제공하는 vue, @wikimedia/codex (또는 codex)  */ /* global mw, Vue, require */ ( function () { 	&amp;#039;use strict&amp;#039;;  	// 디버그 플래그...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* eslint-disable mediawiki/class-doc */&lt;br /&gt;
/**&lt;br /&gt;
 * MediaWiki 가사 Viewer — MediaWiki: 네임스페이스 JS&lt;br /&gt;
 * UseResource 확장으로 직접 로드되는 클라이언트 스크립트입니다.&lt;br /&gt;
 * 리소스 모듈 등록 없이 MediaWiki: 네임스페이스의 JS 페이지로 동작합니다.&lt;br /&gt;
 * 의존성: MediaWiki 1.45+ ResourceLoader가 제공하는 vue, @wikimedia/codex (또는 codex)&lt;br /&gt;
 */&lt;br /&gt;
/* global mw, Vue, require */&lt;br /&gt;
( function () {&lt;br /&gt;
	&#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
	// 디버그 플래그 저장 키&lt;br /&gt;
	var DEBUG_STORAGE_KEY = &#039;mw-lyrics-viewer-debug&#039;;&lt;br /&gt;
&lt;br /&gt;
	// 디버그 로그 버퍼 저장 키 — 새로고침으로 콘솔이 초기화되어도 이전 세션 로그를 확인할 수 있게 버퍼링합니다.&lt;br /&gt;
	var DEBUG_LOG_STORAGE_KEY = &#039;mw-lyrics-viewer-debuglog&#039;;&lt;br /&gt;
	var DEBUG_LOG_MAX_ENTRIES = 50;&lt;br /&gt;
&lt;br /&gt;
	// 디버그 활성화 여부 확인&lt;br /&gt;
	function isDebugEnabled() {&lt;br /&gt;
		try {&lt;br /&gt;
			// 디버그 플래그가 저장되어 있으면 활성화로 반환합니다.&lt;br /&gt;
			if ( window.localStorage.getItem( DEBUG_STORAGE_KEY ) === &#039;1&#039; ) { return true; }&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
		try {&lt;br /&gt;
			return /(?:\?|&amp;amp;)lyricsViewerDebug=1(?:&amp;amp;|$)/.test( window.location.search );&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 디버그 로그 출력&lt;br /&gt;
	function debugLog() {&lt;br /&gt;
		// 디버그가 비활성화되어 있다면 중단합니다.&lt;br /&gt;
		if ( !isDebugEnabled() ) { return; }&lt;br /&gt;
&lt;br /&gt;
		var args = Array.prototype.slice.call( arguments );&lt;br /&gt;
		args.unshift( &#039;[LyricsViewer:debug]&#039; );&lt;br /&gt;
		try { window.console.log.apply( window.console, args ); } catch ( error ) {}&lt;br /&gt;
		bufferDebugLog( &#039;log&#039;, args );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 디버그 경고 출력&lt;br /&gt;
	function debugWarn() {&lt;br /&gt;
		var args = Array.prototype.slice.call( arguments );&lt;br /&gt;
		args.unshift( &#039;[LyricsViewer]&#039; );&lt;br /&gt;
		try { window.console.warn.apply( window.console, args ); } catch ( error ) {}&lt;br /&gt;
		bufferDebugLog( &#039;warn&#039;, args );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 디버그 로그를 저장소 버퍼에 기록&lt;br /&gt;
	function bufferDebugLog( level, logArguments ) {&lt;br /&gt;
		try {&lt;br /&gt;
			var entries = JSON.parse( window.sessionStorage.getItem( DEBUG_LOG_STORAGE_KEY ) || &#039;[]&#039; );&lt;br /&gt;
			entries.push( {&lt;br /&gt;
				level: level,&lt;br /&gt;
				time: new Date().toISOString(),&lt;br /&gt;
				text: Array.prototype.map.call( logArguments, function ( item ) {&lt;br /&gt;
					// 객체는 사람이 읽을 수 있는 형태로 직렬화합니다.&lt;br /&gt;
					if ( typeof item === &#039;object&#039; ) {&lt;br /&gt;
						try { return JSON.stringify( item ); } catch ( innerError ) { return String( item ); }&lt;br /&gt;
					}&lt;br /&gt;
					return String( item );&lt;br /&gt;
				} ).join( &#039; &#039; )&lt;br /&gt;
			} );&lt;br /&gt;
&lt;br /&gt;
			// 최대 개수를 넘으면 오래된 로그부터 버립니다.&lt;br /&gt;
			if ( entries.length &amp;gt; DEBUG_LOG_MAX_ENTRIES ) { entries = entries.slice( -DEBUG_LOG_MAX_ENTRIES ); }&lt;br /&gt;
			window.sessionStorage.setItem( DEBUG_LOG_STORAGE_KEY, JSON.stringify( entries ) );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 새로고침 전 세션에 기록된 디버그 로그를 콘솔로 다시 출력&lt;br /&gt;
	function dumpBufferedDebugLogs() {&lt;br /&gt;
		try {&lt;br /&gt;
			var rawEntries = window.sessionStorage.getItem( DEBUG_LOG_STORAGE_KEY );&lt;br /&gt;
			window.sessionStorage.removeItem( DEBUG_LOG_STORAGE_KEY );&lt;br /&gt;
			var entries = rawEntries ? JSON.parse( rawEntries ) : [];&lt;br /&gt;
&lt;br /&gt;
			// 이전 세션에 기록된 로그가 있다면 출력합니다.&lt;br /&gt;
			if ( entries.length ) {&lt;br /&gt;
				entries.forEach( function ( entry ) {&lt;br /&gt;
					var consoleMethod = entry.level === &#039;warn&#039; ? window.console.warn : window.console.log;&lt;br /&gt;
					consoleMethod.call( window.console, &#039;[LyricsViewer:debug@&#039; + entry.time + &#039;]&#039;, entry.text );&lt;br /&gt;
				} );&lt;br /&gt;
			}&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 전역 디버그 핸들 노출&lt;br /&gt;
	try {&lt;br /&gt;
		window.__lyricsViewerDebug = window.__lyricsViewerDebug || {};&lt;br /&gt;
		window.__lyricsViewerDebug.enable = function () {&lt;br /&gt;
			try { window.localStorage.setItem( DEBUG_STORAGE_KEY, &#039;1&#039; ); } catch ( error ) {}&lt;br /&gt;
			try { window.console.info( &#039;[LyricsViewer] 디버그 모드 활성화 — 새로고침 후 상세 로그가 출력되며, 새로고침 전 세션의 로그도 함께 출력됩니다.&#039; ); } catch ( error2 ) {}&lt;br /&gt;
		};&lt;br /&gt;
		window.__lyricsViewerDebug.disable = function () {&lt;br /&gt;
			try { window.localStorage.removeItem( DEBUG_STORAGE_KEY ); } catch ( error ) {}&lt;br /&gt;
			try { window.console.info( &#039;[LyricsViewer] 디버그 모드 비활성화&#039; ); } catch ( error2 ) {}&lt;br /&gt;
		};&lt;br /&gt;
		window.__lyricsViewerDebug.isEnabled = isDebugEnabled;&lt;br /&gt;
	} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
	// ※ Codex 아이콘은 ResourceLoader에 모듈이 존재하지 않으므로 MediaWiki 1.44+의&lt;br /&gt;
	//   list=codexicons API를 통해 필요한 아이콘만 URL 기반으로 동적 로드합니다.&lt;br /&gt;
	//   외부 CDN보다 Same-Origin이라 더 안전합니다.&lt;br /&gt;
	//   https://www.mediawiki.org/wiki/API:CodexIcons&lt;br /&gt;
	//   &amp;quot;For performance reasons, there is no ResourceLoader module named codex-icons containing all the icons&amp;quot;&lt;br /&gt;
	var API_ICON_NAMES = [ &#039;cdxIconAdd&#039;, &#039;cdxIconEdit&#039;, &#039;cdxIconSettings&#039; ];&lt;br /&gt;
&lt;br /&gt;
	// 아이콘 세션 캐시 키 — 대상 아이콘 목록이나 응답 형식이 바뀌면 버전을 올립니다.&lt;br /&gt;
	var API_ICON_STORAGE_KEY = &#039;mw-lyrics-viewer-codex-icons-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
	var apiIconCache = null;&lt;br /&gt;
	var apiIconFetchPromise = null;&lt;br /&gt;
&lt;br /&gt;
	// 라디오 그룹 고유 번호 카운터&lt;br /&gt;
	var radioGroupCounter = 0;&lt;br /&gt;
&lt;br /&gt;
	// 캐시된 아이콘을 콜백으로 전달&lt;br /&gt;
	function deliverCachedIcons( callback ) {&lt;br /&gt;
		try { callback( apiIconCache ); } catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 진행 중인 요청에 콜백 연결&lt;br /&gt;
	function attachToPendingFetch( callback ) {&lt;br /&gt;
		apiIconFetchPromise.then( function ( cachedIcons ) {&lt;br /&gt;
			try { callback( cachedIcons ); } catch ( error ) {}&lt;br /&gt;
		} ).catch( function () {} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// API 응답에서 필요한 아이콘만 추출&lt;br /&gt;
	function pickNeededIcons( iconData ) {&lt;br /&gt;
		var pickedIcons = {};&lt;br /&gt;
		API_ICON_NAMES.forEach( function ( iconName ) {&lt;br /&gt;
			pickedIcons[ iconName ] = iconData[ iconName ] || null;&lt;br /&gt;
		} );&lt;br /&gt;
		return pickedIcons;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// API URL 생성&lt;br /&gt;
	function buildCodexIconsApiUrl() {&lt;br /&gt;
		var namesParam = encodeURIComponent( API_ICON_NAMES.join( &#039;|&#039; ) );&lt;br /&gt;
		var queryString = &#039;action=query&amp;amp;list=codexicons&amp;amp;names=&#039; + namesParam + &#039;&amp;amp;format=json&amp;amp;formatversion=2&#039;;&lt;br /&gt;
		try {&lt;br /&gt;
			return mw.util.wikiScript( &#039;api&#039; ) + &#039;?&#039; + queryString;&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return &#039;/w/api.php?&#039; + queryString;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 세션 저장소에서 아이콘 캐시를 읽습니다. 형식이 맞지 않으면 캐시 없음으로 취급합니다.&lt;br /&gt;
	function readIconsFromSessionStorage() {&lt;br /&gt;
		try {&lt;br /&gt;
			var storedIcons = JSON.parse( window.sessionStorage.getItem( API_ICON_STORAGE_KEY ) || &#039;null&#039; );&lt;br /&gt;
&lt;br /&gt;
			// 필요한 아이콘 이름이 모두 갖춰진 객체인지 검사합니다.&lt;br /&gt;
			if ( !storedIcons || typeof storedIcons !== &#039;object&#039; ) { return null; }&lt;br /&gt;
			for ( var index = 0; index &amp;lt; API_ICON_NAMES.length; index++ ) {&lt;br /&gt;
				if ( !( API_ICON_NAMES[ index ] in storedIcons ) ) { return null; }&lt;br /&gt;
			}&lt;br /&gt;
			return storedIcons;&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 아이콘 캐시를 세션 저장소에 기록합니다. 실제 아이콘 데이터가 하나도 없다면 기록하지 않습니다.&lt;br /&gt;
	function saveIconsToSessionStorage( icons ) {&lt;br /&gt;
		try {&lt;br /&gt;
			var hasIcon = false;&lt;br /&gt;
			API_ICON_NAMES.forEach( function ( iconName ) {&lt;br /&gt;
				if ( icons[ iconName ] ) { hasIcon = true; }&lt;br /&gt;
			} );&lt;br /&gt;
			if ( !hasIcon ) { return; }&lt;br /&gt;
			window.sessionStorage.setItem( API_ICON_STORAGE_KEY, JSON.stringify( icons ) );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// list=codexicons API에서 아이콘 JSON 가져오기&lt;br /&gt;
	function fetchIconsFromApi( callback ) {&lt;br /&gt;
		// 캐시된 아이콘이 있다면 즉시 전달합니다.&lt;br /&gt;
		if ( apiIconCache ) { deliverCachedIcons( callback ); return; }&lt;br /&gt;
&lt;br /&gt;
		// 진행 중인 요청이 있다면 콜백만 연결합니다.&lt;br /&gt;
		if ( apiIconFetchPromise ) { attachToPendingFetch( callback ); return; }&lt;br /&gt;
&lt;br /&gt;
		// 세션 저장소에 캐시가 남아 있다면 요청 없이 사용합니다.&lt;br /&gt;
		var storedIcons = readIconsFromSessionStorage();&lt;br /&gt;
		if ( storedIcons ) {&lt;br /&gt;
			apiIconCache = storedIcons;&lt;br /&gt;
			deliverCachedIcons( callback );&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// fetch API를 사용할 수 없다면 경고를 남기고 중단합니다.&lt;br /&gt;
		if ( typeof window.fetch !== &#039;function&#039; ) { debugWarn( &#039;fetch API를 사용할 수 없어 list=codexicons에서 아이콘을 가져올 수 없습니다.&#039; ); return; }&lt;br /&gt;
&lt;br /&gt;
		var apiUrl = buildCodexIconsApiUrl();&lt;br /&gt;
&lt;br /&gt;
		// API 요청 시작&lt;br /&gt;
		apiIconFetchPromise = window.fetch( apiUrl ).then( function ( response ) {&lt;br /&gt;
			// 응답이 정상이 아니라면 에러를 던집니다.&lt;br /&gt;
			if ( !response.ok ) { throw new Error( &#039;HTTP &#039; + response.status ); }&lt;br /&gt;
			return response.json();&lt;br /&gt;
		} ).then( function ( responseData ) {&lt;br /&gt;
			var iconData = responseData &amp;amp;&amp;amp; responseData.query &amp;amp;&amp;amp; responseData.query.codexicons ? responseData.query.codexicons : responseData;&lt;br /&gt;
			var loadedIcons = pickNeededIcons( iconData );&lt;br /&gt;
			apiIconCache = loadedIcons;&lt;br /&gt;
			saveIconsToSessionStorage( loadedIcons );&lt;br /&gt;
			debugLog( &#039;list=codexicons API에서 아이콘 로드 성공:&#039;, apiUrl, &#039;iconAdd:&#039;, !!loadedIcons.cdxIconAdd, &#039;iconEdit:&#039;, !!loadedIcons.cdxIconEdit, &#039;iconSettings:&#039;, !!loadedIcons.cdxIconSettings );&lt;br /&gt;
			return loadedIcons;&lt;br /&gt;
		} ).catch( function ( fetchError ) {&lt;br /&gt;
			debugWarn( &#039;list=codexicons API에서 아이콘을 가져오지 못했습니다: &#039; + apiUrl + &#039; — &#039; + ( fetchError &amp;amp;&amp;amp; fetchError.message ) );&lt;br /&gt;
			apiIconFetchPromise = null;&lt;br /&gt;
			throw fetchError;&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		attachToPendingFetch( callback );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 브라우저 저장소 키&lt;br /&gt;
	var STORAGE_PREFIX = &#039;mw-lyrics-viewer-&#039;;&lt;br /&gt;
	var STORAGE_KEYS = {&lt;br /&gt;
		multimode: STORAGE_PREFIX + &#039;multimode&#039;,&lt;br /&gt;
		fontSize: STORAGE_PREFIX + &#039;fontSize&#039;,&lt;br /&gt;
		fontFamily: STORAGE_PREFIX + &#039;fontFamily&#039;,&lt;br /&gt;
		align: STORAGE_PREFIX + &#039;align&#039;,&lt;br /&gt;
		lineHeight: STORAGE_PREFIX + &#039;lineHeight&#039;,&lt;br /&gt;
		letterSpacing: STORAGE_PREFIX + &#039;letterSpacing&#039;&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	// UI 문자열&lt;br /&gt;
	var MESSAGES = {&lt;br /&gt;
		defaultVariationLabel: &#039;가사&#039;,&lt;br /&gt;
		documentNamespacePrefix: &#039;가사:&#039;,&lt;br /&gt;
		addButtonLabel: &#039;다른 가사 추가&#039;,&lt;br /&gt;
		addDialogTitle: &#039;다른 가사 추가&#039;,&lt;br /&gt;
		targetDocumentLabel: &#039;대상 문서:&#039;,&lt;br /&gt;
		customVariationLabel: &#039;그 외&#039;,&lt;br /&gt;
		customVariationPlaceholder: &#039;가사 변형 이름 입력&#039;,&lt;br /&gt;
		documentNameLabel: &#039;문서 이름:&#039;,&lt;br /&gt;
		createLabel: &#039;생성&#039;,&lt;br /&gt;
		editLabel: &#039;편집&#039;,&lt;br /&gt;
		editButtonAriaLabel: &#039;편집&#039;,&lt;br /&gt;
		editButtonTooltip: &#039;편집&#039;,&lt;br /&gt;
		addVariationAriaLabel: &#039;추가&#039;,&lt;br /&gt;
		addVariationTooltip: &#039;추가&#039;,&lt;br /&gt;
		closeLabel: &#039;닫기&#039;,&lt;br /&gt;
		editModalTitle: &#039;가사 편집&#039;,&lt;br /&gt;
		saveLabel: &#039;저장&#039;,&lt;br /&gt;
		editSummaryPlaceholder: &#039;편집 요약&#039;,&lt;br /&gt;
		unsavedChangesMessage: &#039;저장하지 않은 변경 사항이 있습니다. 정말로 닫으시겠습니까?&#039;,&lt;br /&gt;
		saveFailedMessage: &#039;저장에 실패했습니다. 잠시 후 다시 시도해 주세요.&#039;,&lt;br /&gt;
		diffLabel: &#039;차이 보기&#039;,&lt;br /&gt;
		noDiffLabel: &#039;차이 없음&#039;,&lt;br /&gt;
		displaySettingsTitle: &#039;가사 표시 설정&#039;,&lt;br /&gt;
		displaySettingsAriaLabel: &#039;가사 표시 설정&#039;,&lt;br /&gt;
		multiSelectLabel: &#039;다중 선택&#039;,&lt;br /&gt;
		fontSizeLabel: &#039;글자 크기&#039;,&lt;br /&gt;
		fontSizeSmall: &#039;작게&#039;,&lt;br /&gt;
		fontSizeMedium: &#039;보통&#039;,&lt;br /&gt;
		fontSizeLarge: &#039;크게&#039;,&lt;br /&gt;
		fontFamilyLabel: &#039;글꼴&#039;,&lt;br /&gt;
		fontFamilyDefault: &#039;기본&#039;,&lt;br /&gt;
		fontFamilySerif: &#039;세리프&#039;,&lt;br /&gt;
		fontFamilySans: &#039;산세리프&#039;,&lt;br /&gt;
		fontFamilyMono: &#039;고정폭&#039;,&lt;br /&gt;
		alignmentLabel: &#039;정렬&#039;,&lt;br /&gt;
		alignLeft: &#039;왼쪽&#039;,&lt;br /&gt;
		alignCenter: &#039;가운데&#039;,&lt;br /&gt;
		alignRight: &#039;오른쪽&#039;,&lt;br /&gt;
		lineHeightLabel: &#039;줄 간격&#039;,&lt;br /&gt;
		lineHeightNarrow: &#039;좁게&#039;,&lt;br /&gt;
		lineHeightNormal: &#039;보통&#039;,&lt;br /&gt;
		lineHeightWide: &#039;넓게&#039;,&lt;br /&gt;
		letterSpacingLabel: &#039;글자 간격&#039;,&lt;br /&gt;
		letterSpacingNarrow: &#039;좁게&#039;,&lt;br /&gt;
		letterSpacingNormal: &#039;보통&#039;,&lt;br /&gt;
		letterSpacingWide: &#039;넓게&#039;,&lt;br /&gt;
		variationTypeTranslation: { value: &#039;번역&#039;, label: &#039;번역&#039;, description: &#039;외국어 가사를 한국어로 번역한 가사&#039; },&lt;br /&gt;
		variationTypePronunciation: { value: &#039;발음&#039;, label: &#039;발음&#039;, description: &#039;가사를 발음에 맞게 표기한 가사&#039; },&lt;br /&gt;
		variationTypePrompt: { value: &#039;프롬프트&#039;, label: &#039;프롬프트&#039;, description: &#039;AI 음악 생성 도구 등에 사용할 수 있도록 구조나 지시문이 포함된 가사&#039; }&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	// 표준 가사 문서 유형&lt;br /&gt;
	var STANDARD_LYRICS_TYPES = [&lt;br /&gt;
		MESSAGES.variationTypeTranslation,&lt;br /&gt;
		MESSAGES.variationTypePronunciation,&lt;br /&gt;
		MESSAGES.variationTypePrompt&lt;br /&gt;
	];&lt;br /&gt;
&lt;br /&gt;
	// 스타일 매핑 테이블&lt;br /&gt;
	var FONT_SIZE_MAP = { small: &#039;0.875em&#039;, medium: &#039;&#039;, large: &#039;1.2em&#039; };&lt;br /&gt;
	var FONT_FAMILY_MAP = {&lt;br /&gt;
		default: &#039;&#039;,&lt;br /&gt;
		serif: &#039;serif&#039;,&lt;br /&gt;
		&#039;sans-serif&#039;: &#039;sans-serif&#039;,&lt;br /&gt;
		monospace: &#039;monospace&#039;&lt;br /&gt;
	};&lt;br /&gt;
	var LINE_HEIGHT_MAP = { narrow: &#039;1.4&#039;, normal: &#039;&#039;, wide: &#039;2.0&#039; };&lt;br /&gt;
	var LETTER_SPACING_MAP = { narrow: &#039;-0.02em&#039;, normal: &#039;&#039;, wide: &#039;0.06em&#039; };&lt;br /&gt;
&lt;br /&gt;
	// Viewer 템플릿&lt;br /&gt;
	var VIEWER_TEMPLATE = `&amp;lt;div v-if=&amp;quot;shouldShowVariationToggle&amp;quot; class=&amp;quot;mw--lyrics-viewer-variation&amp;quot;&amp;gt;&amp;lt;cdx-toggle-button-group v-model=&amp;quot;toggleValue&amp;quot; :buttons=&amp;quot;variationToggleButtons&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw--lyrics-viewer-add&amp;quot;&amp;gt;&amp;lt;cdx-button @click=&amp;quot;dialogOpen = true&amp;quot; :aria-label=&amp;quot;messages.addButtonLabel&amp;quot; v-tooltip=&amp;quot;tooltipDirectiveAvailable ? messages.addButtonLabel : null&amp;quot;&amp;gt;&amp;lt;cdx-icon v-if=&amp;quot;iconAdd&amp;quot; :icon=&amp;quot;iconAdd&amp;quot;&amp;gt;&amp;lt;/cdx-icon&amp;gt; &amp;lt;span class=&amp;quot;mw--lyrics-viewer-add__label&amp;quot;&amp;gt;{{ messages.addButtonLabel }}&amp;lt;/span&amp;gt;&amp;lt;/cdx-button&amp;gt;&lt;br /&gt;
  &amp;lt;cdx-dialog v-model:open=&amp;quot;dialogOpen&amp;quot; :title=&amp;quot;messages.addDialogTitle&amp;quot; :use-close-button=&amp;quot;true&amp;quot; :primary-action=&amp;quot;primaryAction&amp;quot; :default-action=&amp;quot;defaultAction&amp;quot; @primary=&amp;quot;handlePrimaryAction&amp;quot; @default=&amp;quot;dialogOpen = false&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mw--lyrics-viewer-dialog-body&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;p class=&amp;quot;mw--lyrics-viewer-dialog-target&amp;quot;&amp;gt;{{ messages.targetDocumentLabel }} &amp;lt;strong&amp;gt;{{ songPageTitle }}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
      &amp;lt;div v-for=&amp;quot;lyricType in standardTypes&amp;quot; :key=&amp;quot;lyricType.value&amp;quot; class=&amp;quot;mw--lyrics-viewer-type-card&amp;quot; :style=&amp;quot;{ background: selectedType===lyricType.value ? &#039;var(--background-color-progressive-subtle)&#039; : &#039;&#039; }&amp;quot; @click=&amp;quot;selectedType=lyricType.value&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;label class=&amp;quot;mw--lyrics-viewer-type-card__header&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;radio&amp;quot; :name=&amp;quot;lyricTypeRadioGroupName&amp;quot; :value=&amp;quot;lyricType.value&amp;quot; v-model=&amp;quot;selectedType&amp;quot; /&amp;gt; &amp;lt;strong&amp;gt;{{ lyricType.label }}&amp;lt;/strong&amp;gt;&amp;lt;/label&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;mw--lyrics-viewer-type-card__description&amp;quot;&amp;gt;{{ lyricType.description }}&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div class=&amp;quot;mw--lyrics-viewer-type-card&amp;quot; :style=&amp;quot;{ background: selectedType===&#039;__custom&#039; ? &#039;var(--background-color-progressive-subtle)&#039; : &#039;&#039; }&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;label class=&amp;quot;mw--lyrics-viewer-type-card__header&amp;quot; @click=&amp;quot;selectedType=&#039;__custom&#039;&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;radio&amp;quot; :name=&amp;quot;lyricTypeRadioGroupName&amp;quot; value=&amp;quot;__custom&amp;quot; v-model=&amp;quot;selectedType&amp;quot; /&amp;gt; &amp;lt;strong&amp;gt;{{ messages.customVariationLabel }}&amp;lt;/strong&amp;gt;&amp;lt;/label&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;mw--lyrics-viewer-type-card__input-wrap&amp;quot;&amp;gt;&amp;lt;cdx-text-input v-model=&amp;quot;customVariationName&amp;quot; :placeholder=&amp;quot;messages.customVariationPlaceholder&amp;quot; @focus=&amp;quot;selectedType=&#039;__custom&#039;&amp;quot;&amp;gt;&amp;lt;/cdx-text-input&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div v-if=&amp;quot;previewDocumentTitle&amp;quot; class=&amp;quot;mw--lyrics-viewer-dialog-preview&amp;quot;&amp;gt;{{ messages.documentNameLabel }} &amp;lt;code&amp;gt;{{ previewDocumentTitle }}&amp;lt;/code&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/cdx-dialog&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw--lyrics-viewer-bar__spacer&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw--lyrics-viewer-settings&amp;quot;&amp;gt;&amp;lt;cdx-button ref=&amp;quot;settingsButtonAnchorRef&amp;quot; :aria-label=&amp;quot;messages.displaySettingsAriaLabel&amp;quot; v-tooltip=&amp;quot;tooltipDirectiveAvailable ? messages.displaySettingsAriaLabel : null&amp;quot; @click=&amp;quot;popoverOpen = !popoverOpen&amp;quot;&amp;gt;&amp;lt;cdx-icon v-if=&amp;quot;iconSettings&amp;quot; :icon=&amp;quot;iconSettings&amp;quot;&amp;gt;&amp;lt;/cdx-icon&amp;gt;&amp;lt;/cdx-button&amp;gt;&lt;br /&gt;
  &amp;lt;cdx-popover v-model:open=&amp;quot;popoverOpen&amp;quot; :anchor=&amp;quot;settingsButtonAnchorEl&amp;quot; :title=&amp;quot;messages.displaySettingsTitle&amp;quot; :use-close-button=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-body&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-row&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;span class=&amp;quot;mw--lyrics-viewer-popover-row__label&amp;quot;&amp;gt;{{ messages.multiSelectLabel }}&amp;lt;/span&amp;gt;&lt;br /&gt;
        &amp;lt;cdx-toggle-switch v-if=&amp;quot;isToggleSwitchAvailable&amp;quot; :model-value=&amp;quot;displayState.isMultimodeEnabled&amp;quot; @update:model-value=&amp;quot;handleMultimodeChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-switch&amp;gt;&lt;br /&gt;
        &amp;lt;input v-else type=&amp;quot;checkbox&amp;quot; :checked=&amp;quot;displayState.isMultimodeEnabled&amp;quot; @change=&amp;quot;handleMultimodeChange($event.target.checked)&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.fontSizeLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.fontSize&amp;quot; :buttons=&amp;quot;fontSizeButtons&amp;quot; @update:model-value=&amp;quot;handleFontSizeChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.fontFamilyLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.fontFamily&amp;quot; :buttons=&amp;quot;fontFamilyButtons&amp;quot; @update:model-value=&amp;quot;handleFontFamilyChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.alignmentLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.textAlignment&amp;quot; :buttons=&amp;quot;alignmentButtons&amp;quot; @update:model-value=&amp;quot;handleAlignmentChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.lineHeightLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.lineHeight&amp;quot; :buttons=&amp;quot;lineHeightButtons&amp;quot; @update:model-value=&amp;quot;handleLineHeightChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
      &amp;lt;div&amp;gt;&amp;lt;div class=&amp;quot;mw--lyrics-viewer-popover-section-title&amp;quot;&amp;gt;{{ messages.letterSpacingLabel }}&amp;lt;/div&amp;gt;&amp;lt;cdx-toggle-button-group :model-value=&amp;quot;displayState.letterSpacing&amp;quot; :buttons=&amp;quot;letterSpacingButtons&amp;quot; @update:model-value=&amp;quot;handleLetterSpacingChange&amp;quot;&amp;gt;&amp;lt;/cdx-toggle-button-group&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/cdx-popover&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;cdx-dialog v-model:open=&amp;quot;editModalOpen&amp;quot; :title=&amp;quot;messages.editModalTitle&amp;quot; :use-close-button=&amp;quot;true&amp;quot; :primary-action=&amp;quot;saveAction&amp;quot; :default-action=&amp;quot;cancelAction&amp;quot; @primary=&amp;quot;handleEditSave&amp;quot; @default=&amp;quot;editModalOpen = false&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div class=&amp;quot;mw--lyrics-viewer-edit-dialog-body&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;p class=&amp;quot;mw--lyrics-viewer-dialog-target&amp;quot;&amp;gt;{{ messages.targetDocumentLabel }} &amp;lt;strong v-if=&amp;quot;editTargetUrl&amp;quot;&amp;gt;&amp;lt;a :href=&amp;quot;editTargetUrl&amp;quot;&amp;gt;{{ editTargetTitle }}&amp;lt;/a&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong v-else&amp;gt;{{ editTargetTitle }}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;cdx-text-area v-model=&amp;quot;wikitext&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-textarea&amp;quot; :disabled=&amp;quot;isFetchingWikitext&amp;quot;&amp;gt;&amp;lt;/cdx-text-area&amp;gt;&lt;br /&gt;
    &amp;lt;cdx-text-input v-model=&amp;quot;editSummary&amp;quot; :placeholder=&amp;quot;messages.editSummaryPlaceholder&amp;quot; :disabled=&amp;quot;isSavingWikitext&amp;quot;&amp;gt;&amp;lt;/cdx-text-input&amp;gt;&lt;br /&gt;
    &amp;lt;div&amp;gt;&amp;lt;cdx-button size=&amp;quot;small&amp;quot; :disabled=&amp;quot;isDiffLoading&amp;quot; @click=&amp;quot;handleDiffRefresh&amp;quot;&amp;gt;{{ messages.diffLabel }}&amp;lt;/cdx-button&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;div v-if=&amp;quot;isDiffShown&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-diff&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;table v-if=&amp;quot;diffHtml&amp;quot; class=&amp;quot;diff&amp;quot;&amp;gt;&amp;lt;colgroup&amp;gt;&amp;lt;col class=&amp;quot;diff-marker&amp;quot;&amp;gt;&amp;lt;col class=&amp;quot;diff-content&amp;quot;&amp;gt;&amp;lt;col class=&amp;quot;diff-marker&amp;quot;&amp;gt;&amp;lt;col class=&amp;quot;diff-content&amp;quot;&amp;gt;&amp;lt;/colgroup&amp;gt;&amp;lt;tbody v-html=&amp;quot;diffHtml&amp;quot;&amp;gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;br /&gt;
      &amp;lt;p v-else-if=&amp;quot;diffErrorMessage&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-error&amp;quot;&amp;gt;{{ diffErrorMessage }}&amp;lt;/p&amp;gt;&lt;br /&gt;
      &amp;lt;p v-else class=&amp;quot;mw--lyrics-viewer-edit-diff-empty&amp;quot;&amp;gt;{{ messages.noDiffLabel }}&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;cdx-message v-if=&amp;quot;isMessageAvailable &amp;amp;&amp;amp; editErrorMessage&amp;quot; type=&amp;quot;error&amp;quot;&amp;gt;{{ editErrorMessage }}&amp;lt;/cdx-message&amp;gt;&lt;br /&gt;
    &amp;lt;p v-else-if=&amp;quot;editErrorMessage&amp;quot; class=&amp;quot;mw--lyrics-viewer-edit-error&amp;quot;&amp;gt;{{ editErrorMessage }}&amp;lt;/p&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/cdx-dialog&amp;gt;`;&lt;br /&gt;
&lt;br /&gt;
	// localStorage 읽기&lt;br /&gt;
	function readFromStorage( key ) {&lt;br /&gt;
		try { return window.localStorage.getItem( key ); } catch ( error ) { return null; }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// localStorage 쓰기&lt;br /&gt;
	function writeToStorage( key, value ) {&lt;br /&gt;
		try { window.localStorage.setItem( key, value ); } catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// API 에러를 사용자에게 표시할 메시지로 변환&lt;br /&gt;
	function formatApiError( apiError ) {&lt;br /&gt;
		var errorData = apiError &amp;amp;&amp;amp; apiError.error;&lt;br /&gt;
		// 표준 API 에러라면 코드와 설명을 함께 표시합니다.&lt;br /&gt;
		if ( errorData &amp;amp;&amp;amp; errorData.info ) { return ( errorData.code ? errorData.code + &#039;: &#039; : &#039;&#039; ) + errorData.info; }&lt;br /&gt;
		return String( ( apiError &amp;amp;&amp;amp; ( apiError.text || apiError.exception ) ) || apiError );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// mw.Api 실패는 (코드, 결과) 인자로 거부되지만 .then 체인에서는 코드 문자열만 전달되므로,&lt;br /&gt;
	// 모든 인자를 받는 .fail에서 결과 객체를 묶어 다시 거부합니다.&lt;br /&gt;
	function normalizeApiPromise( apiPromise ) {&lt;br /&gt;
		var wrapped = window.jQuery.Deferred();&lt;br /&gt;
		apiPromise.done( function ( responseData ) {&lt;br /&gt;
			wrapped.resolve( responseData );&lt;br /&gt;
		} );&lt;br /&gt;
		apiPromise.fail( function ( apiErrorCode, apiResult ) {&lt;br /&gt;
			var errorData = apiResult &amp;amp;&amp;amp; ( apiResult.error || ( apiResult.errors &amp;amp;&amp;amp; apiResult.errors[ 0 ] ) ) || null;&lt;br /&gt;
			wrapped.reject( {&lt;br /&gt;
				code: apiErrorCode,&lt;br /&gt;
				error: errorData,&lt;br /&gt;
				text: apiResult &amp;amp;&amp;amp; apiResult.text,&lt;br /&gt;
				exception: apiResult &amp;amp;&amp;amp; apiResult.exception&lt;br /&gt;
			} );&lt;br /&gt;
		} );&lt;br /&gt;
		return wrapped.promise();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// MediaWiki API가 받아들이는 ISO 8601 타임스탬프 생성&lt;br /&gt;
	function toApiTimestamp( date ) {&lt;br /&gt;
		return date.toISOString().replace( /\.\d+Z$/, &#039;Z&#039; );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 문서 위키텍스트 조회/저장 헬퍼 생성&lt;br /&gt;
	function createLyricsRepository( mwApi ) {&lt;br /&gt;
		return {&lt;br /&gt;
			fetchWikitext: function ( documentTitle ) {&lt;br /&gt;
				// 편집 시작 시각 — 저장 시 starttimestamp로 전달해 그 사이 문서 이동/삭제를 감지합니다.&lt;br /&gt;
				var fetchStartTimestamp = toApiTimestamp( new Date() );&lt;br /&gt;
				return normalizeApiPromise( mwApi.get( {&lt;br /&gt;
					action: &#039;query&#039;,&lt;br /&gt;
					prop: &#039;revisions&#039;,&lt;br /&gt;
					titles: documentTitle,&lt;br /&gt;
					rvslots: &#039;main&#039;,&lt;br /&gt;
					rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
					formatversion: 2&lt;br /&gt;
				} ) ).then( function ( responseData ) {&lt;br /&gt;
					var page = responseData &amp;amp;&amp;amp; responseData.query &amp;amp;&amp;amp; responseData.query.pages ? responseData.query.pages[ 0 ] : null;&lt;br /&gt;
&lt;br /&gt;
					// 문서가 존재하지 않으면 빈 내용의 새 문서로 취급합니다.&lt;br /&gt;
					if ( !page || page.missing ) { return { text: &#039;&#039;, baseTimestamp: &#039;&#039;, startTimestamp: fetchStartTimestamp }; }&lt;br /&gt;
					var revision = page.revisions &amp;amp;&amp;amp; page.revisions[ 0 ];&lt;br /&gt;
					return {&lt;br /&gt;
						text: revision &amp;amp;&amp;amp; revision.slots &amp;amp;&amp;amp; revision.slots.main ? revision.slots.main.content : &#039;&#039;,&lt;br /&gt;
						baseTimestamp: revision ? revision.timestamp : &#039;&#039;,&lt;br /&gt;
						startTimestamp: fetchStartTimestamp&lt;br /&gt;
					};&lt;br /&gt;
				} );&lt;br /&gt;
			},&lt;br /&gt;
			saveWikitext: function ( documentTitle, text, summary, baseTimestamp, startTimestamp ) {&lt;br /&gt;
				var editParams = {&lt;br /&gt;
					action: &#039;edit&#039;,&lt;br /&gt;
					title: documentTitle,&lt;br /&gt;
					text: text,&lt;br /&gt;
					summary: summary&lt;br /&gt;
				};&lt;br /&gt;
				// 불러온 판의 시각과 편집 시작 시각을 넘겨 편집 충돌을 감지합니다.&lt;br /&gt;
				if ( baseTimestamp ) { editParams.basetimestamp = baseTimestamp; }&lt;br /&gt;
				if ( startTimestamp ) { editParams.starttimestamp = startTimestamp; }&lt;br /&gt;
				return normalizeApiPromise( mwApi.postWithEditToken( editParams ) ).then( function ( responseData ) {&lt;br /&gt;
					// 무변경(null edit) 편집도 성공으로 처리합니다.&lt;br /&gt;
					return !!( responseData &amp;amp;&amp;amp; responseData.edit &amp;amp;&amp;amp; responseData.edit.result === &#039;Success&#039; );&lt;br /&gt;
				} );&lt;br /&gt;
			},&lt;br /&gt;
			purgePage: function ( documentTitle ) {&lt;br /&gt;
				return normalizeApiPromise( mwApi.post( {&lt;br /&gt;
					action: &#039;purge&#039;,&lt;br /&gt;
					titles: documentTitle,&lt;br /&gt;
					forcelinkupdate: 1&lt;br /&gt;
				} ) );&lt;br /&gt;
			},&lt;br /&gt;
			fetchWikitextDiff: function ( fromText, toText ) {&lt;br /&gt;
				return normalizeApiPromise( mwApi.get( {&lt;br /&gt;
					action: &#039;compare&#039;,&lt;br /&gt;
					fromslots: &#039;main&#039;,&lt;br /&gt;
					&#039;fromtext-main&#039;: fromText,&lt;br /&gt;
					toslots: &#039;main&#039;,&lt;br /&gt;
					&#039;totext-main&#039;: toText,&lt;br /&gt;
					difftype: &#039;table&#039;,&lt;br /&gt;
					formatversion: 2&lt;br /&gt;
				} ) ).then( function ( responseData ) {&lt;br /&gt;
					return responseData &amp;amp;&amp;amp; responseData.compare &amp;amp;&amp;amp; responseData.compare.body ? responseData.compare.body : &#039;&#039;;&lt;br /&gt;
				} );&lt;br /&gt;
			}&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 현재 문서 제목 반환 — wgPageName은 URL 형식이므로 밑줄을 공백으로 되돌립니다.&lt;br /&gt;
	function getCurrentPageTitle() {&lt;br /&gt;
		try { return String( mw.config.get( &#039;wgPageName&#039; ) || &#039;&#039; ).replace( /_/g, &#039; &#039; ); } catch ( error ) { return &#039;&#039;; }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 문서 제목 생성 — mw.Title로 정규화된 문서 제목을 만들어 반환합니다.&lt;br /&gt;
	// 곡 문서 제목이나 변형 이름에 제목으로 쓸 수 없는 문자가 포함되었다면 null을 반환합니다.&lt;br /&gt;
	function buildLyricsDocumentTitle( songPageTitle, variationName ) {&lt;br /&gt;
		var rawTitle = MESSAGES.documentNamespacePrefix + songPageTitle + ( variationName ? &#039;/&#039; + variationName : &#039;&#039; );&lt;br /&gt;
		try {&lt;br /&gt;
			var title = mw.Title.newFromText( rawTitle );&lt;br /&gt;
			return title ? title.getPrefixedText() : null;&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 전역 표시 상태 생성&lt;br /&gt;
	function createGlobalDisplayState( Vue ) {&lt;br /&gt;
		return Vue.reactive( {&lt;br /&gt;
			isMultimodeEnabled: readFromStorage( STORAGE_KEYS.multimode ) === &#039;true&#039;,&lt;br /&gt;
			fontSize: readFromStorage( STORAGE_KEYS.fontSize ) || &#039;medium&#039;,&lt;br /&gt;
			fontFamily: readFromStorage( STORAGE_KEYS.fontFamily ) || &#039;default&#039;,&lt;br /&gt;
			textAlignment: readFromStorage( STORAGE_KEYS.align ) || &#039;left&#039;,&lt;br /&gt;
			lineHeight: readFromStorage( STORAGE_KEYS.lineHeight ) || &#039;normal&#039;,&lt;br /&gt;
			letterSpacing: readFromStorage( STORAGE_KEYS.letterSpacing ) || &#039;normal&#039;&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 다른 탭의 저장소 변경을 전역 상태에 동기화&lt;br /&gt;
	function syncDisplayStateWithStorage( globalDisplayState ) {&lt;br /&gt;
		try {&lt;br /&gt;
			window.addEventListener( &#039;storage&#039;, function ( storageEvent ) {&lt;br /&gt;
				// 변경된 저장소 키에 따라 상태를 갱신합니다.&lt;br /&gt;
				if ( storageEvent.key === STORAGE_KEYS.multimode ) {&lt;br /&gt;
					globalDisplayState.isMultimodeEnabled = storageEvent.newValue === &#039;true&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.fontSize ) {&lt;br /&gt;
					globalDisplayState.fontSize = storageEvent.newValue || &#039;medium&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.fontFamily ) {&lt;br /&gt;
					globalDisplayState.fontFamily = storageEvent.newValue || &#039;default&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.align ) {&lt;br /&gt;
					globalDisplayState.textAlignment = storageEvent.newValue || &#039;left&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.lineHeight ) {&lt;br /&gt;
					globalDisplayState.lineHeight = storageEvent.newValue || &#039;normal&#039;;&lt;br /&gt;
				} else if ( storageEvent.key === STORAGE_KEYS.letterSpacing ) {&lt;br /&gt;
					globalDisplayState.letterSpacing = storageEvent.newValue || &#039;normal&#039;;&lt;br /&gt;
				}&lt;br /&gt;
			} );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 변형 데이터 수집&lt;br /&gt;
	function collectVariationData( viewerContainer ) {&lt;br /&gt;
		var variationElements = Array.from(&lt;br /&gt;
			viewerContainer.querySelectorAll( &#039;.mw--lyrics-variation[data-lyric-variation]&#039; )&lt;br /&gt;
		);&lt;br /&gt;
		var variationValues = variationElements.map( function ( element ) {&lt;br /&gt;
			return element.getAttribute( &#039;data-lyric-variation&#039; ) || &#039;&#039;;&lt;br /&gt;
		} );&lt;br /&gt;
		var hasNonDefaultVariation = variationValues.some( function ( value ) { return value !== &#039;&#039;; } );&lt;br /&gt;
		var existingVariationSet = new Set( variationValues );&lt;br /&gt;
		var variationToggleButtons = variationValues.map( function ( value ) {&lt;br /&gt;
			return { value: value, label: value === &#039;&#039; ? MESSAGES.defaultVariationLabel : value };&lt;br /&gt;
		} );&lt;br /&gt;
		var shouldShowVariationToggle = hasNonDefaultVariation &amp;amp;&amp;amp; variationValues.length &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			variationElements: variationElements,&lt;br /&gt;
			variationValues: variationValues,&lt;br /&gt;
			existingVariationSet: existingVariationSet,&lt;br /&gt;
			variationToggleButtons: variationToggleButtons,&lt;br /&gt;
			shouldShowVariationToggle: shouldShowVariationToggle&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 서버 렌더링된 뷰어 바 참조&lt;br /&gt;
	function findViewerBar( viewerContainer ) {&lt;br /&gt;
		var viewerBar = viewerContainer.querySelector( &#039;:scope &amp;gt; .mw--lyrics-viewer-bar&#039; );&lt;br /&gt;
&lt;br /&gt;
		// 뷰어 바가 없다면 경고를 남기고 null을 반환합니다.&lt;br /&gt;
		if ( !viewerBar ) {&lt;br /&gt;
			debugWarn( &#039;컨테이너 안에서 .mw--lyrics-viewer-bar를 찾을 수 없어 Viewer를 초기화하지 않습니다. 서버 마크업에 뷰어 바가 렌더링되어 있는지 확인하세요.&#039; );&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return viewerBar;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가로 넘침이 실제로 생겼는지 확인해 컨테이너 속성을 갱신&lt;br /&gt;
	function updateOverflowState( viewerContainer ) {&lt;br /&gt;
		// 변형 컨테이너가 없다면 경고를 남기고 판단하지 않습니다.&lt;br /&gt;
		var variationContainer = viewerContainer.querySelector( &#039;.mw--lyrics-variation-container&#039; );&lt;br /&gt;
		if ( !variationContainer ) {&lt;br /&gt;
			debugWarn( &#039;컨테이너 안에서 .mw--lyrics-variation-container를 찾을 수 없어 가로 넘침을 판정하지 않습니다.&#039; );&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// float 해제(clear)가 적용된 상태에서는 폭이 넓어져 판정이 뒤집히고, 해제→재적용이 무한 반복될 수 있습니다.&lt;br /&gt;
		// 속성을 잠시 지운 &#039;해제 상태&#039;에서 측정해 판정한 뒤 필요하면 다시 적용합니다 — 동기 실행이라 중간 상태는 화면에 그려지지 않습니다.&lt;br /&gt;
		if ( viewerContainer.getAttribute( &#039;data-lyrics-viewer-overflow&#039; ) === &#039;true&#039; ) {&lt;br /&gt;
			viewerContainer.removeAttribute( &#039;data-lyrics-viewer-overflow&#039; );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 변형 컨테이너 자신의 넘침은 overflow-X 값과 무관하게 검사합니다 —&lt;br /&gt;
		// Citizen 스킨처럼 컨테이너가 직접 스크롤하지 않고 넘친 내용을 상위 상자가 스크롤/클립하는 환경이 있기 때문입니다.&lt;br /&gt;
		var hasOverflow = variationContainer.scrollWidth &amp;gt; variationContainer.clientWidth;&lt;br /&gt;
&lt;br /&gt;
		if ( !hasOverflow ) {&lt;br /&gt;
			// 컨테이너에서 넘치지 않았다면 넘친 내용을 잘라내는 상위 상자가 있는지 확인합니다.&lt;br /&gt;
			var current = variationContainer.parentElement;&lt;br /&gt;
			while ( current &amp;amp;&amp;amp; current !== document.documentElement ) {&lt;br /&gt;
				if ( current.scrollWidth &amp;gt; current.clientWidth ) {&lt;br /&gt;
					var overflowX = window.getComputedStyle( current ).overflowX;&lt;br /&gt;
					if ( overflowX !== &#039;visible&#039; ) { hasOverflow = true; break; }&lt;br /&gt;
				}&lt;br /&gt;
				current = current.parentElement;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		debugLog( &#039;가로 넘침 판정:&#039;, variationContainer, &#039;컨테이너:&#039;, variationContainer.scrollWidth + &#039;/&#039; + variationContainer.clientWidth, &#039;→ 넘침:&#039;, hasOverflow );&lt;br /&gt;
&lt;br /&gt;
		if ( hasOverflow ) { viewerContainer.setAttribute( &#039;data-lyrics-viewer-overflow&#039;, &#039;true&#039; ); }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 가사 표시/숨김 적용 함수 생성&lt;br /&gt;
	function createVariationApplier( viewerContainer, variationElements ) {&lt;br /&gt;
		// 창 크기 변경 등으로 컨테이너 폭이 변하면 스크롤 여부를 다시 판단합니다.&lt;br /&gt;
		try {&lt;br /&gt;
			var variationContainer = viewerContainer.querySelector( &#039;.mw--lyrics-variation-container&#039; );&lt;br /&gt;
			if ( variationContainer &amp;amp;&amp;amp; typeof window.ResizeObserver === &#039;function&#039; ) {&lt;br /&gt;
				new window.ResizeObserver( function () { updateOverflowState( viewerContainer ); } ).observe( variationContainer );&lt;br /&gt;
			}&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
		return function ( singleSelectedValue, multiSelectedValues, isMultimodeActive ) {&lt;br /&gt;
			// 다중 선택 여부에 따라 표시할 집합을 결정합니다.&lt;br /&gt;
			var visibleSet;&lt;br /&gt;
			if ( isMultimodeActive ) {&lt;br /&gt;
				visibleSet = new Set( multiSelectedValues.length ? multiSelectedValues : [ singleSelectedValue ] );&lt;br /&gt;
			} else {&lt;br /&gt;
				visibleSet = new Set( [ singleSelectedValue ] );&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// 각 변형 요소의 표시 여부 갱신&lt;br /&gt;
			variationElements.forEach( function ( element ) {&lt;br /&gt;
				var variationValue = element.getAttribute( &#039;data-lyric-variation&#039; ) || &#039;&#039;;&lt;br /&gt;
				element.hidden = !visibleSet.has( variationValue );&lt;br /&gt;
			} );&lt;br /&gt;
&lt;br /&gt;
			// 라벨 표시를 다중 선택 모드에 맞춰 갱신합니다.&lt;br /&gt;
			viewerContainer.querySelectorAll( &#039;.ts-lyric-label&#039; ).forEach( function ( lyricLabel ) { lyricLabel.hidden = !isMultimodeActive; } );&lt;br /&gt;
&lt;br /&gt;
			// 다중 선택 모드에 따라 컨테이너 속성을 갱신합니다.&lt;br /&gt;
			if ( isMultimodeActive ) {&lt;br /&gt;
				viewerContainer.setAttribute( &#039;data-lyrics-viewer-multimode&#039;, &#039;true&#039; );&lt;br /&gt;
			} else {&lt;br /&gt;
				viewerContainer.removeAttribute( &#039;data-lyrics-viewer-multimode&#039; );&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// 표시 변화로 가로 스크롤 생김 여부가 달라질 수 있으니 함께 갱신합니다.&lt;br /&gt;
			updateOverflowState( viewerContainer );&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 글자 크기/글꼴/정렬/줄 간격/글자 간격 적용 함수 생성&lt;br /&gt;
	function createStyleApplier( viewerContainer ) {&lt;br /&gt;
		return function ( fontSizeValue, fontFamilyValue, alignmentValue, lineHeightValue, letterSpacingValue ) {&lt;br /&gt;
			// 각 가사 본문에 스타일 적용&lt;br /&gt;
			viewerContainer.querySelectorAll( &#039;.ts-lyric-content&#039; ).forEach( function ( contentElement ) {&lt;br /&gt;
				contentElement.style.fontSize = FONT_SIZE_MAP[ fontSizeValue ] || &#039;&#039;;&lt;br /&gt;
				contentElement.style.fontFamily = FONT_FAMILY_MAP[ fontFamilyValue ] || &#039;&#039;;&lt;br /&gt;
				contentElement.style.lineHeight = LINE_HEIGHT_MAP[ lineHeightValue ] || &#039;&#039;;&lt;br /&gt;
				contentElement.style.letterSpacing = LETTER_SPACING_MAP[ letterSpacingValue ] || &#039;&#039;;&lt;br /&gt;
				// 정렬 값에 따라 텍스트 정렬을 갱신합니다.&lt;br /&gt;
				if ( alignmentValue === &#039;left&#039; || !alignmentValue ) {&lt;br /&gt;
					contentElement.style.textAlign = &#039;&#039;;&lt;br /&gt;
				} else {&lt;br /&gt;
					contentElement.style.textAlign = alignmentValue;&lt;br /&gt;
				}&lt;br /&gt;
			} );&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 대상 컨테이너 수집&lt;br /&gt;
	function collectViewerTargets( seenContainers ) {&lt;br /&gt;
		var allMainMarkers = document.querySelectorAll( &#039;[data-lyric-mainpage]&#039; );&lt;br /&gt;
		var targets = [];&lt;br /&gt;
&lt;br /&gt;
		allMainMarkers.forEach( function ( mainMarkerElement ) {&lt;br /&gt;
			var viewerContainer = mainMarkerElement.closest( &#039;.mw--lyrics-container&#039; );&lt;br /&gt;
&lt;br /&gt;
			// 유효하지 않은 컨테이너는 건너뜁니다.&lt;br /&gt;
			if ( !viewerContainer ) { return; }&lt;br /&gt;
&lt;br /&gt;
			// 이미 처리한 컨테이너는 건너뜁니다.&lt;br /&gt;
			if ( seenContainers.has( viewerContainer ) ) { return; }&lt;br /&gt;
			seenContainers.add( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
			// 마운트에 성공한 컨테이너는 건너뜁니다.&lt;br /&gt;
			if ( viewerContainer.dataset.lyricsViewerMounted === &#039;1&#039; ) { return; }&lt;br /&gt;
&lt;br /&gt;
			targets.push( { mainMarkerElement: mainMarkerElement, viewerContainer: viewerContainer } );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return targets;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Codex 아이콘 로드 및 디버그 상태 노출&lt;br /&gt;
	function setupCodexIcons( Codex, iconAddRef, iconEditRef, iconSettingsRef ) {&lt;br /&gt;
		var CdxIcon = Codex.CdxIcon;&lt;br /&gt;
&lt;br /&gt;
		var initialApiUrl = buildCodexIconsApiUrl();&lt;br /&gt;
		debugLog( &#039;list=codexicons API에서 아이콘 동적 로드:&#039;, initialApiUrl );&lt;br /&gt;
		fetchIconsFromApi( function ( apiIcons ) {&lt;br /&gt;
			// 받은 아이콘을 ref에 반영합니다.&lt;br /&gt;
			if ( apiIcons.cdxIconAdd ) { iconAddRef.value = apiIcons.cdxIconAdd; debugLog( &#039;API iconAdd 적용 완료&#039; ); }&lt;br /&gt;
			if ( apiIcons.cdxIconEdit ) { iconEditRef.value = apiIcons.cdxIconEdit; debugLog( &#039;API iconEdit 적용 완료&#039; ); }&lt;br /&gt;
			if ( apiIcons.cdxIconSettings ) { iconSettingsRef.value = apiIcons.cdxIconSettings; debugLog( &#039;API iconSettings 적용 완료&#039; ); }&lt;br /&gt;
			try {&lt;br /&gt;
				window.__lyricsViewerDebug.lastApiIcons = apiIcons;&lt;br /&gt;
				window.__lyricsViewerDebug.lastIconAdd = iconAddRef.value;&lt;br /&gt;
				window.__lyricsViewerDebug.lastIconEdit = iconEditRef.value;&lt;br /&gt;
				window.__lyricsViewerDebug.lastIconSettings = iconSettingsRef.value;&lt;br /&gt;
			} catch ( error2 ) {}&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		try {&lt;br /&gt;
			window.__lyricsViewerDebug.lastIconAdd = iconAddRef.value;&lt;br /&gt;
			window.__lyricsViewerDebug.lastIconEdit = iconEditRef.value;&lt;br /&gt;
			window.__lyricsViewerDebug.lastIconSettings = iconSettingsRef.value;&lt;br /&gt;
			window.__lyricsViewerDebug.getIconState = function () {&lt;br /&gt;
				return {&lt;br /&gt;
					hasIconAdd: !!iconAddRef.value,&lt;br /&gt;
					hasIconEdit: !!iconEditRef.value,&lt;br /&gt;
					hasIconSettings: !!iconSettingsRef.value,&lt;br /&gt;
					iconAddSource: iconAddRef.value ? &#039;api-list=codexicons&#039; : &#039;missing&#039;,&lt;br /&gt;
					iconEditSource: iconEditRef.value ? &#039;api-list=codexicons&#039; : &#039;missing&#039;,&lt;br /&gt;
					iconSettingsSource: iconSettingsRef.value ? &#039;api-list=codexicons&#039; : &#039;missing&#039;,&lt;br /&gt;
					hasCdxIcon: !!CdxIcon&lt;br /&gt;
				};&lt;br /&gt;
			};&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
		// CdxIcon이 없다면 경고를 남깁니다.&lt;br /&gt;
		if ( !CdxIcon ) { debugWarn( &#039;Codex CdxIcon 컴포넌트를 찾을 수 없습니다. Codex 버전이 오래되었거나 모듈이 잘못 로드되었을 수 있습니다. Codex.CdxIcon 존재 여부를 확인하세요.&#039; ); }&lt;br /&gt;
&lt;br /&gt;
		return CdxIcon;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 뷰어별 반응형 상태 생성&lt;br /&gt;
	function createViewerState( Vue ) {&lt;br /&gt;
		return Vue.reactive( {&lt;br /&gt;
			singleSelection: &#039;&#039;,&lt;br /&gt;
			multiSelection: [ &#039;&#039; ],&lt;br /&gt;
			isDialogOpen: false,&lt;br /&gt;
			isPopoverOpen: false,&lt;br /&gt;
			isEditModalOpen: false,&lt;br /&gt;
			editTargetTitle: &#039;&#039;,&lt;br /&gt;
			wikitext: &#039;&#039;,&lt;br /&gt;
			originalWikitext: &#039;&#039;,&lt;br /&gt;
			baseTimestamp: &#039;&#039;,&lt;br /&gt;
			startTimestamp: &#039;&#039;,&lt;br /&gt;
			editSummary: &#039;&#039;,&lt;br /&gt;
			isFetchingWikitext: false,&lt;br /&gt;
			isSavingWikitext: false,&lt;br /&gt;
			editErrorMessage: &#039;&#039;,&lt;br /&gt;
			isDiffShown: false,&lt;br /&gt;
			isDiffLoading: false,&lt;br /&gt;
			diffHtml: &#039;&#039;,&lt;br /&gt;
			diffErrorMessage: &#039;&#039;&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 초기 표시 상태 적용&lt;br /&gt;
	function applyInitialViewerDisplay( viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles ) {&lt;br /&gt;
		applyVariationVisibility( viewerState.singleSelection, viewerState.multiSelection, globalDisplayState.isMultimodeEnabled );&lt;br /&gt;
		applyDisplayStyles( globalDisplayState.fontSize, globalDisplayState.fontFamily, globalDisplayState.textAlignment, globalDisplayState.lineHeight, globalDisplayState.letterSpacing );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 상태 변경 감시&lt;br /&gt;
	function watchViewerDisplayChanges( Vue, viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles ) {&lt;br /&gt;
		// 다중 선택 모드 변경 감시&lt;br /&gt;
		Vue.watch( function () { return globalDisplayState.isMultimodeEnabled; }, function ( isActive ) {&lt;br /&gt;
			// 다중 선택 모드가 활성화되어 있나요?&lt;br /&gt;
			if ( isActive ) {&lt;br /&gt;
				// 활성화됨 — 다중 선택이 비어 있다면 단일 선택으로 채웁니다.&lt;br /&gt;
				if ( !viewerState.multiSelection.length ) { viewerState.multiSelection = [ viewerState.singleSelection || &#039;&#039; ]; }&lt;br /&gt;
			} else if ( viewerState.multiSelection.length ) {&lt;br /&gt;
				// 비활성화됨 — 첫 선택을 단일 선택으로 복원합니다.&lt;br /&gt;
				viewerState.singleSelection = viewerState.multiSelection[ 0 ];&lt;br /&gt;
			}&lt;br /&gt;
			applyVariationVisibility( viewerState.singleSelection, viewerState.multiSelection, isActive );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 글자/글꼴/정렬/줄 간격/글자 간격 변경 감시&lt;br /&gt;
		Vue.watch( function () { return [ globalDisplayState.fontSize, globalDisplayState.fontFamily, globalDisplayState.textAlignment, globalDisplayState.lineHeight, globalDisplayState.letterSpacing ]; }, function () {&lt;br /&gt;
			applyDisplayStyles( globalDisplayState.fontSize, globalDisplayState.fontFamily, globalDisplayState.textAlignment, globalDisplayState.lineHeight, globalDisplayState.letterSpacing );&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 토글 값 바인딩 생성&lt;br /&gt;
	function buildToggleBinding( Vue, viewerState, globalDisplayState, applyVariationVisibility ) {&lt;br /&gt;
		return Vue.computed( {&lt;br /&gt;
			get: function () {&lt;br /&gt;
				return globalDisplayState.isMultimodeEnabled ? viewerState.multiSelection : viewerState.singleSelection;&lt;br /&gt;
			},&lt;br /&gt;
			set: function ( newValue ) {&lt;br /&gt;
				// 배열 값인가요? (다중 선택)&lt;br /&gt;
				if ( Array.isArray( newValue ) ) {&lt;br /&gt;
					// 배열 — 빈 배열이라면 무시합니다.&lt;br /&gt;
					if ( !newValue.length ) { return; }&lt;br /&gt;
					// 배열 — 다중 선택 갱신&lt;br /&gt;
					viewerState.multiSelection = newValue.slice();&lt;br /&gt;
				} else {&lt;br /&gt;
					// 단일 값 — 단일 선택 갱신&lt;br /&gt;
					viewerState.singleSelection = newValue == null ? &#039;&#039; : String( newValue );&lt;br /&gt;
&lt;br /&gt;
					// 다중 선택이 단일 항목이라면 동기화합니다.&lt;br /&gt;
					if ( viewerState.multiSelection.length === 1 ) { viewerState.multiSelection = [ viewerState.singleSelection ]; }&lt;br /&gt;
				}&lt;br /&gt;
				applyVariationVisibility(&lt;br /&gt;
					viewerState.singleSelection,&lt;br /&gt;
					viewerState.multiSelection,&lt;br /&gt;
					globalDisplayState.isMultimodeEnabled&lt;br /&gt;
				);&lt;br /&gt;
			}&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Dialog 상태와 동작 생성&lt;br /&gt;
	function buildDialogBindings( Vue, viewerState, songPageTitle, existingVariationSet, openEditModal ) {&lt;br /&gt;
		var selectedType = Vue.ref( &#039;&#039; );&lt;br /&gt;
		var customVariationName = Vue.ref( &#039;&#039; );&lt;br /&gt;
&lt;br /&gt;
		// 라디오 그룹 이름 — 뷰어마다 독립된 그룹이 되도록 고유한 값을 만듭니다.&lt;br /&gt;
		var lyricTypeRadioGroupName = &#039;mw-lyrics-viewer-lyric-type-&#039; + ( ++radioGroupCounter );&lt;br /&gt;
&lt;br /&gt;
		var dialogOpen = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.isDialogOpen; },&lt;br /&gt;
			set: function ( value ) { viewerState.isDialogOpen = value; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 대상 문서 제목 계산 — 유효하지 않은 제목은 null이 되어 프리뷰가 비활성화됩니다.&lt;br /&gt;
		var previewDocumentTitle = Vue.computed( function () {&lt;br /&gt;
			// 표준 유형이 선택되었다면 해당 유형 이름으로 제목을 만듭니다.&lt;br /&gt;
			if ( selectedType.value &amp;amp;&amp;amp; selectedType.value !== &#039;__custom&#039; ) { return buildLyricsDocumentTitle( songPageTitle, selectedType.value ); }&lt;br /&gt;
&lt;br /&gt;
			// 표준 유형이 아니라면 입력된 사용자 지정 이름으로 제목을 만듭니다.&lt;br /&gt;
			var trimmedCustomName = customVariationName.value.trim();&lt;br /&gt;
			return trimmedCustomName ? buildLyricsDocumentTitle( songPageTitle, trimmedCustomName ) : &#039;&#039;;&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 이미 존재하는 문서인지 계산&lt;br /&gt;
		var alreadyExists = Vue.computed( function () {&lt;br /&gt;
			// 제목이 비어 있다면 존재하지 않습니다.&lt;br /&gt;
			if ( !previewDocumentTitle.value ) { return false; }&lt;br /&gt;
			var variationName = ( previewDocumentTitle.value.split( &#039;/&#039; ).pop() || &#039;&#039; );&lt;br /&gt;
			return existingVariationSet.has( variationName );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 주 동작 버튼 정의&lt;br /&gt;
		var primaryAction = Vue.computed( function () {&lt;br /&gt;
			return {&lt;br /&gt;
				label: alreadyExists.value ? MESSAGES.editLabel : MESSAGES.createLabel,&lt;br /&gt;
				actionType: &#039;progressive&#039;,&lt;br /&gt;
				disabled: !previewDocumentTitle.value&lt;br /&gt;
			};&lt;br /&gt;
		} );&lt;br /&gt;
		var defaultAction = { label: MESSAGES.closeLabel };&lt;br /&gt;
&lt;br /&gt;
		// 주 동작 실행 — 편집 모달 열기&lt;br /&gt;
		function handlePrimaryAction() {&lt;br /&gt;
			// 대상 문서 제목이 없다면 중단합니다.&lt;br /&gt;
			if ( !previewDocumentTitle.value ) { return; }&lt;br /&gt;
			viewerState.isDialogOpen = false;&lt;br /&gt;
			openEditModal( previewDocumentTitle.value );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Dialog 닫힘 감시&lt;br /&gt;
		Vue.watch( function () { return viewerState.isDialogOpen; }, function ( isOpen ) {&lt;br /&gt;
			// 닫혔다면 선택을 초기화합니다.&lt;br /&gt;
			if ( !isOpen ) { selectedType.value = &#039;&#039;; customVariationName.value = &#039;&#039;; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			selectedType: selectedType,&lt;br /&gt;
			customVariationName: customVariationName,&lt;br /&gt;
			dialogOpen: dialogOpen,&lt;br /&gt;
			previewDocumentTitle: previewDocumentTitle,&lt;br /&gt;
			lyricTypeRadioGroupName: lyricTypeRadioGroupName,&lt;br /&gt;
			primaryAction: primaryAction,&lt;br /&gt;
			defaultAction: defaultAction,&lt;br /&gt;
			handlePrimaryAction: handlePrimaryAction&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 편집 모달 상태와 동작 생성&lt;br /&gt;
	function buildEditModalBindings( Vue, viewerState, lyricsRepository, currentPageTitle ) {&lt;br /&gt;
		var editModalOpen = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.isEditModalOpen; },&lt;br /&gt;
			set: function ( value ) {&lt;br /&gt;
				// 닫기 시도인데 수정된 내용이 있다면 버릴지 브라우저 확인 대화상자로 묻습니다.&lt;br /&gt;
				if ( !value &amp;amp;&amp;amp; viewerState.wikitext !== viewerState.originalWikitext &amp;amp;&amp;amp; !window.confirm( MESSAGES.unsavedChangesMessage ) ) { return; }&lt;br /&gt;
				viewerState.isEditModalOpen = value;&lt;br /&gt;
			}&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 편집 모달에 저장하지 않은 변경 사항이 있을 때 탭 이동/새로고침을 경고합니다.&lt;br /&gt;
		function handleBeforeUnload( beforeUnloadEvent ) {&lt;br /&gt;
			if ( !viewerState.isEditModalOpen || viewerState.wikitext === viewerState.originalWikitext ) { return; }&lt;br /&gt;
			beforeUnloadEvent.preventDefault();&lt;br /&gt;
			// Chrome은 returnValue가 설정되어야 대화상자를 표시합니다.&lt;br /&gt;
			beforeUnloadEvent.returnValue = &#039;&#039;;&lt;br /&gt;
		}&lt;br /&gt;
		window.addEventListener( &#039;beforeunload&#039;, handleBeforeUnload );&lt;br /&gt;
&lt;br /&gt;
		// 모달 내부 입력 상태를 뷰어 상태와 연결합니다.&lt;br /&gt;
		var editTargetTitle = Vue.computed( function () { return viewerState.editTargetTitle; } );&lt;br /&gt;
		var wikitext = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.wikitext; },&lt;br /&gt;
			set: function ( value ) { viewerState.wikitext = value; }&lt;br /&gt;
		} );&lt;br /&gt;
		var editSummary = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.editSummary; },&lt;br /&gt;
			set: function ( value ) { viewerState.editSummary = value; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 저장 중에는 주 동작을 막습니다.&lt;br /&gt;
		var saveAction = Vue.computed( function () {&lt;br /&gt;
			return {&lt;br /&gt;
				label: MESSAGES.saveLabel,&lt;br /&gt;
				actionType: &#039;progressive&#039;,&lt;br /&gt;
				disabled: viewerState.isFetchingWikitext || viewerState.isSavingWikitext&lt;br /&gt;
			};&lt;br /&gt;
		} );&lt;br /&gt;
		var cancelAction = { label: MESSAGES.closeLabel };&lt;br /&gt;
&lt;br /&gt;
		// 모달 열기 — 대상 문서의 위키텍스트를 불러옵니다.&lt;br /&gt;
		var fetchSequence = 0;&lt;br /&gt;
		function openEditModal( documentTitle ) {&lt;br /&gt;
			var requestSequence = ++fetchSequence;&lt;br /&gt;
			viewerState.editTargetTitle = documentTitle;&lt;br /&gt;
			viewerState.wikitext = &#039;&#039;;&lt;br /&gt;
			viewerState.originalWikitext = &#039;&#039;;&lt;br /&gt;
			viewerState.baseTimestamp = &#039;&#039;;&lt;br /&gt;
			viewerState.startTimestamp = &#039;&#039;;&lt;br /&gt;
			viewerState.editSummary = &#039;&#039;;&lt;br /&gt;
			viewerState.editErrorMessage = &#039;&#039;;&lt;br /&gt;
			viewerState.diffHtml = &#039;&#039;;&lt;br /&gt;
			viewerState.diffErrorMessage = &#039;&#039;;&lt;br /&gt;
			viewerState.isDiffShown = false;&lt;br /&gt;
			viewerState.isEditModalOpen = true;&lt;br /&gt;
			viewerState.isFetchingWikitext = true;&lt;br /&gt;
			lyricsRepository.fetchWikitext( documentTitle ).then( function ( fetchedRevision ) {&lt;br /&gt;
				// 다른 문서 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== fetchSequence ) { return; }&lt;br /&gt;
				viewerState.wikitext = fetchedRevision.text;&lt;br /&gt;
				viewerState.originalWikitext = fetchedRevision.text;&lt;br /&gt;
				viewerState.baseTimestamp = fetchedRevision.baseTimestamp;&lt;br /&gt;
				viewerState.startTimestamp = fetchedRevision.startTimestamp;&lt;br /&gt;
			}, function ( fetchError ) {&lt;br /&gt;
				// 다른 문서 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== fetchSequence ) { return; }&lt;br /&gt;
				viewerState.editErrorMessage = formatApiError( fetchError );&lt;br /&gt;
			} ).then( function () {&lt;br /&gt;
				// 다른 문서 요청이 시작되었다면 이전 요청의 로딩 상태를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== fetchSequence ) { return; }&lt;br /&gt;
				viewerState.isFetchingWikitext = false;&lt;br /&gt;
			} );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 편집 저장 — 곡 문서 캐시를 정리한 뒤 페이지를 새로고칩니다.&lt;br /&gt;
		function handleEditSave() {&lt;br /&gt;
			// 저장이 이미 진행 중이라면 중단합니다.&lt;br /&gt;
			if ( viewerState.isSavingWikitext ) { return; }&lt;br /&gt;
			viewerState.isSavingWikitext = true;&lt;br /&gt;
			viewerState.editErrorMessage = &#039;&#039;;&lt;br /&gt;
			lyricsRepository.saveWikitext( viewerState.editTargetTitle, viewerState.wikitext, viewerState.editSummary, viewerState.baseTimestamp, viewerState.startTimestamp ).then( function ( saveSucceeded ) {&lt;br /&gt;
				// API 오류가 아니어도 저장이 성공하지 않았다면 새로고침하지 않고 안내합니다.&lt;br /&gt;
				if ( !saveSucceeded ) {&lt;br /&gt;
					viewerState.isSavingWikitext = false;&lt;br /&gt;
					viewerState.editErrorMessage = MESSAGES.saveFailedMessage;&lt;br /&gt;
					return;&lt;br /&gt;
				}&lt;br /&gt;
				// 트랜스클루전된 가사를 갱신하기 위해 현재 문서에 purge를 수행합니다.&lt;br /&gt;
				// 저장에 성공했으므로 현재 내용을 기준으로 삼아 나감 경고가 발동하지 않게 합니다.&lt;br /&gt;
				viewerState.originalWikitext = viewerState.wikitext;&lt;br /&gt;
				lyricsRepository.purgePage( currentPageTitle ).then( function ( purgeResponse ) {&lt;br /&gt;
					debugLog( &#039;현재 문서 purge 완료:&#039;, currentPageTitle, purgeResponse );&lt;br /&gt;
					window.location.reload();&lt;br /&gt;
				}, function ( purgeError ) {&lt;br /&gt;
					// 캐시 정리에 실패해도 저장은 완료된 상태이므로 새로고침합니다.&lt;br /&gt;
					debugWarn( &#039;곡 문서 캐시 정리에 실패했습니다: &#039; + formatApiError( purgeError ) );&lt;br /&gt;
					window.location.reload();&lt;br /&gt;
				} );&lt;br /&gt;
			}, function ( saveError ) {&lt;br /&gt;
				viewerState.isSavingWikitext = false;&lt;br /&gt;
				viewerState.editErrorMessage = formatApiError( saveError );&lt;br /&gt;
			} );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 대상 문서 링크 URL 생성&lt;br /&gt;
		var editTargetUrl = Vue.computed( function () {&lt;br /&gt;
			try { return mw.util.getUrl( viewerState.editTargetTitle ); } catch ( error ) { return &#039;&#039;; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 차이 보기 — 불러온 내용과 현재 내용의 차이를 요청합니다.&lt;br /&gt;
		var diffSequence = 0;&lt;br /&gt;
		function handleDiffRefresh() {&lt;br /&gt;
			var requestSequence = ++diffSequence;&lt;br /&gt;
			viewerState.isDiffShown = true;&lt;br /&gt;
			viewerState.diffHtml = &#039;&#039;;&lt;br /&gt;
			viewerState.diffErrorMessage = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
			// 변경 전후가 모두 비어 있다면 요청 없이 차이 없음으로 처리합니다.&lt;br /&gt;
			if ( !viewerState.originalWikitext &amp;amp;&amp;amp; !viewerState.wikitext ) { return; }&lt;br /&gt;
			viewerState.isDiffLoading = true;&lt;br /&gt;
&lt;br /&gt;
			// 차이 표 스타일은 mediawiki.diff.styles 모듈이 제공합니다.&lt;br /&gt;
			try {&lt;br /&gt;
				mw.loader.using( &#039;mediawiki.diff.styles&#039; ).catch( function () {&lt;br /&gt;
					debugWarn( &#039;mediawiki.diff.styles 모듈을 불러오지 못했습니다.&#039; );&lt;br /&gt;
				} );&lt;br /&gt;
			} catch ( error ) {}&lt;br /&gt;
&lt;br /&gt;
			lyricsRepository.fetchWikitextDiff( viewerState.originalWikitext, viewerState.wikitext ).then( function ( diffHtml ) {&lt;br /&gt;
				// 다른 차이 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== diffSequence ) { return; }&lt;br /&gt;
				viewerState.diffHtml = diffHtml;&lt;br /&gt;
			}, function ( diffError ) {&lt;br /&gt;
				// 다른 차이 요청이 시작되었다면 이전 요청의 결과를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== diffSequence ) { return; }&lt;br /&gt;
				viewerState.diffErrorMessage = formatApiError( diffError );&lt;br /&gt;
			} ).then( function () {&lt;br /&gt;
				// 다른 차이 요청이 시작되었다면 이전 요청의 로딩 상태를 반영하지 않습니다.&lt;br /&gt;
				if ( requestSequence !== diffSequence ) { return; }&lt;br /&gt;
				viewerState.isDiffLoading = false;&lt;br /&gt;
			} );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			editModalOpen: editModalOpen,&lt;br /&gt;
			editTargetTitle: editTargetTitle,&lt;br /&gt;
			editTargetUrl: editTargetUrl,&lt;br /&gt;
			wikitext: wikitext,&lt;br /&gt;
			editSummary: editSummary,&lt;br /&gt;
			isFetchingWikitext: Vue.computed( function () { return viewerState.isFetchingWikitext; } ),&lt;br /&gt;
			isSavingWikitext: Vue.computed( function () { return viewerState.isSavingWikitext; } ),&lt;br /&gt;
			editErrorMessage: Vue.computed( function () { return viewerState.editErrorMessage; } ),&lt;br /&gt;
			saveAction: saveAction,&lt;br /&gt;
			cancelAction: cancelAction,&lt;br /&gt;
			openEditModal: openEditModal,&lt;br /&gt;
			handleEditSave: handleEditSave,&lt;br /&gt;
			isDiffShown: Vue.computed( function () { return viewerState.isDiffShown; } ),&lt;br /&gt;
			isDiffLoading: Vue.computed( function () { return viewerState.isDiffLoading; } ),&lt;br /&gt;
			diffHtml: Vue.computed( function () { return viewerState.diffHtml; } ),&lt;br /&gt;
			diffErrorMessage: Vue.computed( function () { return viewerState.diffErrorMessage; } ),&lt;br /&gt;
			handleDiffRefresh: handleDiffRefresh&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Popover 앵커 상태 생성&lt;br /&gt;
	function buildPopoverAnchor( Vue ) {&lt;br /&gt;
		var settingsButtonAnchorRef = Vue.ref( null );&lt;br /&gt;
		var settingsButtonAnchorEl = Vue.computed( function () {&lt;br /&gt;
			var raw = settingsButtonAnchorRef.value;&lt;br /&gt;
			// 컴포넌트 래퍼라면 내부 엘리먼트를 반환합니다.&lt;br /&gt;
			if ( raw &amp;amp;&amp;amp; raw.$el ) { return raw.$el; }&lt;br /&gt;
			// 일반 엘리먼트 — 그대로 반환합니다.&lt;br /&gt;
			return raw;&lt;br /&gt;
		} );&lt;br /&gt;
		return { settingsButtonAnchorRef: settingsButtonAnchorRef, settingsButtonAnchorEl: settingsButtonAnchorEl };&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 설정 버튼 목록 생성&lt;br /&gt;
	function buildDisplaySettingButtons() {&lt;br /&gt;
		return {&lt;br /&gt;
			fontSizeButtons: [&lt;br /&gt;
				{ value: &#039;small&#039;, label: MESSAGES.fontSizeSmall },&lt;br /&gt;
				{ value: &#039;medium&#039;, label: MESSAGES.fontSizeMedium },&lt;br /&gt;
				{ value: &#039;large&#039;, label: MESSAGES.fontSizeLarge }&lt;br /&gt;
			],&lt;br /&gt;
			fontFamilyButtons: [&lt;br /&gt;
				{ value: &#039;default&#039;, label: MESSAGES.fontFamilyDefault },&lt;br /&gt;
				{ value: &#039;serif&#039;, label: MESSAGES.fontFamilySerif },&lt;br /&gt;
				{ value: &#039;sans-serif&#039;, label: MESSAGES.fontFamilySans },&lt;br /&gt;
				{ value: &#039;monospace&#039;, label: MESSAGES.fontFamilyMono }&lt;br /&gt;
			],&lt;br /&gt;
			alignmentButtons: [&lt;br /&gt;
				{ value: &#039;left&#039;, label: MESSAGES.alignLeft },&lt;br /&gt;
				{ value: &#039;center&#039;, label: MESSAGES.alignCenter },&lt;br /&gt;
				{ value: &#039;right&#039;, label: MESSAGES.alignRight }&lt;br /&gt;
			],&lt;br /&gt;
			lineHeightButtons: [&lt;br /&gt;
				{ value: &#039;narrow&#039;, label: MESSAGES.lineHeightNarrow },&lt;br /&gt;
				{ value: &#039;normal&#039;, label: MESSAGES.lineHeightNormal },&lt;br /&gt;
				{ value: &#039;wide&#039;, label: MESSAGES.lineHeightWide }&lt;br /&gt;
			],&lt;br /&gt;
			letterSpacingButtons: [&lt;br /&gt;
				{ value: &#039;narrow&#039;, label: MESSAGES.letterSpacingNarrow },&lt;br /&gt;
				{ value: &#039;normal&#039;, label: MESSAGES.letterSpacingNormal },&lt;br /&gt;
				{ value: &#039;wide&#039;, label: MESSAGES.letterSpacingWide }&lt;br /&gt;
			]&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 표시 설정 변경 핸들러 생성&lt;br /&gt;
	function buildDisplaySettingHandlers( globalDisplayState ) {&lt;br /&gt;
		return {&lt;br /&gt;
			handleMultimodeChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.isMultimodeEnabled = !!value;&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.multimode, String( globalDisplayState.isMultimodeEnabled ) );&lt;br /&gt;
			},&lt;br /&gt;
			handleFontSizeChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.fontSize = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.fontSize, globalDisplayState.fontSize );&lt;br /&gt;
			},&lt;br /&gt;
			handleFontFamilyChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.fontFamily = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.fontFamily, globalDisplayState.fontFamily );&lt;br /&gt;
			},&lt;br /&gt;
			handleAlignmentChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.textAlignment = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.align, globalDisplayState.textAlignment );&lt;br /&gt;
			},&lt;br /&gt;
			handleLineHeightChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.lineHeight = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.lineHeight, globalDisplayState.lineHeight );&lt;br /&gt;
			},&lt;br /&gt;
			handleLetterSpacingChange: function ( value ) {&lt;br /&gt;
				globalDisplayState.letterSpacing = String( value );&lt;br /&gt;
				writeToStorage( STORAGE_KEYS.letterSpacing, globalDisplayState.letterSpacing );&lt;br /&gt;
			}&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Settings Popover 상태와 버튼 생성&lt;br /&gt;
	function buildSettingsBindings( Vue, viewerState, globalDisplayState, Codex ) {&lt;br /&gt;
		var anchor = buildPopoverAnchor( Vue );&lt;br /&gt;
		var buttons = buildDisplaySettingButtons();&lt;br /&gt;
		var handlers = buildDisplaySettingHandlers( globalDisplayState );&lt;br /&gt;
&lt;br /&gt;
		var popoverOpen = Vue.computed( {&lt;br /&gt;
			get: function () { return viewerState.isPopoverOpen; },&lt;br /&gt;
			set: function ( value ) { viewerState.isPopoverOpen = value; }&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return {&lt;br /&gt;
			settingsButtonAnchorRef: anchor.settingsButtonAnchorRef,&lt;br /&gt;
			settingsButtonAnchorEl: anchor.settingsButtonAnchorEl,&lt;br /&gt;
			popoverOpen: popoverOpen,&lt;br /&gt;
			fontSizeButtons: buttons.fontSizeButtons,&lt;br /&gt;
			fontFamilyButtons: buttons.fontFamilyButtons,&lt;br /&gt;
			alignmentButtons: buttons.alignmentButtons,&lt;br /&gt;
			lineHeightButtons: buttons.lineHeightButtons,&lt;br /&gt;
			letterSpacingButtons: buttons.letterSpacingButtons,&lt;br /&gt;
			handleMultimodeChange: handlers.handleMultimodeChange,&lt;br /&gt;
			handleFontSizeChange: handlers.handleFontSizeChange,&lt;br /&gt;
			handleFontFamilyChange: handlers.handleFontFamilyChange,&lt;br /&gt;
			handleAlignmentChange: handlers.handleAlignmentChange,&lt;br /&gt;
			handleLineHeightChange: handlers.handleLineHeightChange,&lt;br /&gt;
			handleLetterSpacingChange: handlers.handleLetterSpacingChange,&lt;br /&gt;
			isToggleSwitchAvailable: !!Codex.CdxToggleSwitch&lt;br /&gt;
		};&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// Codex 컴포넌트 등록&lt;br /&gt;
	function registerViewerComponents( viewerApp, Codex ) {&lt;br /&gt;
		viewerApp.component( &#039;cdx-button&#039;, Codex.CdxButton );&lt;br /&gt;
		viewerApp.component( &#039;cdx-toggle-button-group&#039;, Codex.CdxToggleButtonGroup );&lt;br /&gt;
		viewerApp.component( &#039;cdx-dialog&#039;, Codex.CdxDialog );&lt;br /&gt;
		viewerApp.component( &#039;cdx-popover&#039;, Codex.CdxPopover );&lt;br /&gt;
		viewerApp.component( &#039;cdx-icon&#039;, Codex.CdxIcon );&lt;br /&gt;
		viewerApp.component( &#039;cdx-text-input&#039;, Codex.CdxTextInput );&lt;br /&gt;
		viewerApp.component( &#039;cdx-text-area&#039;, Codex.CdxTextArea );&lt;br /&gt;
&lt;br /&gt;
		// Tooltip directive가 제공되면 등록합니다.&lt;br /&gt;
		if ( Codex.CdxTooltip ) { viewerApp.directive( &#039;tooltip&#039;, Codex.CdxTooltip ); }&lt;br /&gt;
&lt;br /&gt;
		// 선택적 Codex 컴포넌트가 제공되면 등록합니다.&lt;br /&gt;
		if ( Codex.CdxToggleSwitch ) { viewerApp.component( &#039;cdx-toggle-switch&#039;, Codex.CdxToggleSwitch ); }&lt;br /&gt;
		if ( Codex.CdxMessage ) { viewerApp.component( &#039;cdx-message&#039;, Codex.CdxMessage ); }&lt;br /&gt;
		if ( Codex.CdxField ) { viewerApp.component( &#039;cdx-field&#039;, Codex.CdxField ); }&lt;br /&gt;
		if ( Codex.CdxLabel ) { viewerApp.component( &#039;cdx-label&#039;, Codex.CdxLabel ); }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 단일 Viewer Vue 앱 생성 및 마운트&lt;br /&gt;
	function createViewerApp( viewerBar, songPageTitle, variationData, viewerState, globalDisplayState, editModalBindings, iconAddRef, iconEditRef, iconSettingsRef, applyVariationVisibility, Vue, Codex ) {&lt;br /&gt;
		// 토글 바인딩 생성&lt;br /&gt;
		var toggleValue = buildToggleBinding( Vue, viewerState, globalDisplayState, applyVariationVisibility );&lt;br /&gt;
&lt;br /&gt;
		// Dialog 상태 생성&lt;br /&gt;
		var dialogBindings = buildDialogBindings( Vue, viewerState, songPageTitle, variationData.existingVariationSet, editModalBindings.openEditModal );&lt;br /&gt;
&lt;br /&gt;
		// Settings 상태 생성&lt;br /&gt;
		var settingsBindings = buildSettingsBindings( Vue, viewerState, globalDisplayState, Codex );&lt;br /&gt;
&lt;br /&gt;
		// 툴팁 디렉티브 사용 가능 여부&lt;br /&gt;
		var tooltipDirectiveAvailable = !!Codex.CdxTooltip;&lt;br /&gt;
&lt;br /&gt;
		// Vue 앱 생성&lt;br /&gt;
		var viewerApp = Vue.createMwApp( {&lt;br /&gt;
			setup: function () {&lt;br /&gt;
				return {&lt;br /&gt;
					messages: MESSAGES,&lt;br /&gt;
					shouldShowVariationToggle: variationData.shouldShowVariationToggle,&lt;br /&gt;
					variationToggleButtons: variationData.variationToggleButtons,&lt;br /&gt;
					toggleValue: toggleValue,&lt;br /&gt;
					displayState: globalDisplayState,&lt;br /&gt;
					selectedType: dialogBindings.selectedType,&lt;br /&gt;
					customVariationName: dialogBindings.customVariationName,&lt;br /&gt;
					lyricTypeRadioGroupName: dialogBindings.lyricTypeRadioGroupName,&lt;br /&gt;
					dialogOpen: dialogBindings.dialogOpen,&lt;br /&gt;
					previewDocumentTitle: dialogBindings.previewDocumentTitle,&lt;br /&gt;
					primaryAction: dialogBindings.primaryAction,&lt;br /&gt;
					defaultAction: dialogBindings.defaultAction,&lt;br /&gt;
					handlePrimaryAction: dialogBindings.handlePrimaryAction,&lt;br /&gt;
					standardTypes: STANDARD_LYRICS_TYPES,&lt;br /&gt;
					songPageTitle: songPageTitle,&lt;br /&gt;
					iconAdd: iconAddRef,&lt;br /&gt;
					iconEdit: iconEditRef,&lt;br /&gt;
					iconSettings: iconSettingsRef,&lt;br /&gt;
					settingsButtonAnchorRef: settingsBindings.settingsButtonAnchorRef,&lt;br /&gt;
					settingsButtonAnchorEl: settingsBindings.settingsButtonAnchorEl,&lt;br /&gt;
					popoverOpen: settingsBindings.popoverOpen,&lt;br /&gt;
					fontSizeButtons: settingsBindings.fontSizeButtons,&lt;br /&gt;
					fontFamilyButtons: settingsBindings.fontFamilyButtons,&lt;br /&gt;
					alignmentButtons: settingsBindings.alignmentButtons,&lt;br /&gt;
					lineHeightButtons: settingsBindings.lineHeightButtons,&lt;br /&gt;
					letterSpacingButtons: settingsBindings.letterSpacingButtons,&lt;br /&gt;
					handleMultimodeChange: settingsBindings.handleMultimodeChange,&lt;br /&gt;
					handleFontSizeChange: settingsBindings.handleFontSizeChange,&lt;br /&gt;
					handleFontFamilyChange: settingsBindings.handleFontFamilyChange,&lt;br /&gt;
					handleAlignmentChange: settingsBindings.handleAlignmentChange,&lt;br /&gt;
					handleLineHeightChange: settingsBindings.handleLineHeightChange,&lt;br /&gt;
					handleLetterSpacingChange: settingsBindings.handleLetterSpacingChange,&lt;br /&gt;
					isToggleSwitchAvailable: settingsBindings.isToggleSwitchAvailable,&lt;br /&gt;
					tooltipDirectiveAvailable: tooltipDirectiveAvailable,&lt;br /&gt;
					editModalOpen: editModalBindings.editModalOpen,&lt;br /&gt;
					editTargetTitle: editModalBindings.editTargetTitle,&lt;br /&gt;
					wikitext: editModalBindings.wikitext,&lt;br /&gt;
					editSummary: editModalBindings.editSummary,&lt;br /&gt;
					isFetchingWikitext: editModalBindings.isFetchingWikitext,&lt;br /&gt;
					isSavingWikitext: editModalBindings.isSavingWikitext,&lt;br /&gt;
					editErrorMessage: editModalBindings.editErrorMessage,&lt;br /&gt;
					saveAction: editModalBindings.saveAction,&lt;br /&gt;
					cancelAction: editModalBindings.cancelAction,&lt;br /&gt;
					handleEditSave: editModalBindings.handleEditSave,&lt;br /&gt;
					editTargetUrl: editModalBindings.editTargetUrl,&lt;br /&gt;
					isDiffShown: editModalBindings.isDiffShown,&lt;br /&gt;
					isDiffLoading: editModalBindings.isDiffLoading,&lt;br /&gt;
					diffHtml: editModalBindings.diffHtml,&lt;br /&gt;
					diffErrorMessage: editModalBindings.diffErrorMessage,&lt;br /&gt;
					handleDiffRefresh: editModalBindings.handleDiffRefresh,&lt;br /&gt;
					isMessageAvailable: !!Codex.CdxMessage&lt;br /&gt;
				};&lt;br /&gt;
			},&lt;br /&gt;
			template: VIEWER_TEMPLATE&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// Codex 컴포넌트 등록&lt;br /&gt;
		registerViewerComponents( viewerApp, Codex );&lt;br /&gt;
&lt;br /&gt;
		// Viewer 마운트&lt;br /&gt;
		viewerApp.mount( viewerBar );&lt;br /&gt;
		return viewerApp;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 각 변형의 .ts-lyric-content 우상단에 편집/생성 버튼 주입&lt;br /&gt;
	function injectVariationEditButtons( songPageTitle, variationElements, Vue, Codex, iconAddRef, iconEditRef, openEditModal ) {&lt;br /&gt;
		var buttonSlots = [];&lt;br /&gt;
		variationElements.forEach( function ( variationElement ) {&lt;br /&gt;
			var variationValue = variationElement.getAttribute( &#039;data-lyric-variation&#039; ) || &#039;&#039;;&lt;br /&gt;
			var contentElement = variationElement.querySelector( &#039;.ts-lyric-content&#039; );&lt;br /&gt;
&lt;br /&gt;
			// 가사 본문이 없으면 건너뜁니다.&lt;br /&gt;
			if ( !contentElement ) { return; }&lt;br /&gt;
&lt;br /&gt;
			// 이전 초기화 시도에서 만든 마운트 지점이 있다면 재사용해 중복 생성을 막습니다.&lt;br /&gt;
			var mountPoint = contentElement.querySelector( &#039;:scope &amp;gt; .mw--lyrics-variation-edit&#039; );&lt;br /&gt;
			if ( !mountPoint ) {&lt;br /&gt;
				mountPoint = document.createElement( &#039;div&#039; );&lt;br /&gt;
				mountPoint.className = &#039;mw--lyrics-variation-edit&#039;;&lt;br /&gt;
				contentElement.appendChild( mountPoint );&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// 내용이 없는 변형이면 추가 버튼으로 표시합니다.&lt;br /&gt;
			buttonSlots.push( {&lt;br /&gt;
				target: mountPoint,&lt;br /&gt;
				variationValue: variationValue,&lt;br /&gt;
				isExistingDocument: !contentElement.hasAttribute( &#039;data-empty&#039; )&lt;br /&gt;
			} );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		// 주입할 버튼이 없다면 앱을 만들지 않습니다.&lt;br /&gt;
		if ( !buttonSlots.length ) { return; }&lt;br /&gt;
&lt;br /&gt;
		// 모든 버튼을 하나의 앱에서 렌더링해 각 마운트 지점으로 텔레포트합니다.&lt;br /&gt;
		var editApp = Vue.createMwApp( {&lt;br /&gt;
			setup: function () {&lt;br /&gt;
				return {&lt;br /&gt;
					iconAdd: iconAddRef,&lt;br /&gt;
					iconEdit: iconEditRef,&lt;br /&gt;
					messages: MESSAGES,&lt;br /&gt;
					tooltipAvailable: !!Codex.CdxTooltip,&lt;br /&gt;
					buttonSlots: buttonSlots,&lt;br /&gt;
					handleEdit: function ( buttonSlot ) {&lt;br /&gt;
						var documentTitle = buildLyricsDocumentTitle( songPageTitle, buttonSlot.variationValue );&lt;br /&gt;
&lt;br /&gt;
						// 제목을 만들 수 없는 변형이라면 모달을 열지 않습니다.&lt;br /&gt;
						if ( documentTitle == null ) { debugWarn( &#039;문서 제목을 만들 수 없어 편집을 시작하지 않습니다:&#039;, buttonSlot.variationValue ); return; }&lt;br /&gt;
						openEditModal( documentTitle );&lt;br /&gt;
					}&lt;br /&gt;
				};&lt;br /&gt;
			},&lt;br /&gt;
			template: &#039;&amp;lt;teleport v-for=&amp;quot;(buttonSlot, index) in buttonSlots&amp;quot; :key=&amp;quot;index&amp;quot; :to=&amp;quot;buttonSlot.target&amp;quot;&amp;gt;&amp;lt;cdx-button weight=&amp;quot;quiet&amp;quot; size=&amp;quot;small&amp;quot; :aria-label=&amp;quot;buttonSlot.isExistingDocument ? messages.editButtonAriaLabel : messages.addVariationAriaLabel&amp;quot; v-tooltip=&amp;quot;tooltipAvailable ? (buttonSlot.isExistingDocument ? messages.editButtonTooltip : messages.addVariationTooltip) : null&amp;quot; @click=&amp;quot;handleEdit(buttonSlot)&amp;quot;&amp;gt;&amp;lt;cdx-icon v-if=&amp;quot;buttonSlot.isExistingDocument ? iconEdit : iconAdd&amp;quot; :icon=&amp;quot;buttonSlot.isExistingDocument ? iconEdit : iconAdd&amp;quot; size=&amp;quot;small&amp;quot;&amp;gt;&amp;lt;/cdx-icon&amp;gt;&amp;lt;/cdx-button&amp;gt;&amp;lt;/teleport&amp;gt;&#039;&lt;br /&gt;
		} );&lt;br /&gt;
		editApp.component( &#039;cdx-button&#039;, Codex.CdxButton );&lt;br /&gt;
		editApp.component( &#039;cdx-icon&#039;, Codex.CdxIcon );&lt;br /&gt;
		if ( Codex.CdxTooltip ) { editApp.directive( &#039;tooltip&#039;, Codex.CdxTooltip ); }&lt;br /&gt;
&lt;br /&gt;
		// 앱 루트에는 텔레포트 앵커만 남으므로 화면에 표시되는 내용은 없습니다.&lt;br /&gt;
		var appRoot = document.createElement( &#039;div&#039; );&lt;br /&gt;
		document.body.appendChild( appRoot );&lt;br /&gt;
		editApp.mount( appRoot );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 단일 Viewer 인스턴스 마운트&lt;br /&gt;
	function mountSingleViewer( viewerContainer, mainMarkerElement, globalDisplayState, Vue, Codex, iconAddRef, iconEditRef, iconSettingsRef ) {&lt;br /&gt;
		var songPageTitle = ( mainMarkerElement.getAttribute( &#039;data-lyric-mainpage&#039; ) || &#039;&#039; ).trim();&lt;br /&gt;
&lt;br /&gt;
		// 변형 데이터 수집&lt;br /&gt;
		var variationData = collectVariationData( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
		// 변형 요소가 하나도 없다면 중단합니다.&lt;br /&gt;
		if ( !variationData.variationElements.length ) { return null; }&lt;br /&gt;
&lt;br /&gt;
		// 서버 렌더링된 뷰어 바 참조&lt;br /&gt;
		var viewerBar = findViewerBar( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
		// 뷰어 바가 없다면 뷰어를 초기화하지 않습니다.&lt;br /&gt;
		if ( !viewerBar ) { return null; }&lt;br /&gt;
&lt;br /&gt;
		// 기본 가사만 존재할 때 spacer를 앞으로 옮겨 추가/설정 버튼을 우측에 모읍니다.&lt;br /&gt;
		viewerBar.classList.toggle( &#039;mw--lyrics-viewer-bar--no-variation-toggle&#039;, !variationData.shouldShowVariationToggle );&lt;br /&gt;
&lt;br /&gt;
		var applyVariationVisibility = createVariationApplier(&lt;br /&gt;
			viewerContainer, variationData.variationElements&lt;br /&gt;
		);&lt;br /&gt;
		var applyDisplayStyles = createStyleApplier( viewerContainer );&lt;br /&gt;
&lt;br /&gt;
		// 뷰어 상태 생성&lt;br /&gt;
		var viewerState = createViewerState( Vue );&lt;br /&gt;
&lt;br /&gt;
		// 편집 모달 상태 생성 — 에러 메시지를 화면 언어로 받기 위해 errorlang을 지정합니다.&lt;br /&gt;
		var editModalBindings = buildEditModalBindings( Vue, viewerState, createLyricsRepository( new mw.Api( {&lt;br /&gt;
			parameters: { errorlang: mw.config.get( &#039;wgUserLanguage&#039; ) }&lt;br /&gt;
		} ) ), getCurrentPageTitle() );&lt;br /&gt;
&lt;br /&gt;
		// 변형 편집/생성 버튼 주입&lt;br /&gt;
		injectVariationEditButtons( songPageTitle, variationData.variationElements, Vue, Codex, iconAddRef, iconEditRef, editModalBindings.openEditModal );&lt;br /&gt;
&lt;br /&gt;
		// 초기 표시 반영&lt;br /&gt;
		applyInitialViewerDisplay( viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles );&lt;br /&gt;
&lt;br /&gt;
		// 표시 변경 감시&lt;br /&gt;
		watchViewerDisplayChanges( Vue, viewerState, globalDisplayState, applyVariationVisibility, applyDisplayStyles );&lt;br /&gt;
&lt;br /&gt;
		// Vue 앱 생성 및 마운트&lt;br /&gt;
		var viewerApp = createViewerApp(&lt;br /&gt;
			viewerBar,&lt;br /&gt;
			songPageTitle,&lt;br /&gt;
			variationData,&lt;br /&gt;
			viewerState,&lt;br /&gt;
			globalDisplayState,&lt;br /&gt;
			editModalBindings,&lt;br /&gt;
			iconAddRef,&lt;br /&gt;
			iconEditRef,&lt;br /&gt;
			iconSettingsRef,&lt;br /&gt;
			applyVariationVisibility,&lt;br /&gt;
			Vue,&lt;br /&gt;
			Codex&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		// 마운트에 성공했으므로 재초기화되지 않게 표시합니다. 실패한 컨테이너는 다음 초기화 때 재시도됩니다.&lt;br /&gt;
		viewerContainer.dataset.lyricsViewerMounted = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
		return viewerApp;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// ResourceLoader 모듈에서 라이브러리 추출&lt;br /&gt;
	function extractViewerLibraries( requireFunction ) {&lt;br /&gt;
		var vueLibrary = null;&lt;br /&gt;
		var codexLibrary = null;&lt;br /&gt;
		try { vueLibrary = requireFunction( &#039;vue&#039; ); } catch ( error ) { vueLibrary = window.Vue; }&lt;br /&gt;
		try { codexLibrary = requireFunction( &#039;@wikimedia/codex&#039; ); } catch ( error ) {&lt;br /&gt;
			try { codexLibrary = requireFunction( &#039;codex&#039; ); } catch ( innerError ) {}&lt;br /&gt;
		}&lt;br /&gt;
		return { vueLibrary: vueLibrary, codexLibrary: codexLibrary };&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 라이브러리 확보 후 마운트 시도 — 마운트 성공 여부를 반환합니다.&lt;br /&gt;
	function tryMountViewers( requireFunction ) {&lt;br /&gt;
		var libs = extractViewerLibraries( requireFunction );&lt;br /&gt;
		debugLog( &#039;tryMount — vue:&#039;, !!libs.vueLibrary, &#039;codex:&#039;, !!libs.codexLibrary );&lt;br /&gt;
&lt;br /&gt;
		// Vue 또는 Codex를 찾지 못했다면 경고를 남기고 실패를 반환합니다.&lt;br /&gt;
		if ( !libs.vueLibrary || !libs.codexLibrary ) {&lt;br /&gt;
			debugWarn( &#039;Vue 또는 Codex 모듈을 찾을 수 없습니다. ResourceLoader 모듈명이 다른지 확인하세요. mw.loader.getState 확인 필요.&#039; );&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		mountViewers( libs.vueLibrary, libs.codexLibrary );&lt;br /&gt;
		return true;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// &#039;codex&#039; 모듈명으로 재시도 — 오래된 MediaWiki/Codex 설치를 위한 대체 경로입니다.&lt;br /&gt;
	function retryMountWithLegacyCodex() {&lt;br /&gt;
		try {&lt;br /&gt;
			mw.loader.using( [ &#039;vue&#039;, &#039;codex&#039;, &#039;mediawiki.api&#039;, &#039;mediawiki.Title&#039; ] ).then( tryMountViewers ).catch( function () {&lt;br /&gt;
				debugWarn( &#039;vue와 codex 모듈을 모두 불러오지 못해 Viewer를 초기화할 수 없습니다.&#039; );&lt;br /&gt;
			} );&lt;br /&gt;
		} catch ( error ) {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 진입점 — ResourceLoader로 Vue/Codex를 불러온 뒤 Viewer를 마운트&lt;br /&gt;
	function initializeViewers() {&lt;br /&gt;
		// 새로고침 전 세션의 디버그 로그를 출력합니다.&lt;br /&gt;
		if ( isDebugEnabled() ) { dumpBufferedDebugLogs(); }&lt;br /&gt;
&lt;br /&gt;
		// ResourceLoader로 Vue/Codex 로드&lt;br /&gt;
		var basePromise;&lt;br /&gt;
		try {&lt;br /&gt;
			basePromise = mw.loader.using( [ &#039;vue&#039;, &#039;@wikimedia/codex&#039;, &#039;mediawiki.api&#039;, &#039;mediawiki.Title&#039; ] );&lt;br /&gt;
		} catch ( error ) {&lt;br /&gt;
			basePromise = null;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// Promise가 유효하지 않다면 구식 모듈명으로 재시도합니다.&lt;br /&gt;
		if ( !basePromise || typeof basePromise.then !== &#039;function&#039; ) {&lt;br /&gt;
			retryMountWithLegacyCodex();&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		// 로드 성공 시 마운트, 마운트에 실패했다면 구식 모듈명으로 재시도합니다.&lt;br /&gt;
		basePromise.then( function ( requireFunction ) {&lt;br /&gt;
			if ( !tryMountViewers( requireFunction ) ) { retryMountWithLegacyCodex(); }&lt;br /&gt;
		} ).catch( retryMountWithLegacyCodex );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 전체 Viewer 마운트&lt;br /&gt;
	function mountViewers( Vue, Codex ) {&lt;br /&gt;
		// 표시 대상 수집&lt;br /&gt;
		var seenContainers = new Set();&lt;br /&gt;
		var viewerTargets = collectViewerTargets( seenContainers );&lt;br /&gt;
&lt;br /&gt;
		// 표시 대상이 하나도 없다면 아이콘 요청 없이 중단합니다.&lt;br /&gt;
		if ( !viewerTargets.length ) { return; }&lt;br /&gt;
&lt;br /&gt;
		// Codex 아이콘 준비&lt;br /&gt;
		var iconAddRef = Vue.ref( null );&lt;br /&gt;
		var iconEditRef = Vue.ref( null );&lt;br /&gt;
		var iconSettingsRef = Vue.ref( null );&lt;br /&gt;
		setupCodexIcons( Codex, iconAddRef, iconEditRef, iconSettingsRef );&lt;br /&gt;
&lt;br /&gt;
		// 전역 표시 상태 생성 및 동기화&lt;br /&gt;
		var globalDisplayState = createGlobalDisplayState( Vue );&lt;br /&gt;
		syncDisplayStateWithStorage( globalDisplayState );&lt;br /&gt;
&lt;br /&gt;
		// 각 대상에 Viewer 마운트&lt;br /&gt;
		viewerTargets.forEach( function ( target ) {&lt;br /&gt;
			mountSingleViewer(&lt;br /&gt;
				target.viewerContainer,&lt;br /&gt;
				target.mainMarkerElement,&lt;br /&gt;
				globalDisplayState,&lt;br /&gt;
				Vue,&lt;br /&gt;
				Codex,&lt;br /&gt;
				iconAddRef,&lt;br /&gt;
				iconEditRef,&lt;br /&gt;
				iconSettingsRef&lt;br /&gt;
			);&lt;br /&gt;
		} );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// DOM 준비 상태에 따라 초기화를 분기합니다.&lt;br /&gt;
	if ( document.readyState === &#039;loading&#039; ) {&lt;br /&gt;
		document.addEventListener( &#039;DOMContentLoaded&#039;, initializeViewers );&lt;br /&gt;
	} else {&lt;br /&gt;
		initializeViewers();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 편집 미리보기, AJAX 치환 등으로 나중에 삽입되는 콘텐츠도 초기화합니다.&lt;br /&gt;
	// 훅은 초기 로딩에도 발화하지만 마운트 가드 덕분에 이미 마운트된 컨테이너는 중복 초기화되지 않습니다.&lt;br /&gt;
	try {&lt;br /&gt;
		mw.hook( &#039;wikipage.content&#039; ).add( initializeViewers );&lt;br /&gt;
	} catch ( error ) {}&lt;br /&gt;
}() );&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Sitenotice&amp;diff=2794</id>
		<title>미디어위키:Sitenotice</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Sitenotice&amp;diff=2794"/>
		<updated>2026-08-13T12:48:03Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 문서를 비움&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:2026%EB%85%84_3%ED%8E%B8_%EC%88%98%EB%A1%9D%EA%B3%A1&amp;diff=2793</id>
		<title>분류:2026년 3편 수록곡</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:2026%EB%85%84_3%ED%8E%B8_%EC%88%98%EB%A1%9D%EA%B3%A1&amp;diff=2793"/>
		<updated>2026-08-13T12:30:09Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: {{창드컵 수록곡 둘러보기 |창드컵=2026년 3편 |펼치기=예 }} 분류:창드컵 수록곡&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{창드컵 수록곡 둘러보기&lt;br /&gt;
|창드컵=2026년 3편&lt;br /&gt;
|펼치기=예&lt;br /&gt;
}}&lt;br /&gt;
[[분류:창드컵 수록곡]]&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:2025%EB%85%84_5%ED%8E%B8_%EC%88%98%EB%A1%9D%EA%B3%A1&amp;diff=2792</id>
		<title>분류:2025년 5편 수록곡</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%B6%84%EB%A5%98:2025%EB%85%84_5%ED%8E%B8_%EC%88%98%EB%A1%9D%EA%B3%A1&amp;diff=2792"/>
		<updated>2026-08-13T12:29:47Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: {{창드컵 수록곡 둘러보기 |창드컵=2025년 5편 |펼치기=예 }} 분류:창드컵 수록곡&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{창드컵 수록곡 둘러보기&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
|펼치기=예&lt;br /&gt;
}}&lt;br /&gt;
[[분류:창드컵 수록곡]]&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%8B%B9%EA%B7%BC%EA%B2%8C%EC%9E%84&amp;diff=2791</id>
		<title>당근게임</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%8B%B9%EA%B7%BC%EA%B2%8C%EC%9E%84&amp;diff=2791"/>
		<updated>2026-08-13T12:28:24Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=3TcdbJ1aTqc&lt;br /&gt;
|제작자=렌슝좍&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Mountain.&amp;diff=2790</id>
		<title>Mountain.</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Mountain.&amp;diff=2790"/>
		<updated>2026-08-13T12:28:00Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=6TAL-Jl39Jk&lt;br /&gt;
|제작자=리플족&lt;br /&gt;
|수록컵={{수록 창드컵|창드컵=봄소풍컵 2026}}{{수록 창드컵|창드컵=2025년 5편}}&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Stars_don%27t_cry&amp;diff=2789</id>
		<title>Stars don&#039;t cry</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Stars_don%27t_cry&amp;diff=2789"/>
		<updated>2026-08-13T12:27:17Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=YbW3fCyAOJQ&lt;br /&gt;
|제작자=리플족&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|곡명=Stars don&#039;t cry&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%9B%90%ED%95%A0%EC%88%98%EB%A1%9D&amp;diff=2788</id>
		<title>원할수록</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%9B%90%ED%95%A0%EC%88%98%EB%A1%9D&amp;diff=2788"/>
		<updated>2026-08-13T12:25:41Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=JWSPvRML9yQ&lt;br /&gt;
|제작자=이단아&lt;br /&gt;
|공동제작자=상추토끼, 창팝용, 캡펠, 음악으로세상을정상화&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EC%8B%9C%EB%B2%A8%EB%A1%AC&amp;diff=2787</id>
		<title>시벨롬</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EC%8B%9C%EB%B2%A8%EB%A1%AC&amp;diff=2787"/>
		<updated>2026-08-13T12:25:27Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=xUlbL02Lf10&lt;br /&gt;
|제작자=리스항르&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|화자=메벤남&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=NANA&amp;diff=2786</id>
		<title>NANA</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=NANA&amp;diff=2786"/>
		<updated>2026-08-13T12:25:15Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=QmoqUv2_AVc&lt;br /&gt;
|제작자=라이프라인&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Hello,_pirates&amp;diff=2785</id>
		<title>Hello, pirates</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Hello,_pirates&amp;diff=2785"/>
		<updated>2026-08-13T12:25:06Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=ldRPOcPJZNo&lt;br /&gt;
|제작자=상추토끼&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|다른언어제목=안녕해적&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Ailasing&amp;diff=2784</id>
		<title>Ailasing</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Ailasing&amp;diff=2784"/>
		<updated>2026-08-13T12:24:56Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=Rb7n3drgUqQ&lt;br /&gt;
|제작자=다하카&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%A6%AC%EC%84%A0%EC%A1%B1%EC%9D%98_%EC%82%AC%EA%B3%84&amp;diff=2783</id>
		<title>리선족의 사계</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%A6%AC%EC%84%A0%EC%A1%B1%EC%9D%98_%EC%82%AC%EA%B3%84&amp;diff=2783"/>
		<updated>2026-08-13T12:24:46Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=qqqL5nKpc3k&lt;br /&gt;
|제작자=리데오&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{이전 수록 창드컵&lt;br /&gt;
|창드컵=2024년 5편&lt;br /&gt;
}}{{이전 수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|다른언어제목=Record of Year&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=%EB%8B%B9%EA%B7%BC%EA%B2%8C%EC%9E%84&amp;diff=2782</id>
		<title>당근게임</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=%EB%8B%B9%EA%B7%BC%EA%B2%8C%EC%9E%84&amp;diff=2782"/>
		<updated>2026-08-13T12:24:02Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=3TcdbJ1aTqc&lt;br /&gt;
|제작자=렌슝좍&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=NANA&amp;diff=2781</id>
		<title>NANA</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=NANA&amp;diff=2781"/>
		<updated>2026-08-13T12:23:48Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=QmoqUv2_AVc&lt;br /&gt;
|제작자=라이프라인&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Hello,_pirates&amp;diff=2780</id>
		<title>Hello, pirates</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Hello,_pirates&amp;diff=2780"/>
		<updated>2026-08-13T12:23:37Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=ldRPOcPJZNo&lt;br /&gt;
|제작자=상추토끼&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|다른언어제목=안녕해적&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=Ailasing&amp;diff=2779</id>
		<title>Ailasing</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=Ailasing&amp;diff=2779"/>
		<updated>2026-08-13T12:23:20Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창팝&lt;br /&gt;
|유튜브Id=Rb7n3drgUqQ&lt;br /&gt;
|제작자=다하카&lt;br /&gt;
|개사여부=아니오&lt;br /&gt;
|이전수록컵={{수록 창드컵&lt;br /&gt;
|창드컵=2025년 5편&lt;br /&gt;
}}&lt;br /&gt;
|고화질썸네일없음=아니오&lt;br /&gt;
}}&lt;br /&gt;
{{창팝 기본 개요}}&lt;br /&gt;
&amp;lt;!-- 직접 개요를 작성한 뒤에 위 개요 틀은 지워 주세요! --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 가사 ==&lt;br /&gt;
{{가사}}&lt;br /&gt;
&lt;br /&gt;
{{창팝 둘러보기}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=2026%EB%85%84_3%ED%8E%B8/%EC%88%98%EB%A1%9D%EA%B3%A1_%EA%B0%80%EC%82%AC_%EB%AA%A8%EC%9D%8C&amp;diff=2778</id>
		<title>2026년 3편/수록곡 가사 모음</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=2026%EB%85%84_3%ED%8E%B8/%EC%88%98%EB%A1%9D%EA%B3%A1_%EA%B0%80%EC%82%AC_%EB%AA%A8%EC%9D%8C&amp;diff=2778"/>
		<updated>2026-08-13T12:22:03Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: {{문서:창드컵 수록곡 가사 모음}}&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창드컵 수록곡 가사 모음}}&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=2026%EB%85%84_3%ED%8E%B8&amp;diff=2777</id>
		<title>2026년 3편</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=2026%EB%85%84_3%ED%8E%B8&amp;diff=2777"/>
		<updated>2026-08-13T12:21:46Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: 새 문서: {{문서:창드컵 |명칭=2026년 3편 |PikuId=arlVTl |강=32 |유형=엄선작 2026 |오픈날짜=2026-08-03 }} &amp;#039;&amp;#039;&amp;#039;2026년 3편&amp;#039;&amp;#039;&amp;#039;은 2026년 엄선작 창드컵이다. 2026년 8월 3일에 베타를 시작했다.&amp;lt;ref&amp;gt;[https://gall.dcinside.com/mgallery/board/view/?id=maplereboot&amp;amp;no=3740829 창드컵 2주년 기념 26-3 베타 시작]&amp;lt;/ref&amp;gt;  == 수록곡 == {{창드컵 수록곡 둘러보기 | 창드컵 = {{문서이름}} | 펼치기 = 예  }} {{수록곡 표}}  === 가사...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{문서:창드컵&lt;br /&gt;
|명칭=2026년 3편&lt;br /&gt;
|PikuId=arlVTl&lt;br /&gt;
|강=32&lt;br /&gt;
|유형=엄선작 2026&lt;br /&gt;
|오픈날짜=2026-08-03&lt;br /&gt;
}}&lt;br /&gt;
&#039;&#039;&#039;2026년 3편&#039;&#039;&#039;은 2026년 엄선작 [[창드컵]]이다. 2026년 8월 3일에 베타를 시작했다.&amp;lt;ref&amp;gt;[https://gall.dcinside.com/mgallery/board/view/?id=maplereboot&amp;amp;no=3740829 창드컵 2주년 기념 26-3 베타 시작]&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 수록곡 ==&lt;br /&gt;
{{창드컵 수록곡 둘러보기&lt;br /&gt;
| 창드컵 = {{문서이름}}&lt;br /&gt;
| 펼치기 = 예 &lt;br /&gt;
}}&lt;br /&gt;
{{수록곡 표}}&lt;br /&gt;
&lt;br /&gt;
=== 가사 모음 ===&lt;br /&gt;
[[/수록곡 가사 모음]]&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
	<entry>
		<id>https://changpop.wiki/index.php?title=26-3&amp;diff=2776</id>
		<title>26-3</title>
		<link rel="alternate" type="text/html" href="https://changpop.wiki/index.php?title=26-3&amp;diff=2776"/>
		<updated>2026-08-13T12:21:39Z</updated>

		<summary type="html">&lt;p&gt;창팝위키: Redirect added via the RedirectManager dialog.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#넘겨주기 [[2026년 3편]]&lt;/div&gt;</summary>
		<author><name>창팝위키</name></author>
	</entry>
</feed>