Skip to content

Commit d700f6a

Browse files
committed
Add mobile table of contents functionality to all post pages.
1 parent c35aee7 commit d700f6a

13 files changed

Lines changed: 3107 additions & 0 deletions

post_template.html

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,24 @@ <h2 class="section-heading" style="justify-content: flex-start;">Technologies &
211211
<!-- ADD THIS LINE TO LOAD THE PYTHON LANGUAGE -->
212212
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
213213

214+
<!-- ===== Mobile TOC Start ===== -->
215+
<button id="mobile-toc-toggle" class="mobile-toc-toggle" aria-label="Table of contents">
216+
<i class="fas fa-list-ul"></i>
217+
</button>
218+
219+
<div id="mobile-toc-overlay" class="mobile-toc-overlay">
220+
<div class="mobile-toc-panel">
221+
<div class="mobile-toc-header">
222+
<span class="mobile-toc-title">On This Page</span>
223+
<button id="mobile-toc-close" class="mobile-toc-close" aria-label="Close">
224+
<i class="fas fa-xmark"></i>
225+
</button>
226+
</div>
227+
<div id="mobile-toc-container" class="mobile-toc-container"></div>
228+
</div>
229+
</div>
230+
<!-- ===== Mobile TOC End ===== -->
231+
214232
<!-- ===== Style Switcher Start ===== -->
215233
<div class="theme-toggle-container">
216234
<div id="theme-toggle-icon" class="theme-icon">
@@ -399,6 +417,175 @@ <h2 class="section-heading" style="justify-content: flex-start;">Technologies &
399417
margin-right: 0.4rem;
400418
color: gray;
401419
}
420+
421+
/* ===== Mobile TOC Floating Button ===== */
422+
.mobile-toc-toggle {
423+
display: none;
424+
/* Hidden by default — shown via JS + media query */
425+
}
426+
427+
/* ===== Mobile TOC Overlay ===== */
428+
.mobile-toc-overlay {
429+
display: none;
430+
position: fixed;
431+
inset: 0;
432+
z-index: 999;
433+
background: rgba(0, 0, 0, 0.5);
434+
backdrop-filter: blur(4px);
435+
-webkit-backdrop-filter: blur(4px);
436+
}
437+
438+
.mobile-toc-overlay.open {
439+
display: flex;
440+
align-items: flex-end;
441+
justify-content: center;
442+
animation: tocFadeIn 0.2s ease-out;
443+
}
444+
445+
@keyframes tocFadeIn {
446+
from {
447+
opacity: 0;
448+
}
449+
450+
to {
451+
opacity: 1;
452+
}
453+
}
454+
455+
@keyframes tocSlideUp {
456+
from {
457+
transform: translateY(100%);
458+
}
459+
460+
to {
461+
transform: translateY(0);
462+
}
463+
}
464+
465+
.mobile-toc-panel {
466+
background: var(--bg-color);
467+
border-radius: 16px 16px 0 0;
468+
width: 100%;
469+
max-width: 500px;
470+
max-height: 70vh;
471+
display: flex;
472+
flex-direction: column;
473+
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
474+
animation: tocSlideUp 0.3s ease-out;
475+
}
476+
477+
.mobile-toc-header {
478+
display: flex;
479+
align-items: center;
480+
justify-content: space-between;
481+
padding: 1rem 1.25rem;
482+
border-bottom: 1px solid var(--border-color);
483+
flex-shrink: 0;
484+
}
485+
486+
.mobile-toc-title {
487+
font-weight: 700;
488+
font-size: 1.1rem;
489+
text-transform: uppercase;
490+
letter-spacing: 0.5px;
491+
}
492+
493+
.mobile-toc-close {
494+
background: none;
495+
border: none;
496+
font-size: 1.2rem;
497+
color: var(--text-color);
498+
cursor: pointer;
499+
padding: 0.25rem 0.5rem;
500+
border-radius: 4px;
501+
transition: background 0.2s;
502+
}
503+
504+
.mobile-toc-close:hover {
505+
background: var(--card-bg-color);
506+
}
507+
508+
.mobile-toc-container {
509+
overflow-y: auto;
510+
padding: 0.75rem 1.25rem 1.5rem;
511+
}
512+
513+
.mobile-toc-container .toc-list {
514+
list-style: none;
515+
padding-left: 0;
516+
margin: 0;
517+
display: flex;
518+
flex-direction: column;
519+
gap: 0.15rem;
520+
}
521+
522+
.mobile-toc-container .toc-link {
523+
display: block;
524+
color: var(--text-color);
525+
text-decoration: none;
526+
line-height: 1.4;
527+
padding: 0.5rem 0.5rem 0.5rem 0.75rem;
528+
border-left: 2px solid transparent;
529+
border-radius: 0 6px 6px 0;
530+
transition: all 0.15s ease;
531+
font-size: 0.95rem;
532+
}
533+
534+
.mobile-toc-container .toc-link:hover,
535+
.mobile-toc-container .toc-link:active {
536+
background: var(--card-bg-color);
537+
color: var(--link-color);
538+
border-left-color: var(--link-color);
539+
}
540+
541+
/* ===== Mobile: hide sidebar TOC, show toggle, stack nav ===== */
542+
@media (max-width: 48rem) {
543+
.post-sidebar-left {
544+
display: none !important;
545+
}
546+
547+
.mobile-toc-toggle.has-toc {
548+
display: flex;
549+
align-items: center;
550+
justify-content: center;
551+
position: fixed;
552+
bottom: 5.5rem;
553+
right: 1.25rem;
554+
z-index: 50;
555+
width: 48px;
556+
height: 48px;
557+
border-radius: 50%;
558+
border: 1px solid var(--border-color);
559+
background: var(--card-bg-color);
560+
color: var(--link-color);
561+
font-size: 1.15rem;
562+
cursor: pointer;
563+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
564+
transition: all 0.2s ease;
565+
}
566+
567+
.mobile-toc-toggle.has-toc:hover,
568+
.mobile-toc-toggle.has-toc:active {
569+
border-color: var(--link-color);
570+
box-shadow: 0 4px 16px rgba(0, 119, 204, 0.2);
571+
transform: scale(1.05);
572+
}
573+
574+
/* Stack prev/next navigation vertically */
575+
.post-navigation {
576+
flex-direction: column;
577+
gap: 1rem;
578+
}
579+
580+
.nav-link {
581+
width: 100%;
582+
}
583+
584+
.next-link {
585+
text-align: left;
586+
justify-content: flex-start;
587+
}
588+
}
402589
</style>
403590

404591
<script>
@@ -474,6 +661,58 @@ <h2 class="section-heading" style="justify-content: flex-start;">Technologies &
474661

475662
tocContainer.appendChild(tocList);
476663

664+
// ===== Mobile TOC: clone into overlay =====
665+
const mobileTocContainer = document.getElementById('mobile-toc-container');
666+
const mobileTocToggle = document.getElementById('mobile-toc-toggle');
667+
const mobileTocOverlay = document.getElementById('mobile-toc-overlay');
668+
const mobileTocClose = document.getElementById('mobile-toc-close');
669+
670+
if (mobileTocContainer && mobileTocToggle) {
671+
// Show the floating toggle button (CSS will only display it on mobile)
672+
mobileTocToggle.classList.add('has-toc');
673+
674+
// Clone the TOC list into the mobile panel
675+
const mobileList = tocList.cloneNode(true);
676+
mobileTocContainer.appendChild(mobileList);
677+
678+
// Handle link clicks in mobile TOC
679+
mobileList.querySelectorAll('.toc-link').forEach(mobileLink => {
680+
mobileLink.addEventListener('click', (e) => {
681+
e.preventDefault();
682+
const targetId = mobileLink.getAttribute('href').substring(1);
683+
const targetEl = document.getElementById(targetId);
684+
if (targetEl) {
685+
// Close overlay
686+
mobileTocOverlay.classList.remove('open');
687+
// Navigate
688+
history.pushState(null, null, '#' + targetId);
689+
setTimeout(() => {
690+
targetEl.scrollIntoView({ behavior: 'smooth' });
691+
}, 100);
692+
}
693+
});
694+
});
695+
696+
// Toggle button opens overlay
697+
mobileTocToggle.addEventListener('click', () => {
698+
mobileTocOverlay.classList.toggle('open');
699+
});
700+
701+
// Close button
702+
if (mobileTocClose) {
703+
mobileTocClose.addEventListener('click', () => {
704+
mobileTocOverlay.classList.remove('open');
705+
});
706+
}
707+
708+
// Click backdrop to close
709+
mobileTocOverlay.addEventListener('click', (e) => {
710+
if (e.target === mobileTocOverlay) {
711+
mobileTocOverlay.classList.remove('open');
712+
}
713+
});
714+
}
715+
477716
// Scrollspy logic
478717
function updateScrollSpy() {
479718
let currentActive = null;

0 commit comments

Comments
 (0)