bsc

Comprehensive codebase and cou...
Log | Files | Refs | README | LICENSE

commit 6f5d2aac18e53b5433c64aed02db6c018d0e4d3b
parent bbf871a969ce7dfa2de1c2421ece226e2fd1f3ee
Author: Amit Dutta <amitdutta4255@gmail.com>
Date:   Mon, 15 Dec 2025 11:29:46 +0530

[2025-12-15] .\docs\template.html : Introduced a whole new interactive design.

Diffstat:
Mdocs/index.html | 354++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mdocs/template.html | 356++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 613 insertions(+), 97 deletions(-)

diff --git a/docs/index.html b/docs/index.html @@ -7,14 +7,16 @@ <link rel="icon" href="data:,"> <title>notamitgamer/bsc Repository File Index</title> <script src="https://cdn.tailwindcss.com"></script> + <!-- Fonts --> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> + + <!-- Default Theme (Visual Studio Light for High Contrast) --> <link - href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" - rel="stylesheet" - /> - <!-- Default Theme (Dark) --> - <link + id="highlight-theme" rel="stylesheet" - href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" + href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs.min.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> @@ -41,18 +43,20 @@ padding: 0; margin: 0; height: 100%; - overflow: hidden; + overflow: auto; } /* Code Block */ #modal-code-block { - height: 100%; - overflow: auto; + min-height: 100%; + overflow: visible; padding: 1rem; font-family: "JetBrains Mono", "Fira Code", monospace; font-size: 0.8125rem; line-height: 1.6; - background-color: transparent !important; /* Allow container bg */ + background-color: transparent !important; + display: block; + color: #000000; /* Ensure high contrast text */ } @media (min-width: 640px) { @@ -63,27 +67,31 @@ } /* Scrollbars */ - #modal-code-block::-webkit-scrollbar { + #modal-code-container::-webkit-scrollbar, + .custom-scrollbar::-webkit-scrollbar { width: 10px; height: 10px; } - #modal-code-block::-webkit-scrollbar-track { - background: transparent; + #modal-code-container::-webkit-scrollbar-track, + .custom-scrollbar::-webkit-scrollbar-track { + background: #f1f5f9; } - #modal-code-block::-webkit-scrollbar-thumb { - background: #30363d; + #modal-code-container::-webkit-scrollbar-thumb, + .custom-scrollbar::-webkit-scrollbar-thumb { + background: #cbd5e1; border-radius: 5px; - border: 2px solid transparent; + border: 2px solid #f1f5f9; background-clip: content-box; } - #modal-code-block::-webkit-scrollbar-thumb:hover { - background-color: #58a6ff; + #modal-code-container::-webkit-scrollbar-thumb:hover, + .custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: #94a3b8; } .hljs { background: transparent !important; } - + /* License Modal Scrollbar */ #license-content::-webkit-scrollbar { width: 8px; @@ -104,12 +112,50 @@ #app-content { display: none; } + + /* Back to Top Button */ + #back-to-top { + transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; + } + #back-to-top.hidden { + opacity: 0; + pointer-events: none; + transform: translateY(20px); + } + + /* Keyboard Navigation Highlight */ + .file-item.nav-selected { + background-color: #eff6ff; /* blue-50 */ + border-color: #bfdbfe; /* blue-200 */ + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + } + .file-item.nav-selected span { + color: #2563eb; /* blue-600 */ + } + + /* Ensure modal background is white in light mode */ + #code-modal .modal-content-area, + #code-modal .bg-\[\#0d1117\] { + background-color: #ffffff; + border-color: #e2e8f0; + color: #1e293b; + } + #code-modal .bg-\[\#161b22\] { + background-color: #f8fafc; + border-color: #e2e8f0; + } + #modal-filename { + color: #334155; + } + #code-loading { + background-color: #ffffff; + } </style> </head> <body class="bg-slate-50 text-slate-900 h-screen flex flex-col overflow-hidden"> <!-- App Wrapper (Hidden until license accepted) --> - <div id="app-content" class="flex flex-col h-full overflow-hidden"> + <div id="app-content" class="flex flex-col h-full overflow-hidden relative"> <!-- Header --> <header class="bg-white border-b border-slate-200 px-4 sm:px-6 py-3 sm:py-4 flex flex-col sm:flex-row items-start sm:items-center justify-between shrink-0 shadow-sm z-10 gap-4 sm:gap-0" @@ -141,17 +187,6 @@ </p> </div> </div> - <!-- Mobile: Github Icon --> - <a href="https://github.com/notamitgamer/bsc" target="_blank" class="sm:hidden text-slate-600 hover:text-slate-900"> - <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> - <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-2.126 1.029-2.935-.103-.253-.446-1.372.097-2.897 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.525.202 2.644.1 2.897.64.809 1.026 1.841 1.026 2.935 0 3.847-2.337 4.687-4.565 4.935.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path> - </svg> - </a> - </div> - - <!-- Actions Section --> - <div class="flex items-center gap-3 w-full sm:w-auto flex-wrap sm:flex-nowrap"> - <!-- License Button --> <button onclick="openLicenseModal(false)" @@ -163,6 +198,16 @@ </svg> <span class="inline hidden sm:inline">License</span> </button> + <!-- Mobile: Github Icon --> + <a href="https://github.com/notamitgamer/bsc" target="_blank" class="sm:hidden text-slate-600 hover:text-slate-900"> + <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> + <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-2.126 1.029-2.935-.103-.253-.446-1.372.097-2.897 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.525.202 2.644.1 2.897.64.809 1.026 1.841 1.026 2.935 0 3.847-2.337 4.687-4.565 4.935.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path> + </svg> + </a> + </div> + + <!-- Actions Section --> + <div class="flex items-center gap-3 w-full sm:w-auto flex-wrap sm:flex-nowrap"> <!-- Search Bar --> <div class="relative group grow sm:grow-0 w-full sm:w-auto"> @@ -174,7 +219,7 @@ <input type="text" id="search-input" - placeholder="Search code..." + placeholder="Search code... (/)" class="pl-10 pr-10 sm:pr-4 py-2 bg-slate-100 border-none rounded-lg text-sm w-full sm:w-64 focus:ring-2 focus:ring-blue-500/20 focus:bg-white transition-all outline-none text-slate-700 placeholder:text-slate-400 font-medium" /> <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none hidden sm:flex"> @@ -197,14 +242,17 @@ </header> <!-- Main Content Area --> - <main class="flex-1 flex overflow-hidden"> + <main class="flex-1 flex overflow-hidden relative" id="main-scroll-container"> <!-- File Explorer (Left Panel) --> - <div class="flex-1 overflow-y-auto custom-scrollbar p-4 sm:p-6"> + <div class="flex-1 overflow-y-auto custom-scrollbar p-4 sm:p-6 h-full" id="file-explorer-scroll"> <div class="max-w-4xl mx-auto space-y-4"> <!-- Recent Files Section --> <div id="recent-files-container" class="hidden mb-6"> - <h2 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3 px-1">Recent Files</h2> + <div class="flex items-center justify-between mb-3 px-1"> + <h2 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Recent Files</h2> + <button onclick="confirmClearRecent()" class="text-xs text-red-400 hover:text-red-600 font-medium transition-colors hover:underline">Clear</button> + </div> <div id="recent-files-list" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2"> <!-- Recent files injected here --> </div> @@ -213,9 +261,9 @@ <!-- Global Controls (Sticky) --> <div class="sticky top-0 z-20 bg-slate-50 py-2 flex justify-end gap-2 border-b border-slate-200/50 backdrop-blur-sm bg-opacity-90 mb-2"> - <button id="btn-expand" onclick="expandAll()" class="text-xs font-medium px-2 py-1 rounded transition-colors duration-200">Expand All</button> + <button id="btn-expand" onclick="expandAll()" class="text-xs font-bold text-slate-400 px-3 py-1.5 rounded-md cursor-not-allowed bg-slate-50 border border-transparent" disabled>Expand All</button> <span class="text-slate-300">|</span> - <button id="btn-collapse" onclick="collapseAll()" class="text-xs font-medium px-2 py-1 rounded transition-colors duration-200">Collapse All</button> + <button id="btn-collapse" onclick="collapseAll()" class="text-xs font-bold text-slate-400 px-3 py-1.5 rounded-md cursor-not-allowed bg-slate-50 border border-transparent" disabled>Collapse All</button> </div> <div id="file-list-container"> @@ -20283,6 +20331,14 @@ int sumEven(int arr[], int n) </div> </div> </div> + + <!-- Back to Top Button --> + <button id="back-to-top" onclick="scrollToTop()" class="hidden absolute bottom-6 right-6 p-3 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition-all z-30"> + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path> + </svg> + </button> + </main> </div> @@ -20317,6 +20373,17 @@ int sumEven(int arr[], int n) </div> <div class="flex items-center gap-1 sm:gap-2 shrink-0"> + <!-- Open in New Tab Button --> + <button + id="new-tab-btn" + class="p-2 text-slate-400 hover:text-white hover:bg-slate-700/50 rounded-lg transition-all group relative" + title="Open in New Tab" + > + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> + </svg> + </button> + <!-- Download Button --> <button id="download-btn" @@ -20328,6 +20395,18 @@ int sumEven(int arr[], int n) </svg> </button> + <!-- Print Button --> + <button + id="print-btn" + onclick="printCode()" + class="p-2 text-slate-400 hover:text-white hover:bg-slate-700/50 rounded-lg transition-all group relative" + title="Print Code" + > + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" /> + </svg> + </button> + <!-- Share Button --> <button id="share-btn" @@ -20447,7 +20526,7 @@ int sumEven(int arr[], int n) </div> <!-- Code Content --> - <div class="flex-1 min-h-0 bg-[#0d1117] relative"> + <div class="flex-1 min-h-0 bg-[#0d1117] relative dark-modal-bg"> <!-- Loading State --> <div id="code-loading" @@ -20584,6 +20663,21 @@ int sumEven(int arr[], int n) </div> </div> </div> + + <!-- Confirm Dialog Modal --> + <div + id="confirm-modal" + class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center z-[70] p-4 sm:p-6 opacity-0 transition-opacity duration-200" + > + <div class="bg-white w-full max-w-sm rounded-xl shadow-2xl flex flex-col border border-slate-200 transform scale-95 transition-transform duration-200 p-6"> + <h3 class="text-lg font-bold text-slate-800 mb-2">Clear Recent Files?</h3> + <p class="text-sm text-slate-600 mb-6">Are you sure you want to delete your recent file history? This action cannot be undone.</p> + <div class="flex justify-end gap-3"> + <button onclick="closeConfirmModal()" class="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-sm font-medium rounded-lg transition-colors">Cancel</button> + <button onclick="confirmClearRecentAction()" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-lg transition-colors shadow-sm">Delete</button> + </div> + </div> + </div> <script> function toggleFolder(header) { @@ -20642,12 +20736,20 @@ int sumEven(int arr[], int n) setBtnState(btnCollapse, false); return; } - - // Expand All is active if at least one folder is closed - setBtnState(btnExpand, openCount < total); - - // Collapse All is active if at least one folder is open - setBtnState(btnCollapse, openCount > 0); + + if (openCount === 0) { + // All folders are collapsed + setBtnState(btnExpand, true); + setBtnState(btnCollapse, false); + } else if (openCount === total) { + // All folders are expanded + setBtnState(btnExpand, false); + setBtnState(btnCollapse, true); + } else { + // Mixed state + setBtnState(btnExpand, true); + setBtnState(btnCollapse, true); + } } function setBtnState(btn, isActive) { @@ -20687,10 +20789,40 @@ int sumEven(int arr[], int n) }); updateFolderState(); } - + /* --- Recent Files Logic --- */ const MAX_RECENT_FILES = 6; let recentFiles = JSON.parse(localStorage.getItem('bsc_recent_files') || '[]'); + + // Auto-reset check + function checkStorageExpiry() { + const now = Date.now(); + const setupTime = localStorage.getItem('bsc_setup_time'); + // 3 days for standard reset, 72 hours for theme reset (logic combined here for simplicity as requested) + // The prompt asked for theme to reset at 72 hours. + // Since 3 days == 72 hours, we can use one timer for general preferences or separate if strictly needed. + // Let's use one 'setup_time' for all expiration logic to keep it simple. + const threeDays = 3 * 24 * 60 * 60 * 1000; + + if (!setupTime) { + // First run or cleared + localStorage.setItem('bsc_setup_time', now); + } else { + if (now - parseInt(setupTime) > threeDays) { + // Expired + console.log("Storage expired. Resetting."); + localStorage.removeItem('license_accepted'); + localStorage.removeItem('bsc_recent_files'); + localStorage.setItem('bsc_setup_time', now); // Reset timer + + // Reset memory variables if needed + recentFiles = []; + } + } + } + + // Call immediately + checkStorageExpiry(); function updateRecentFilesUI() { const container = document.getElementById('recent-files-container'); @@ -20731,10 +20863,63 @@ int sumEven(int arr[], int n) localStorage.setItem('bsc_recent_files', JSON.stringify(recentFiles)); updateRecentFilesUI(); } + + /* --- Custom Confirm Modal Logic --- */ + const confirmModal = document.getElementById("confirm-modal"); + + function confirmClearRecent() { + confirmModal.classList.remove("hidden"); + void confirmModal.offsetWidth; + confirmModal.classList.remove("opacity-0"); + confirmModal.querySelector("div").classList.remove("scale-95"); + } + + function closeConfirmModal() { + confirmModal.classList.add("opacity-0"); + confirmModal.querySelector("div").classList.add("scale-95"); + setTimeout(() => { + confirmModal.classList.add("hidden"); + }, 200); + } + + function confirmClearRecentAction() { + recentFiles = []; + localStorage.setItem('bsc_recent_files', JSON.stringify(recentFiles)); + updateRecentFilesUI(); + closeConfirmModal(); + } // Init Recent Files updateRecentFilesUI(); + /* --- Scroll to Top Logic --- */ + const scrollContainer = document.getElementById('file-explorer-scroll'); + const backToTopBtn = document.getElementById('back-to-top'); + + scrollContainer.addEventListener('scroll', () => { + if (scrollContainer.scrollTop > 300) { + backToTopBtn.classList.remove('hidden'); + // Small delay to allow display:block to apply before opacity transition + setTimeout(() => { + backToTopBtn.classList.remove('opacity-0', 'translate-y-4'); + }, 10); + } else { + backToTopBtn.classList.add('opacity-0', 'translate-y-4'); + setTimeout(() => { + if(scrollContainer.scrollTop <= 300) { // Check again in case user scrolled back up quickly + backToTopBtn.classList.add('hidden'); + } + }, 300); + } + }); + + function scrollToTop() { + scrollContainer.scrollTo({ + top: 0, + behavior: 'smooth' + }); + } + /* --- Code Modal Logic --- */ const modal = document.getElementById("code-modal"); const modalCodeBlock = document.getElementById("modal-code-block"); @@ -20742,6 +20927,7 @@ int sumEven(int arr[], int n) const modalFileSize = document.getElementById("modal-filesize"); const modalRawLink = document.getElementById("modal-raw-link"); const loadingSpinner = document.getElementById("code-loading"); + const newTabBtn = document.getElementById("new-tab-btn"); let currentFileUrl = ""; @@ -20784,6 +20970,15 @@ int sumEven(int arr[], int n) const bytes = new Blob([storedContent.textContent]).size; const sizeStr = bytes > 1024 ? (bytes/1024).toFixed(2) + " KB" : bytes + " B"; modalFileSize.textContent = sizeStr; + + // Setup New Tab Button + newTabBtn.onclick = () => { + const newWindow = window.open(); + newWindow.document.write('<html><head><title>' + filename + '</title>'); + newWindow.document.write('<style>body{font-family: monospace; white-space: pre-wrap; padding: 20px; background: #f0f0f0;} </style>'); + newWindow.document.write('</head><body><pre>' + storedContent.textContent.replace(/</g, "&lt;").replace(/>/g, "&gt;") + '</pre></body></html>'); + newWindow.document.close(); + }; } else { modalCodeBlock.textContent = "Error: Code content not found in page."; @@ -20828,6 +21023,24 @@ int sumEven(int arr[], int n) document.body.removeChild(element); }); + /* --- Print Logic --- */ + function printCode() { + const content = document.getElementById('modal-code-block').innerText; + const filename = document.getElementById('modal-filename').textContent; + + const printWindow = window.open('', '', 'height=600,width=800'); + printWindow.document.write('<html><head><title>' + filename + '</title>'); + printWindow.document.write('<style>body{font-family: monospace; white-space: pre-wrap; padding: 20px;} </style>'); + printWindow.document.write('</head><body>'); + // Basic HTML escaping for print view + const safeContent = content.replace(/</g, "&lt;").replace(/>/g, "&gt;"); + printWindow.document.write('<pre>' + safeContent + '</pre>'); + printWindow.document.write('</body></html>'); + printWindow.document.close(); + printWindow.focus(); + printWindow.print(); + } + /* --- License Modal Logic (UPDATED) --- */ const licenseModal = document.getElementById("license-modal"); const licenseFooter = document.getElementById("license-footer"); @@ -20964,8 +21177,12 @@ int sumEven(int arr[], int n) document.querySelectorAll('.search-snippet').forEach(el => el.remove()); document.querySelectorAll('.file-item').forEach(el => { el.classList.remove('flex-wrap', 'pb-2'); + el.classList.remove('nav-selected'); // Remove selection style // Ensure default styling is maintained }); + + // Reset selection index on search + selectedFileIndex = -1; if (searchIndex.length === 0) buildSearchIndex(); @@ -21075,9 +21292,50 @@ int sumEven(int arr[], int n) } } }); - - // Update states after search filtering (optional behavior, kept simple) }); + + /* --- Keyboard Navigation Logic --- */ + let selectedFileIndex = -1; + + function handleKeyboardNav(e) { + // Only if modal is hidden + if (!modal.classList.contains("hidden")) return; + + const visibleFiles = Array.from(document.querySelectorAll('.file-item:not(.hidden)')); + if (visibleFiles.length === 0) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + // Move selection down, loop back to start if at end + selectedFileIndex = (selectedFileIndex + 1) % visibleFiles.length; + updateSelection(visibleFiles); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + // Move selection up, loop back to end if at start + selectedFileIndex = (selectedFileIndex - 1 + visibleFiles.length) % visibleFiles.length; + updateSelection(visibleFiles); + } else if (e.key === 'Enter' && selectedFileIndex !== -1) { + e.preventDefault(); + // Trigger click on the selected file item container (first child usually has the onclick) + // The structure is .file-item > .flex-1 (this has onclick) + const clickTarget = visibleFiles[selectedFileIndex].querySelector('.flex-1'); + if (clickTarget) clickTarget.click(); + } + } + + function updateSelection(files) { + // Remove 'nav-selected' class from all + files.forEach(el => el.classList.remove('nav-selected')); + + // Add to current + if (selectedFileIndex >= 0 && selectedFileIndex < files.length) { + const selectedEl = files[selectedFileIndex]; + selectedEl.classList.add('nav-selected'); + + // Scroll into view logic + selectedEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + } + } /* --- Share Functionality --- */ function animateSuccess(btn, defaultIcon, successIcon) { @@ -21101,7 +21359,7 @@ int sumEven(int arr[], int n) for (let el of fileElements) { const name = el.querySelector("span").textContent.trim(); if (name === sharedFile) { - const onClickAttr = el.querySelector(".flex-1").getAttribute('onclick'); + const onClickAttr = el.getAttribute('onclick'); const match = onClickAttr.match(/showCode\('([^']*)',\s*'([^']*)',\s*'([^']*)'\)/); if (match) { showCode(match[1], match[2], match[3]); diff --git a/docs/template.html b/docs/template.html @@ -7,14 +7,16 @@ <link rel="icon" href="data:,"> <title>notamitgamer/bsc Repository File Index</title> <script src="https://cdn.tailwindcss.com"></script> + <!-- Fonts --> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> + + <!-- Default Theme (Visual Studio Light for High Contrast) --> <link - href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" - rel="stylesheet" - /> - <!-- Default Theme (Dark) --> - <link + id="highlight-theme" rel="stylesheet" - href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" + href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs.min.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> @@ -41,18 +43,20 @@ padding: 0; margin: 0; height: 100%; - overflow: hidden; + overflow: auto; } /* Code Block */ #modal-code-block { - height: 100%; - overflow: auto; + min-height: 100%; + overflow: visible; padding: 1rem; font-family: "JetBrains Mono", "Fira Code", monospace; font-size: 0.8125rem; line-height: 1.6; - background-color: transparent !important; /* Allow container bg */ + background-color: transparent !important; + display: block; + color: #000000; /* Ensure high contrast text */ } @media (min-width: 640px) { @@ -63,27 +67,31 @@ } /* Scrollbars */ - #modal-code-block::-webkit-scrollbar { + #modal-code-container::-webkit-scrollbar, + .custom-scrollbar::-webkit-scrollbar { width: 10px; height: 10px; } - #modal-code-block::-webkit-scrollbar-track { - background: transparent; + #modal-code-container::-webkit-scrollbar-track, + .custom-scrollbar::-webkit-scrollbar-track { + background: #f1f5f9; } - #modal-code-block::-webkit-scrollbar-thumb { - background: #30363d; + #modal-code-container::-webkit-scrollbar-thumb, + .custom-scrollbar::-webkit-scrollbar-thumb { + background: #cbd5e1; border-radius: 5px; - border: 2px solid transparent; + border: 2px solid #f1f5f9; background-clip: content-box; } - #modal-code-block::-webkit-scrollbar-thumb:hover { - background-color: #58a6ff; + #modal-code-container::-webkit-scrollbar-thumb:hover, + .custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: #94a3b8; } .hljs { background: transparent !important; } - + /* License Modal Scrollbar */ #license-content::-webkit-scrollbar { width: 8px; @@ -104,12 +112,50 @@ #app-content { display: none; } + + /* Back to Top Button */ + #back-to-top { + transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; + } + #back-to-top.hidden { + opacity: 0; + pointer-events: none; + transform: translateY(20px); + } + + /* Keyboard Navigation Highlight */ + .file-item.nav-selected { + background-color: #eff6ff; /* blue-50 */ + border-color: #bfdbfe; /* blue-200 */ + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + } + .file-item.nav-selected span { + color: #2563eb; /* blue-600 */ + } + + /* Ensure modal background is white in light mode */ + #code-modal .modal-content-area, + #code-modal .bg-\[\#0d1117\] { + background-color: #ffffff; + border-color: #e2e8f0; + color: #1e293b; + } + #code-modal .bg-\[\#161b22\] { + background-color: #f8fafc; + border-color: #e2e8f0; + } + #modal-filename { + color: #334155; + } + #code-loading { + background-color: #ffffff; + } </style> </head> <body class="bg-slate-50 text-slate-900 h-screen flex flex-col overflow-hidden"> <!-- App Wrapper (Hidden until license accepted) --> - <div id="app-content" class="flex flex-col h-full overflow-hidden"> + <div id="app-content" class="flex flex-col h-full overflow-hidden relative"> <!-- Header --> <header class="bg-white border-b border-slate-200 px-4 sm:px-6 py-3 sm:py-4 flex flex-col sm:flex-row items-start sm:items-center justify-between shrink-0 shadow-sm z-10 gap-4 sm:gap-0" @@ -141,17 +187,6 @@ </p> </div> </div> - <!-- Mobile: Github Icon --> - <a href="https://github.com/notamitgamer/bsc" target="_blank" class="sm:hidden text-slate-600 hover:text-slate-900"> - <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> - <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-2.126 1.029-2.935-.103-.253-.446-1.372.097-2.897 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.525.202 2.644.1 2.897.64.809 1.026 1.841 1.026 2.935 0 3.847-2.337 4.687-4.565 4.935.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path> - </svg> - </a> - </div> - - <!-- Actions Section --> - <div class="flex items-center gap-3 w-full sm:w-auto flex-wrap sm:flex-nowrap"> - <!-- License Button --> <button onclick="openLicenseModal(false)" @@ -161,8 +196,18 @@ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3" /> </svg> - <span class="inline hidden sm:inline">License</span> + <span class="inline hidden sm:inline">View License</span> </button> + <!-- Mobile: Github Icon --> + <a href="https://github.com/notamitgamer/bsc" target="_blank" class="sm:hidden text-slate-600 hover:text-slate-900"> + <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"> + <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-2.126 1.029-2.935-.103-.253-.446-1.372.097-2.897 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.525.202 2.644.1 2.897.64.809 1.026 1.841 1.026 2.935 0 3.847-2.337 4.687-4.565 4.935.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path> + </svg> + </a> + </div> + + <!-- Actions Section --> + <div class="flex items-center gap-3 w-full sm:w-auto flex-wrap sm:flex-nowrap"> <!-- Search Bar --> <div class="relative group grow sm:grow-0 w-full sm:w-auto"> @@ -174,7 +219,7 @@ <input type="text" id="search-input" - placeholder="Search code..." + placeholder="Search code... (/)" class="pl-10 pr-10 sm:pr-4 py-2 bg-slate-100 border-none rounded-lg text-sm w-full sm:w-64 focus:ring-2 focus:ring-blue-500/20 focus:bg-white transition-all outline-none text-slate-700 placeholder:text-slate-400 font-medium" /> <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none hidden sm:flex"> @@ -197,14 +242,17 @@ </header> <!-- Main Content Area --> - <main class="flex-1 flex overflow-hidden"> + <main class="flex-1 flex overflow-hidden relative" id="main-scroll-container"> <!-- File Explorer (Left Panel) --> - <div class="flex-1 overflow-y-auto custom-scrollbar p-4 sm:p-6"> + <div class="flex-1 overflow-y-auto custom-scrollbar p-4 sm:p-6 h-full" id="file-explorer-scroll"> <div class="max-w-4xl mx-auto space-y-4"> <!-- Recent Files Section --> <div id="recent-files-container" class="hidden mb-6"> - <h2 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3 px-1">Recent Files</h2> + <div class="flex items-center justify-between mb-3 px-1"> + <h2 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Recent Files</h2> + <button onclick="confirmClearRecent()" class="text-xs text-red-400 hover:text-red-600 font-medium transition-colors hover:underline">Clear</button> + </div> <div id="recent-files-list" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2"> <!-- Recent files injected here --> </div> @@ -213,9 +261,9 @@ <!-- Global Controls (Sticky) --> <div class="sticky top-0 z-20 bg-slate-50 py-2 flex justify-end gap-2 border-b border-slate-200/50 backdrop-blur-sm bg-opacity-90 mb-2"> - <button id="btn-expand" onclick="expandAll()" class="text-xs font-medium px-2 py-1 rounded transition-colors duration-200">Expand All</button> + <button id="btn-expand" onclick="expandAll()" class="text-xs font-bold text-slate-400 px-3 py-1.5 rounded-md cursor-not-allowed bg-slate-50 border border-transparent" disabled>Expand All</button> <span class="text-slate-300">|</span> - <button id="btn-collapse" onclick="collapseAll()" class="text-xs font-medium px-2 py-1 rounded transition-colors duration-200">Collapse All</button> + <button id="btn-collapse" onclick="collapseAll()" class="text-xs font-bold text-slate-400 px-3 py-1.5 rounded-md cursor-not-allowed bg-slate-50 border border-transparent" disabled>Collapse All</button> </div> <div id="file-list-container"> @@ -224,6 +272,14 @@ </div> </div> </div> + + <!-- Back to Top Button --> + <button id="back-to-top" onclick="scrollToTop()" class="hidden absolute bottom-6 right-6 p-3 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition-all z-30"> + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path> + </svg> + </button> + </main> </div> @@ -258,6 +314,17 @@ </div> <div class="flex items-center gap-1 sm:gap-2 shrink-0"> + <!-- Open in New Tab Button --> + <button + id="new-tab-btn" + class="p-2 text-slate-400 hover:text-white hover:bg-slate-700/50 rounded-lg transition-all group relative" + title="Open in New Tab" + > + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> + </svg> + </button> + <!-- Download Button --> <button id="download-btn" @@ -269,6 +336,18 @@ </svg> </button> + <!-- Print Button --> + <button + id="print-btn" + onclick="printCode()" + class="p-2 text-slate-400 hover:text-white hover:bg-slate-700/50 rounded-lg transition-all group relative" + title="Print Code" + > + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" /> + </svg> + </button> + <!-- Share Button --> <button id="share-btn" @@ -388,7 +467,7 @@ </div> <!-- Code Content --> - <div class="flex-1 min-h-0 bg-[#0d1117] relative"> + <div class="flex-1 min-h-0 bg-[#0d1117] relative dark-modal-bg"> <!-- Loading State --> <div id="code-loading" @@ -525,6 +604,21 @@ </div> </div> </div> + + <!-- Confirm Dialog Modal --> + <div + id="confirm-modal" + class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm hidden flex items-center justify-center z-[70] p-4 sm:p-6 opacity-0 transition-opacity duration-200" + > + <div class="bg-white w-full max-w-sm rounded-xl shadow-2xl flex flex-col border border-slate-200 transform scale-95 transition-transform duration-200 p-6"> + <h3 class="text-lg font-bold text-slate-800 mb-2">Clear Recent Files?</h3> + <p class="text-sm text-slate-600 mb-6">Are you sure you want to delete your recent file history? This action cannot be undone.</p> + <div class="flex justify-end gap-3"> + <button onclick="closeConfirmModal()" class="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-sm font-medium rounded-lg transition-colors">Cancel</button> + <button onclick="confirmClearRecentAction()" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-lg transition-colors shadow-sm">Delete</button> + </div> + </div> + </div> <script> function toggleFolder(header) { @@ -583,12 +677,20 @@ setBtnState(btnCollapse, false); return; } - - // Expand All is active if at least one folder is closed - setBtnState(btnExpand, openCount < total); - - // Collapse All is active if at least one folder is open - setBtnState(btnCollapse, openCount > 0); + + if (openCount === 0) { + // All folders are collapsed + setBtnState(btnExpand, true); + setBtnState(btnCollapse, false); + } else if (openCount === total) { + // All folders are expanded + setBtnState(btnExpand, false); + setBtnState(btnCollapse, true); + } else { + // Mixed state + setBtnState(btnExpand, true); + setBtnState(btnCollapse, true); + } } function setBtnState(btn, isActive) { @@ -628,10 +730,40 @@ }); updateFolderState(); } - + /* --- Recent Files Logic --- */ const MAX_RECENT_FILES = 6; let recentFiles = JSON.parse(localStorage.getItem('bsc_recent_files') || '[]'); + + // Auto-reset check + function checkStorageExpiry() { + const now = Date.now(); + const setupTime = localStorage.getItem('bsc_setup_time'); + // 3 days for standard reset, 72 hours for theme reset (logic combined here for simplicity as requested) + // The prompt asked for theme to reset at 72 hours. + // Since 3 days == 72 hours, we can use one timer for general preferences or separate if strictly needed. + // Let's use one 'setup_time' for all expiration logic to keep it simple. + const threeDays = 3 * 24 * 60 * 60 * 1000; + + if (!setupTime) { + // First run or cleared + localStorage.setItem('bsc_setup_time', now); + } else { + if (now - parseInt(setupTime) > threeDays) { + // Expired + console.log("Storage expired. Resetting."); + localStorage.removeItem('license_accepted'); + localStorage.removeItem('bsc_recent_files'); + localStorage.setItem('bsc_setup_time', now); // Reset timer + + // Reset memory variables if needed + recentFiles = []; + } + } + } + + // Call immediately + checkStorageExpiry(); function updateRecentFilesUI() { const container = document.getElementById('recent-files-container'); @@ -672,10 +804,63 @@ localStorage.setItem('bsc_recent_files', JSON.stringify(recentFiles)); updateRecentFilesUI(); } + + /* --- Custom Confirm Modal Logic --- */ + const confirmModal = document.getElementById("confirm-modal"); + + function confirmClearRecent() { + confirmModal.classList.remove("hidden"); + void confirmModal.offsetWidth; + confirmModal.classList.remove("opacity-0"); + confirmModal.querySelector("div").classList.remove("scale-95"); + } + + function closeConfirmModal() { + confirmModal.classList.add("opacity-0"); + confirmModal.querySelector("div").classList.add("scale-95"); + setTimeout(() => { + confirmModal.classList.add("hidden"); + }, 200); + } + + function confirmClearRecentAction() { + recentFiles = []; + localStorage.setItem('bsc_recent_files', JSON.stringify(recentFiles)); + updateRecentFilesUI(); + closeConfirmModal(); + } // Init Recent Files updateRecentFilesUI(); + /* --- Scroll to Top Logic --- */ + const scrollContainer = document.getElementById('file-explorer-scroll'); + const backToTopBtn = document.getElementById('back-to-top'); + + scrollContainer.addEventListener('scroll', () => { + if (scrollContainer.scrollTop > 300) { + backToTopBtn.classList.remove('hidden'); + // Small delay to allow display:block to apply before opacity transition + setTimeout(() => { + backToTopBtn.classList.remove('opacity-0', 'translate-y-4'); + }, 10); + } else { + backToTopBtn.classList.add('opacity-0', 'translate-y-4'); + setTimeout(() => { + if(scrollContainer.scrollTop <= 300) { // Check again in case user scrolled back up quickly + backToTopBtn.classList.add('hidden'); + } + }, 300); + } + }); + + function scrollToTop() { + scrollContainer.scrollTo({ + top: 0, + behavior: 'smooth' + }); + } + /* --- Code Modal Logic --- */ const modal = document.getElementById("code-modal"); const modalCodeBlock = document.getElementById("modal-code-block"); @@ -683,6 +868,7 @@ const modalFileSize = document.getElementById("modal-filesize"); const modalRawLink = document.getElementById("modal-raw-link"); const loadingSpinner = document.getElementById("code-loading"); + const newTabBtn = document.getElementById("new-tab-btn"); let currentFileUrl = ""; @@ -725,6 +911,15 @@ const bytes = new Blob([storedContent.textContent]).size; const sizeStr = bytes > 1024 ? (bytes/1024).toFixed(2) + " KB" : bytes + " B"; modalFileSize.textContent = sizeStr; + + // Setup New Tab Button + newTabBtn.onclick = () => { + const newWindow = window.open(); + newWindow.document.write('<html><head><title>' + filename + '</title>'); + newWindow.document.write('<style>body{font-family: monospace; white-space: pre-wrap; padding: 20px; background: #f0f0f0;} </style>'); + newWindow.document.write('</head><body><pre>' + storedContent.textContent.replace(/</g, "&lt;").replace(/>/g, "&gt;") + '</pre></body></html>'); + newWindow.document.close(); + }; } else { modalCodeBlock.textContent = "Error: Code content not found in page."; @@ -769,6 +964,24 @@ document.body.removeChild(element); }); + /* --- Print Logic --- */ + function printCode() { + const content = document.getElementById('modal-code-block').innerText; + const filename = document.getElementById('modal-filename').textContent; + + const printWindow = window.open('', '', 'height=600,width=800'); + printWindow.document.write('<html><head><title>' + filename + '</title>'); + printWindow.document.write('<style>body{font-family: monospace; white-space: pre-wrap; padding: 20px;} </style>'); + printWindow.document.write('</head><body>'); + // Basic HTML escaping for print view + const safeContent = content.replace(/</g, "&lt;").replace(/>/g, "&gt;"); + printWindow.document.write('<pre>' + safeContent + '</pre>'); + printWindow.document.write('</body></html>'); + printWindow.document.close(); + printWindow.focus(); + printWindow.print(); + } + /* --- License Modal Logic (UPDATED) --- */ const licenseModal = document.getElementById("license-modal"); const licenseFooter = document.getElementById("license-footer"); @@ -905,8 +1118,12 @@ document.querySelectorAll('.search-snippet').forEach(el => el.remove()); document.querySelectorAll('.file-item').forEach(el => { el.classList.remove('flex-wrap', 'pb-2'); + el.classList.remove('nav-selected'); // Remove selection style // Ensure default styling is maintained }); + + // Reset selection index on search + selectedFileIndex = -1; if (searchIndex.length === 0) buildSearchIndex(); @@ -1016,9 +1233,50 @@ } } }); - - // Update states after search filtering (optional behavior, kept simple) }); + + /* --- Keyboard Navigation Logic --- */ + let selectedFileIndex = -1; + + function handleKeyboardNav(e) { + // Only if modal is hidden + if (!modal.classList.contains("hidden")) return; + + const visibleFiles = Array.from(document.querySelectorAll('.file-item:not(.hidden)')); + if (visibleFiles.length === 0) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + // Move selection down, loop back to start if at end + selectedFileIndex = (selectedFileIndex + 1) % visibleFiles.length; + updateSelection(visibleFiles); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + // Move selection up, loop back to end if at start + selectedFileIndex = (selectedFileIndex - 1 + visibleFiles.length) % visibleFiles.length; + updateSelection(visibleFiles); + } else if (e.key === 'Enter' && selectedFileIndex !== -1) { + e.preventDefault(); + // Trigger click on the selected file item container (first child usually has the onclick) + // The structure is .file-item > .flex-1 (this has onclick) + const clickTarget = visibleFiles[selectedFileIndex].querySelector('.flex-1'); + if (clickTarget) clickTarget.click(); + } + } + + function updateSelection(files) { + // Remove 'nav-selected' class from all + files.forEach(el => el.classList.remove('nav-selected')); + + // Add to current + if (selectedFileIndex >= 0 && selectedFileIndex < files.length) { + const selectedEl = files[selectedFileIndex]; + selectedEl.classList.add('nav-selected'); + + // Scroll into view logic + selectedEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + } + } /* --- Share Functionality --- */ function animateSuccess(btn, defaultIcon, successIcon) { @@ -1042,7 +1300,7 @@ for (let el of fileElements) { const name = el.querySelector("span").textContent.trim(); if (name === sharedFile) { - const onClickAttr = el.querySelector(".flex-1").getAttribute('onclick'); + const onClickAttr = el.getAttribute('onclick'); const match = onClickAttr.match(/showCode\('([^']*)',\s*'([^']*)',\s*'([^']*)'\)/); if (match) { showCode(match[1], match[2], match[3]);
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror