diff --git a/docs/_config.yml b/docs/_config.yml
index 0a4deb4..99a2d6d 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -68,6 +68,9 @@ callouts:
# For copy button on code
enable_copy_code_button: true
+# Configure heading anchor links
+heading_anchors: true
+
# Waiting until it's needed.
# # By default, consuming the theme as a gem leaves mermaid disabled; it is opt-in
# mermaid:
diff --git a/docs/_includes/head_custom.html b/docs/_includes/head_custom.html
index 7ccf75d..c3bcc82 100644
--- a/docs/_includes/head_custom.html
+++ b/docs/_includes/head_custom.html
@@ -1,3 +1,7 @@
-
+
+
+
+
diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss
index 2b871b5..829d346 100644
--- a/docs/_sass/custom/custom.scss
+++ b/docs/_sass/custom/custom.scss
@@ -1,3 +1,429 @@
.site-logo {
padding-right: 3rem;
}
+
+/* Hide the original search container in main header */
+#main-header .search {
+ display: none;
+}
+
+/* Show search container in modal */
+.search-modal .search {
+ display: flex !important;
+}
+
+/* Style the compact search trigger button */
+.search-modal-trigger-wrapper {
+ display: flex;
+ align-items: center;
+}
+
+#main-header .search-modal-trigger {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem 1rem;
+ background: #f8f9fa;
+ border: 1px solid #dee2e6;
+ border-radius: 6px;
+ color: #495057;
+ font-size: 0.875rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+/* Page TOC (Table of Contents) */
+.page-toc {
+ position: fixed;
+ right: 2rem;
+ top: 6rem;
+ width: 250px;
+ max-height: calc(100vh - 8rem);
+ overflow-y: auto;
+ background-color: #fff;
+ border: 1px solid #e8e8e8;
+ border-radius: 6px;
+ padding: 1rem;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ font-size: 0.875rem;
+ z-index: 10;
+}
+
+.page-toc-heading {
+ font-weight: 600;
+ font-size: 0.9rem;
+ color: #333;
+ margin-bottom: 0.75rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid #e8e8e8;
+}
+
+.page-toc-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.page-toc-item {
+ margin: 0.25rem 0;
+}
+
+.page-toc-link {
+ display: block;
+ color: #666;
+ text-decoration: none;
+ padding: 0.25rem 0;
+ transition: color 0.2s ease;
+}
+
+.page-toc-link:hover {
+ color: #0050d0;
+}
+
+.page-toc-link.active {
+ color: #0050d0 !important;
+ font-weight: 500;
+ border-left: 2px solid #0050d0;
+ padding-left: 0.5rem;
+}
+
+/* Dark mode support */
+html.dark-mode .page-toc {
+ background-color: #1a1a1a !important;
+ border-color: #333 !important;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
+}
+
+html.dark-mode .page-toc-heading {
+ color: #e0e0e0 !important;
+ border-color: #333 !important;
+}
+
+html.dark-mode .page-toc-link {
+ color: #a0a0a0 !important;
+}
+
+html.dark-mode .page-toc-link:hover {
+ color: #7dd3fc !important;
+}
+
+html.dark-mode .page-toc-link.active {
+ color: #7dd3fc !important;
+ border-color: #7dd3fc !important;
+}
+
+/* Responsive - hide TOC on smaller screens */
+@media screen and (max-width: 1280px) {
+ .page-toc {
+ display: none;
+ }
+}
+
+/* Smooth scrolling for anchor links */
+html {
+ scroll-behavior: smooth;
+}
+
+/* Search Modal Styles */
+.search-modal-trigger-wrapper {
+ display: flex;
+ align-items: center;
+}
+
+.search-modal-trigger {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem 1rem;
+ background: #f8f9fa;
+ border: 1px solid #dee2e6;
+ border-radius: 6px;
+ color: #495057;
+ font-size: 0.875rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.search-modal-trigger:hover {
+ background: #e9ecef;
+ border-color: #adb5bd;
+}
+
+.search-modal-trigger svg {
+ flex-shrink: 0;
+}
+
+.search-modal-shortcut {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.125rem 0.375rem;
+ background: #fff;
+ border: 1px solid #dee2e6;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: #6c757d;
+}
+
+/* Modal Overlay */
+.search-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 99998;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s ease;
+}
+
+.search-modal-overlay.active {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Modal Container */
+.search-modal {
+ position: fixed;
+ top: 10%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-20px);
+ width: 90%;
+ max-width: 700px;
+ max-height: 80vh;
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+ z-index: 99999;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s ease;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.search-modal.active {
+ opacity: 1;
+ visibility: visible;
+ transform: translateX(-50%) translateY(0);
+}
+
+/* Native search component in modal */
+.search-modal .search {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ height: auto !important;
+ padding: 1rem !important;
+}
+
+/* Show search components when in modal */
+.search-modal .search .search-input-wrap {
+ display: flex !important;
+ width: 100%;
+ flex-shrink: 0;
+ height: auto !important;
+ position: relative !important;
+ max-width: none !important;
+ overflow: visible !important;
+ padding: 0 !important;
+}
+
+/* Override search input styles */
+.search-modal .search .search-input {
+ position: relative !important;
+ width: 100%;
+ height: auto !important;
+ padding: 0.75rem 1rem 0.75rem 2.75rem !important;
+ font-size: 1rem;
+ border: 2px solid #dee2e6;
+ border-radius: 8px;
+ outline: none;
+ background: #fff;
+ color: #212529;
+ transition: none !important;
+}
+
+.search-modal .search .search-input:focus {
+ border-color: #0050d0;
+ box-shadow: 0 0 0 4px rgba(0, 80, 208, 0.1);
+}
+
+.search-modal .search .search-input::placeholder {
+ color: #adb5bd;
+}
+
+/* Override search label styles */
+.search-modal .search .search-label {
+ position: absolute;
+ left: 0.875rem;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 1;
+ color: #6c757d;
+ pointer-events: none;
+ padding: 0 !important;
+ transition: none !important;
+ height: auto !important;
+}
+
+.search-modal .search .search-label .search-icon {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.search-modal .search #search-results {
+ display: block !important;
+ flex: 1;
+ overflow-y: auto;
+ position: relative !important;
+ top: auto !important;
+ left: auto !important;
+ width: auto !important;
+ box-shadow: none !important;
+ border-radius: 0 !important;
+ background: transparent !important;
+}
+
+/* Modal close button */
+.search-modal-close {
+ position: absolute;
+ top: 1.25rem;
+ right: 1.25rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border: none;
+ background: transparent;
+ color: #6c757d;
+ cursor: pointer;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+ z-index: 10;
+}
+
+.search-modal-close:hover {
+ background: #f8f9fa;
+ color: #212529;
+}
+
+/* Dark mode */
+html.dark-mode .search-modal-trigger-wrapper {
+ background: transparent;
+}
+
+html.dark-mode .search-modal-trigger {
+ background: #2d2d2d !important;
+ border-color: #404040 !important;
+ color: #e0e0e0 !important;
+}
+
+html.dark-mode .search-modal-trigger:hover {
+ background: #3d3d3d !important;
+ border-color: #505050 !important;
+}
+
+html.dark-mode .search-modal-shortcut {
+ background: #1a1a1a !important;
+ border-color: #404040 !important;
+ color: #a0a0a0 !important;
+}
+
+html.dark-mode .search-modal-overlay {
+ background: rgba(0, 0, 0, 0.7) !important;
+}
+
+html.dark-mode .search-modal {
+ background: #1a1a1a !important;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5) !important;
+}
+
+html.dark-mode .search-modal .search .search-input {
+ background: #2d2d2d !important;
+ border-color: #404040 !important;
+ color: #e0e0e0 !important;
+}
+
+html.dark-mode .search-modal .search .search-input:focus {
+ border-color: #7dd3fc !important;
+ box-shadow: 0 0 0 4px rgba(125, 211, 252, 0.1) !important;
+}
+
+html.dark-mode .search-modal .search .search-input::placeholder {
+ color: #666 !important;
+}
+
+html.dark-mode .search-modal .search .search-label {
+ color: #a0a0a0 !important;
+}
+
+html.dark-mode .search-modal .search #search-results {
+ background: transparent !important;
+}
+
+html.dark-mode .search-modal-close {
+ color: #a0a0a0 !important;
+}
+
+html.dark-mode .search-modal-close:hover {
+ background: #2d2d2d !important;
+ color: #fff !important;
+}
+
+/* Dark mode scrollbar styles */
+html.dark-mode {
+ /* WebKit browsers (Chrome, Safari, Edge) */
+ ::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+ }
+
+ ::-webkit-scrollbar-track {
+ background: #1a1a1a;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: #404040;
+ border-radius: 5px;
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: #505050;
+ }
+
+ ::-webkit-scrollbar-corner {
+ background: #1a1a1a;
+ }
+}
+
+/* Firefox scrollbar styles */
+@supports (scrollbar-width: thin) {
+ html.dark-mode {
+ scrollbar-color: #404040 #1a1a1a;
+ scrollbar-width: thin;
+ }
+}
+
+/* Responsive */
+@media (max-width: 640px) {
+ .search-modal {
+ top: 5%;
+ width: 95%;
+ max-height: 85vh;
+ }
+
+ .search-modal-trigger {
+ padding: 0.375rem 0.75rem;
+ }
+
+ .search-modal-shortcut {
+ display: none;
+ }
+}
+
diff --git a/docs/assets/js/search-modal.js b/docs/assets/js/search-modal.js
new file mode 100644
index 0000000..72f81e3
--- /dev/null
+++ b/docs/assets/js/search-modal.js
@@ -0,0 +1,230 @@
+/**
+ * Search Modal - VitePress style search overlay
+ * Moves original Just the Docs search component into modal
+ */
+
+(function() {
+ 'use strict';
+
+ let modal = null;
+ let modalOverlay = null;
+ let originalSearchContainer = null;
+ let isOpen = false;
+
+ // Create modal HTML structure
+ function createModal() {
+ // Overlay
+ modalOverlay = document.createElement('div');
+ modalOverlay.className = 'search-modal-overlay';
+ modalOverlay.setAttribute('aria-hidden', 'true');
+
+ // Modal container
+ modal = document.createElement('div');
+ modal.className = 'search-modal';
+ modal.setAttribute('role', 'dialog');
+ modal.setAttribute('aria-modal', 'true');
+ modal.setAttribute('aria-label', 'Search');
+
+ // Close button
+ const closeButton = document.createElement('button');
+ closeButton.className = 'search-modal-close';
+ closeButton.setAttribute('aria-label', 'Close');
+ closeButton.innerHTML = ``;
+ closeButton.addEventListener('click', closeModal);
+
+ // Assemble modal
+ modal.appendChild(closeButton);
+
+ // Add to document
+ document.body.appendChild(modalOverlay);
+ document.body.appendChild(modal);
+
+ // Event listeners
+ modalOverlay.addEventListener('click', closeModal);
+ document.addEventListener('keydown', handleGlobalKeydown);
+ }
+
+ // Move original search component to modal
+ function moveSearchToModal() {
+ const mainHeader = document.querySelector('#main-header');
+ if (!mainHeader) return false;
+
+ originalSearchContainer = mainHeader.querySelector('.search');
+ if (!originalSearchContainer) return false;
+
+ // Move search container to modal
+ modal.appendChild(originalSearchContainer);
+
+ // Update input placeholder
+ const searchInput = document.getElementById('search-input');
+ if (searchInput) {
+ searchInput.placeholder = 'Search twinBASIC Documentation';
+ }
+
+ // Force reflow to ensure styles apply
+ void modal.offsetHeight;
+
+ console.log('Search moved to modal, modal children:', modal.children.length);
+
+ return true;
+ }
+
+ // Open modal
+ function openModal() {
+ // Ensure modal exists
+ if (!modal) {
+ createModal();
+ }
+
+ // Move search component to modal if not done
+ if (!modal.querySelector('.search')) {
+ if (!moveSearchToModal()) {
+ // Wait for search to be ready
+ const checkInterval = setInterval(() => {
+ if (moveSearchToModal()) {
+ clearInterval(checkInterval);
+ openModalNow();
+ }
+ }, 200);
+
+ // Timeout after 3 seconds
+ setTimeout(() => clearInterval(checkInterval), 3000);
+ return;
+ }
+ }
+
+ openModalNow();
+ }
+
+ function openModalNow() {
+ if (!modal || !modalOverlay) {
+ console.error('Modal or overlay not found!');
+ return;
+ }
+
+ isOpen = true;
+ modalOverlay.classList.add('active');
+ modal.classList.add('active');
+ document.body.style.overflow = 'hidden';
+
+ // Focus on search input
+ setTimeout(() => {
+ const searchInput = document.getElementById('search-input');
+ if (searchInput) {
+ searchInput.focus();
+ }
+ }, 100);
+ }
+
+ // Close modal
+ function closeModal() {
+ if (!modal) return;
+
+ isOpen = false;
+ modalOverlay.classList.remove('active');
+ modal.classList.remove('active');
+ document.body.style.overflow = '';
+
+ // Clear search input
+ const searchInput = document.getElementById('search-input');
+ if (searchInput) {
+ searchInput.value = '';
+ const keyupEvent = new KeyboardEvent('keyup', {
+ bubbles: true,
+ cancelable: true,
+ keyCode: 65,
+ key: ''
+ });
+ searchInput.dispatchEvent(keyupEvent);
+ }
+ }
+
+ // Handle global keyboard events
+ function handleGlobalKeydown(e) {
+ // Cmd/Ctrl + K to open search
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+ e.preventDefault();
+ if (isOpen) {
+ closeModal();
+ } else {
+ openModal();
+ }
+ }
+
+ // Escape to close
+ if (e.key === 'Escape' && isOpen) {
+ closeModal();
+ }
+ }
+
+ // Create search button in header
+ function createSearchButton() {
+ const mainHeader = document.querySelector('#main-header');
+ if (!mainHeader) {
+ setTimeout(createSearchButton, 200);
+ return;
+ }
+
+ // Check if button already exists
+ if (document.querySelector('.search-modal-trigger')) {
+ return;
+ }
+
+ // Find original search container
+ const searchContainer = mainHeader.querySelector('.search');
+ if (!searchContainer) {
+ setTimeout(createSearchButton, 200);
+ return;
+ }
+
+ // Find nav element
+ const nav = mainHeader.querySelector('nav');
+ if (!nav) {
+ setTimeout(createSearchButton, 200);
+ return;
+ }
+
+ // Create search button wrapper
+ const buttonWrapper = document.createElement('div');
+ buttonWrapper.className = 'search-modal-trigger-wrapper';
+
+ // Create search button
+ const searchButton = document.createElement('button');
+ searchButton.className = 'search-modal-trigger';
+ searchButton.setAttribute('aria-label', 'Search');
+ searchButton.innerHTML = ``;
+
+ // Add keyboard shortcut hint
+ const shortcut = document.createElement('span');
+ shortcut.className = 'search-modal-shortcut';
+ shortcut.textContent = '⌘K';
+ searchButton.appendChild(shortcut);
+
+ searchButton.addEventListener('click', openModal);
+ buttonWrapper.appendChild(searchButton);
+
+ // Insert button wrapper after nav
+ nav.parentNode.insertBefore(buttonWrapper, nav.nextSibling);
+ }
+
+ // Initialize
+ function init() {
+ // Wait for DOM to be ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', () => {
+ setTimeout(createSearchButton, 100);
+ });
+ } else {
+ setTimeout(createSearchButton, 100);
+ }
+ }
+
+ init();
+
+})();
diff --git a/docs/assets/js/toc.js b/docs/assets/js/toc.js
new file mode 100644
index 0000000..2c7cde1
--- /dev/null
+++ b/docs/assets/js/toc.js
@@ -0,0 +1,176 @@
+/**
+ * Just the Docs - In-page Table of Contents Generator
+ * Generates a floating TOC based on page headings (H1-H4)
+ */
+
+(function() {
+ 'use strict';
+
+ // Configuration
+ const config = {
+ minLevel: 2, // Minimum heading level to include (h2)
+ maxLevel: 4, // Maximum heading level to include (h4)
+ containerId: 'page-toc',
+ containerClass: 'page-toc',
+ headingClass: 'page-toc-heading',
+ listClass: 'page-toc-list',
+ itemClass: 'page-toc-item',
+ linkClass: 'page-toc-link',
+ activeClass: 'active'
+ };
+
+ // Create TOC container
+ function createTOCContainer() {
+ const container = document.createElement('div');
+ container.id = config.containerId;
+ container.className = config.containerClass;
+ return container;
+ }
+
+ // Generate TOC from headings
+ function generateTOC() {
+ const content = document.querySelector('.main-content');
+ if (!content) return null;
+
+ const headings = content.querySelectorAll('h2, h3, h4');
+ if (headings.length < 2) return null;
+
+ const container = createTOCContainer();
+
+ // Add heading
+ const heading = document.createElement('div');
+ heading.className = config.headingClass;
+ heading.textContent = 'On this page';
+ container.appendChild(heading);
+
+ // Create list
+ const list = document.createElement('ul');
+ list.className = config.listClass;
+
+ let currentList = list;
+ let lastLevel = config.minLevel;
+ const listStack = [list];
+
+ headings.forEach((heading, index) => {
+ const level = parseInt(heading.tagName.charAt(1));
+
+ if (level < config.minLevel || level > config.maxLevel) return;
+
+ // Ensure heading has an ID for linking
+ if (!heading.id) {
+ heading.id = heading.textContent
+ .toLowerCase()
+ .replace(/\s+/g, '-')
+ .replace(/[^\w\-]/g, '')
+ .replace(/\-+/g, '-')
+ .replace(/^-+|-+$/g, '') + '-' + index;
+ }
+
+ // Handle nested lists
+ if (level > lastLevel) {
+ const subList = document.createElement('ul');
+ subList.className = config.listClass;
+ const lastItem = currentList.lastElementChild;
+ if (lastItem) {
+ lastItem.appendChild(subList);
+ currentList = subList;
+ listStack.push(subList);
+ }
+ } else if (level < lastLevel) {
+ while (listStack.length > 1) {
+ listStack.pop();
+ currentList = listStack[listStack.length - 1];
+ const stackTopLevel = getCurrentListLevel(currentList);
+ if (stackTopLevel <= level) break;
+ }
+ }
+
+ // Create list item
+ const item = document.createElement('li');
+ item.className = config.itemClass;
+ item.style.paddingLeft = ((level - config.minLevel) * 12) + 'px';
+
+ const link = document.createElement('a');
+ link.className = config.linkClass;
+ link.href = '#' + heading.id;
+ link.textContent = heading.textContent;
+ link.dataset.target = heading.id;
+
+ item.appendChild(link);
+ currentList.appendChild(item);
+ lastLevel = level;
+ });
+
+ container.appendChild(list);
+ return container;
+ }
+
+ function getCurrentListLevel(list) {
+ let level = config.minLevel;
+ let parent = list.parentElement;
+ while (parent && parent.tagName !== 'DIV') {
+ if (parent.tagName === 'UL') level++;
+ parent = parent.parentElement;
+ }
+ return level;
+ }
+
+ // Insert TOC into page
+ function insertTOC(toc) {
+ const mainContent = document.querySelector('.main-content');
+ if (!mainContent) return;
+
+ // Insert after the first h1
+ const firstH1 = mainContent.querySelector('h1');
+ if (firstH1) {
+ firstH1.parentNode.insertBefore(toc, firstH1.nextSibling);
+ } else {
+ mainContent.insertBefore(toc, mainContent.firstChild);
+ }
+ }
+
+ // Highlight active heading on scroll
+ function setupScrollSpy() {
+ const headings = document.querySelectorAll('.main-content h2, .main-content h3, .main-content h4');
+ const tocLinks = document.querySelectorAll(`.${config.linkClass}`);
+
+ if (tocLinks.length === 0) return;
+
+ const observerOptions = {
+ rootMargin: '-100px 0px -66%',
+ threshold: 0
+ };
+
+ const observer = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ const id = entry.target.id;
+ tocLinks.forEach(link => {
+ link.classList.remove(config.activeClass);
+ if (link.dataset.target === id) {
+ link.classList.add(config.activeClass);
+ }
+ });
+ }
+ });
+ }, observerOptions);
+
+ headings.forEach(heading => observer.observe(heading));
+ }
+
+ // Initialize
+ function init() {
+ const toc = generateTOC();
+ if (toc) {
+ insertTOC(toc);
+ setupScrollSpy();
+ }
+ }
+
+ // Run when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();