@@ -41,11 +41,124 @@ <h1 class="post-title">{{ page.title }}</h1>
4141 </ div >
4242 </ header >
4343
44- < div class ="post-content ">
45- {{ content }}
44+ < div class ="post-layout ">
45+ < aside class ="post-sidebar " id ="post-sidebar ">
46+ < div class ="toc-wrapper " id ="toc-wrapper " style ="display: none; ">
47+ < div class ="toc-header ">
48+ < svg xmlns ="http://www.w3.org/2000/svg " width ="14 " height ="14 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round "> < line x1 ="21 " x2 ="3 " y1 ="6 " y2 ="6 "/> < line x1 ="15 " x2 ="3 " y1 ="12 " y2 ="12 "/> < line x1 ="17 " x2 ="3 " y1 ="18 " y2 ="18 "/> </ svg >
49+ < span > Contents</ span >
50+ </ div >
51+ < ul class ="toc-list " id ="toc-list "> </ ul >
52+ </ div >
53+ </ aside >
54+
55+ < div class ="post-content " id ="post-content ">
56+ {{ content }}
57+ </ div >
4658 </ div >
4759</ article >
4860
61+ < script >
62+ document . addEventListener ( 'DOMContentLoaded' , function ( ) {
63+ // 1. Generate Table of Contents (TOC)
64+ const content = document . getElementById ( 'post-content' ) ;
65+ const tocWrapper = document . getElementById ( 'toc-wrapper' ) ;
66+ const tocList = document . getElementById ( 'toc-list' ) ;
67+
68+ if ( content && tocList && tocWrapper ) {
69+ const headings = content . querySelectorAll ( 'h2, h3' ) ;
70+ if ( headings . length >= 2 ) {
71+ tocWrapper . style . display = 'block' ;
72+
73+ headings . forEach ( ( heading , idx ) => {
74+ if ( ! heading . id ) {
75+ heading . id = 'heading-' + idx ;
76+ }
77+
78+ const li = document . createElement ( 'li' ) ;
79+ li . className = 'toc-item ' + heading . tagName . toLowerCase ( ) ;
80+
81+ const a = document . createElement ( 'a' ) ;
82+ a . href = '#' + heading . id ;
83+ a . textContent = heading . textContent . replace ( / ^ # + \s * / , '' ) ;
84+
85+ a . addEventListener ( 'click' , function ( e ) {
86+ e . preventDefault ( ) ;
87+ heading . scrollIntoView ( { behavior : 'smooth' } ) ;
88+ history . pushState ( null , null , '#' + heading . id ) ;
89+ } ) ;
90+
91+ li . appendChild ( a ) ;
92+ tocList . appendChild ( li ) ;
93+ } ) ;
94+
95+ // IntersectionObserver for active heading highlight
96+ const observerOptions = {
97+ root : null ,
98+ rootMargin : '-80px 0px -60% 0px' ,
99+ threshold : 0
100+ } ;
101+
102+ const observer = new IntersectionObserver ( ( entries ) => {
103+ entries . forEach ( entry => {
104+ if ( entry . isIntersecting ) {
105+ const id = entry . target . id ;
106+ const activeLink = tocList . querySelector ( `a[href="#${ id } "]` ) ;
107+ if ( activeLink ) {
108+ tocList . querySelectorAll ( 'a' ) . forEach ( link => link . classList . remove ( 'active' ) ) ;
109+ activeLink . classList . add ( 'active' ) ;
110+ }
111+ }
112+ } ) ;
113+ } , observerOptions ) ;
114+
115+ headings . forEach ( h => observer . observe ( h ) ) ;
116+ }
117+ }
118+
119+ // 2. Dynamic Inline Footnotes Popover
120+ const footnotes = document . querySelectorAll ( 'a.footnote, a[href^="#fn:"], sup[id^="fnref"] a' ) ;
121+ footnotes . forEach ( fnLink => {
122+ fnLink . addEventListener ( 'click' , function ( e ) {
123+ const href = this . getAttribute ( 'href' ) ;
124+ if ( ! href || ! href . startsWith ( '#' ) ) return ;
125+
126+ const targetFn = document . querySelector ( href ) ;
127+ if ( targetFn ) {
128+ e . preventDefault ( ) ;
129+
130+ // Existing popover toggle
131+ let existing = this . parentNode . querySelector ( '.footnote-popover' ) ;
132+ if ( existing ) {
133+ existing . remove ( ) ;
134+ return ;
135+ }
136+
137+ // Close other popovers
138+ document . querySelectorAll ( '.footnote-popover' ) . forEach ( p => p . remove ( ) ) ;
139+
140+ const popover = document . createElement ( 'div' ) ;
141+ popover . className = 'footnote-popover' ;
142+
143+ const textContent = targetFn . innerText . replace ( / ↩ | ↩ ️ / g, '' ) . trim ( ) ;
144+ popover . innerHTML = `
145+ <div style="font-size: 0.85rem; line-height: 1.5; color: var(--text-main);">${ textContent } </div>
146+ <button class="footnote-close-btn" aria-label="Close">×</button>
147+ ` ;
148+
149+ popover . querySelector ( '.footnote-close-btn' ) . addEventListener ( 'click' , ( ev ) => {
150+ ev . stopPropagation ( ) ;
151+ popover . remove ( ) ;
152+ } ) ;
153+
154+ this . parentNode . style . position = 'relative' ;
155+ this . parentNode . appendChild ( popover ) ;
156+ }
157+ } ) ;
158+ } ) ;
159+ } ) ;
160+ </ script >
161+
49162{% if site.related_posts.size > 0 %}
50163< section style ="margin-top: 3rem; margin-bottom: 2.5rem; ">
51164 < div class ="section-header ">
0 commit comments