import './utils/map-upsert-polyfill.js'; import './utils/setup-pdf-worker.js'; import { categories } from './config/tools.js'; import { dom, switchView, hideAlert } from './ui.js'; import { ShortcutsManager } from './logic/shortcuts.js'; import { createIcons, icons } from 'lucide'; import '@phosphor-icons/web/regular'; import * as pdfjsLib from 'pdfjs-dist'; import '../css/styles.css'; import { escapeHtml, formatShortcutDisplay, formatStars, } from './utils/helpers.js'; import { initI18n, applyTranslations, rewriteLinks, injectLanguageSwitcher, t, trText, } from './i18n/index.js'; import { loadRuntimeConfig, isToolDisabled, isCurrentPageDisabled, } from './utils/disabled-tools.js'; import { getStoredItem, setStoredItem, removeStoredItem, } from './utils/safe-storage.js'; import { sanitizeTranslationHtml } from './utils/translation-html.js'; import type { ToolCategory } from '@/types'; const FAVORITE_STAR_PATHS = [ [ 'off', 'M239.18,97.26A16.38,16.38,0,0,0,224.92,86l-59-4.76L143.14,26.15a16.36,16.36,0,0,0-30.27,0L90.11,81.23,31.08,86a16.46,16.46,0,0,0-9.37,28.86l45,38.83L53,211.75a16.38,16.38,0,0,0,24.5,17.82L128,198.49l50.53,31.08A16.4,16.4,0,0,0,203,211.75l-13.76-58.07,45-38.83A16.43,16.43,0,0,0,239.18,97.26Zm-15.34,5.47-48.7,42a8,8,0,0,0-2.56,7.91l14.88,62.8a.37.37,0,0,1-.17.48c-.18.14-.23.11-.38,0l-54.72-33.65a8,8,0,0,0-8.38,0L69.09,215.94c-.15.09-.19.12-.38,0a.37.37,0,0,1-.17-.48l14.88-62.8a8,8,0,0,0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16A8,8,0,0,0,103,91.86l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153,91.86a8,8,0,0,0,6.75,4.92l63.92,5.16c.15,0,.24,0,.33.29S224,102.63,223.84,102.73Z', ], [ 'on', 'M234.29,114.85l-45,38.83L203,211.75a16.4,16.4,0,0,1-24.5,17.82L128,198.49,77.47,229.57A16.4,16.4,0,0,1,53,211.75l13.76-58.07-45-38.83A16.46,16.46,0,0,1,31.08,86l59-4.76,22.76-55.08a16.36,16.36,0,0,1,30.27,0l22.75,55.08,59,4.76a16.46,16.46,0,0,1,9.37,28.86Z', ], ] as const; declare const __BRAND_NAME__: string; const init = async () => { await initI18n(); await loadRuntimeConfig(); injectLanguageSwitcher(); applyTranslations(); if (isCurrentPageDisabled()) { document.title = t('disabledTool.title') || trText('Tool Unavailable'); const main = document.querySelector('main') || document.body; const heading = t('disabledTool.heading') || trText('This tool has been disabled'); const message = t('disabledTool.message') || trText( 'This tool is not available in your deployment. Contact your administrator for more information.' ); const backHome = t('disabledTool.backHome') || trText('Back to Home'); main.innerHTML = `

${sanitizeTranslationHtml(heading)}

${sanitizeTranslationHtml(message)}

${sanitizeTranslationHtml(backHome)}
`; return; } if (__SIMPLE_MODE__) { const hideBrandingSections = () => { const heroSection = document.getElementById('hero-section'); if (heroSection) { heroSection.style.display = 'none'; } const githubLink = document.querySelector( 'a[href*="github.com/alam00000/bentopdf"]' ); if (githubLink) { (githubLink as HTMLElement).style.display = 'none'; } const featuresSection = document.getElementById('features-section'); if (featuresSection) { featuresSection.style.display = 'none'; } const securitySection = document.getElementById( 'security-compliance-section' ); if (securitySection) { securitySection.style.display = 'none'; } const faqSection = document.getElementById('faq-accordion'); if (faqSection) { faqSection.style.display = 'none'; } const testimonialsSection = document.getElementById( 'testimonials-section' ); if (testimonialsSection) { testimonialsSection.style.display = 'none'; } const supportSection = document.getElementById('support-section'); if (supportSection) { supportSection.style.display = 'none'; } // Hide "Used by companies" section const usedBySection = document.querySelector( '.hide-section' ) as HTMLElement; if (usedBySection) { usedBySection.style.display = 'none'; } const sectionDividers = document.querySelectorAll('.section-divider'); sectionDividers.forEach((divider) => { (divider as HTMLElement).style.display = 'none'; }); const brandName = __BRAND_NAME__ || 'BentoPDF'; document.title = trText('{{brandName}} - {{title}}', { brandName, title: t('simpleMode.title'), }); const toolsHeader = document.getElementById('tools-header'); if (toolsHeader) { const title = toolsHeader.querySelector('h2'); const subtitle = toolsHeader.querySelector('p'); if (title) { title.textContent = t('simpleMode.title'); title.className = 'text-4xl md:text-5xl font-bold text-white mb-3'; } if (subtitle) { subtitle.textContent = t('simpleMode.subtitle'); subtitle.className = 'text-lg text-gray-400'; } } const app = document.getElementById('app'); if (app) { app.style.paddingTop = '1rem'; } }; hideBrandingSections(); } // Hide shortcuts buttons on mobile devices (Android/iOS) // exclude iPad -> users can connect keyboard and use shortcuts const isMobile = /Android|iPhone|iPod/i.test(navigator.userAgent); const keyboardShortcutBtn = document.getElementById('shortcut'); const shortcutSettingsBtn = document.getElementById('open-shortcuts-btn'); if (isMobile) { if (keyboardShortcutBtn) keyboardShortcutBtn.style.display = 'none'; if (shortcutSettingsBtn) shortcutSettingsBtn.style.display = 'none'; } else { if (keyboardShortcutBtn) { keyboardShortcutBtn.textContent = navigator.userAgent .toUpperCase() .includes('MAC') ? '⌘ + K' : 'Ctrl + K'; } } const categoryTranslationKeys: Record = { Favorites: 'tools:categories.favorites', 'Popular Tools': 'tools:categories.popularTools', 'Edit & Annotate': 'tools:categories.editAnnotate', 'Convert to PDF': 'tools:categories.convertToPdf', 'Convert from PDF': 'tools:categories.convertFromPdf', 'Organize & Manage': 'tools:categories.organizeManage', 'Optimize & Repair': 'tools:categories.optimizeRepair', 'Secure PDF': 'tools:categories.securePdf', }; const toolTranslationKeys: Record = { 'PDF Workflow Builder': 'tools:pdfWorkflow', 'PDF Multi Tool': 'tools:pdfMultiTool', 'Merge PDF': 'tools:mergePdf', 'Split PDF': 'tools:splitPdf', 'Compress PDF': 'tools:compressPdf', 'PDF Editor': 'tools:pdfEditor', 'Edit PDF Text': 'tools:editPdfText', 'JPG to PDF': 'tools:jpgToPdf', 'Sign PDF': 'tools:signPdf', 'Crop PDF': 'tools:cropPdf', 'Extract Pages': 'tools:extractPages', 'Duplicate & Organize': 'tools:duplicateOrganize', 'Delete Pages': 'tools:deletePages', 'Edit Bookmarks': 'tools:editBookmarks', 'Table of Contents': 'tools:tableOfContents', 'Page Numbers': 'tools:pageNumbers', 'Add Page Labels': 'tools:addPageLabels', 'Add Watermark': 'tools:addWatermark', 'Header & Footer': 'tools:headerFooter', 'Invert Colors': 'tools:invertColors', 'Background Color': 'tools:backgroundColor', 'Change Text Color': 'tools:changeTextColor', 'Add Stamps': 'tools:addStamps', 'Bates Numbering': 'tools:batesNumbering', 'Remove Annotations': 'tools:removeAnnotations', 'PDF Form Filler': 'tools:pdfFormFiller', 'Create PDF Form': 'tools:createPdfForm', 'Remove Blank Pages': 'tools:removeBlankPages', 'Images to PDF': 'tools:imageToPdf', 'PNG to PDF': 'tools:pngToPdf', 'WebP to PDF': 'tools:webpToPdf', 'SVG to PDF': 'tools:svgToPdf', 'BMP to PDF': 'tools:bmpToPdf', 'HEIC to PDF': 'tools:heicToPdf', 'TIFF to PDF': 'tools:tiffToPdf', 'Text to PDF': 'tools:textToPdf', 'JSON to PDF': 'tools:jsonToPdf', 'PDF to JPG': 'tools:pdfToJpg', 'PDF to PNG': 'tools:pdfToPng', 'PDF to WebP': 'tools:pdfToWebp', 'PDF to BMP': 'tools:pdfToBmp', 'PDF to TIFF': 'tools:pdfToTiff', 'PDF to CBZ': 'tools:pdfToCbz', 'PDF to Greyscale': 'tools:pdfToGreyscale', 'PDF to JSON': 'tools:pdfToJson', 'OCR PDF': 'tools:ocrPdf', 'Alternate & Mix Pages': 'tools:alternateMerge', 'Duplex Collate': 'tools:duplexCollate', 'PDF Overlay': 'tools:pdfOverlay', 'Organize & Duplicate': 'tools:duplicateOrganize', 'Add Attachments': 'tools:addAttachments', 'Extract Attachments': 'tools:extractAttachments', 'Edit Attachments': 'tools:editAttachments', 'Divide Pages': 'tools:dividePages', 'Add Blank Page': 'tools:addBlankPage', 'Reverse Pages': 'tools:reversePages', 'Rotate PDF': 'tools:rotatePdf', 'Rotate by Custom Degrees': 'tools:rotateCustom', 'N-Up PDF': 'tools:nUpPdf', 'Combine to Single Page': 'tools:combineToSinglePage', 'View Metadata': 'tools:viewMetadata', 'Edit Metadata': 'tools:editMetadata', 'PDFs to ZIP': 'tools:pdfsToZip', 'Compare PDFs': 'tools:comparePdfs', 'Posterize PDF': 'tools:posterizePdf', 'Fix Page Size': 'tools:fixPageSize', 'Linearize PDF': 'tools:linearizePdf', 'Page Dimensions': 'tools:pageDimensions', 'Remove Restrictions': 'tools:removeRestrictions', 'Repair PDF': 'tools:repairPdf', 'Encrypt PDF': 'tools:encryptPdf', 'Sanitize PDF': 'tools:sanitizePdf', 'Protect PDF': 'tools:protectPdf', 'Unlock PDF': 'tools:unlockPdf', 'Decrypt PDF': 'tools:decryptPdf', 'Flatten PDF': 'tools:flattenPdf', 'Remove Metadata': 'tools:removeMetadata', 'Change Permissions': 'tools:changePermissions', 'Email to PDF': 'tools:emailToPdf', 'Font to Outline': 'tools:fontToOutline', 'Deskew PDF': 'tools:deskewPdf', 'Digital Signature': 'tools:digitalSignPdf', 'Validate Signature': 'tools:validateSignaturePdf', 'Timestamp PDF': 'tools:timestampPdf', 'Scanner Effect': 'tools:scannerEffect', 'Adjust Colors': 'tools:adjustColors', 'Markdown to PDF': 'tools:markdownToPdf', 'PDF Booklet': 'tools:pdfBooklet', 'Word to PDF': 'tools:wordToPdf', 'Excel to PDF': 'tools:excelToPdf', 'PowerPoint to PDF': 'tools:powerpointToPdf', 'XPS to PDF': 'tools:xpsToPdf', 'MOBI to PDF': 'tools:mobiToPdf', 'EPUB to PDF': 'tools:epubToPdf', 'FB2 to PDF': 'tools:fb2ToPdf', 'CBZ to PDF': 'tools:cbzToPdf', 'WPD to PDF': 'tools:wpdToPdf', 'WPS to PDF': 'tools:wpsToPdf', 'XML to PDF': 'tools:xmlToPdf', 'Pages to PDF': 'tools:pagesToPdf', 'ODG to PDF': 'tools:odgToPdf', 'ODS to PDF': 'tools:odsToPdf', 'ODP to PDF': 'tools:odpToPdf', 'PUB to PDF': 'tools:pubToPdf', 'VSD to PDF': 'tools:vsdToPdf', 'PSD to PDF': 'tools:psdToPdf', 'ODT to PDF': 'tools:odtToPdf', 'CSV to PDF': 'tools:csvToPdf', 'RTF to PDF': 'tools:rtfToPdf', 'PDF to SVG': 'tools:pdfToSvg', 'PDF to CSV': 'tools:pdfToCsv', 'PDF to Excel': 'tools:pdfToExcel', 'PDF to Text': 'tools:pdfToText', 'Extract Tables': 'tools:extractTables', 'PDF to Word': 'tools:pdfToWord', 'Extract Images': 'tools:extractImages', 'PDF to Markdown': 'tools:pdfToMarkdown', 'Prepare PDF for AI': 'tools:preparePdfForAi', 'PDF OCG': 'tools:pdfOcg', 'PDF to PDF/A': 'tools:pdfToPdfa', 'Rasterize PDF': 'tools:rasterizePdf', }; // Homepage-only tool grid rendering (not used on individual tool pages) if (dom.toolGrid) { dom.toolGrid.textContent = ''; let collapsedCategories: string[] = []; try { const stored = getStoredItem('collapsedCategories'); if (stored) collapsedCategories = JSON.parse(stored); } catch { removeStoredItem('collapsedCategories'); } function saveCollapsedCategories() { setStoredItem('collapsedCategories', JSON.stringify(collapsedCategories)); } const filteredCategories = categories .map((category) => ({ ...category, tools: category.tools.filter((tool) => !isToolDisabled(tool.id)), })) .filter((category) => category.tools.length > 0); const availableTools = new Map(); filteredCategories.forEach((category) => category.tools.forEach((tool) => { if (!availableTools.has(tool.id)) availableTools.set(tool.id, tool); }) ); let favoriteToolIds: string[] = []; let unavailableFavoriteIds: string[] = []; function loadFavorites() { let stored: unknown = []; try { stored = JSON.parse(getStoredItem('favoriteTools') || '[]'); } catch { removeStoredItem('favoriteTools'); } const ids = Array.isArray(stored) ? [ ...new Set( stored.filter((id): id is string => typeof id === 'string') ), ] : []; favoriteToolIds = ids.filter((id) => availableTools.has(id)); unavailableFavoriteIds = ids.filter((id) => !availableTools.has(id)); } loadFavorites(); function createFavoriteButton(toolId: string): HTMLButtonElement { const button = document.createElement('button'); button.type = 'button'; button.className = 'tool-fav-btn'; button.dataset.favId = toolId; button.appendChild(createStarIcon()); updateFavoriteButton(button); return button; } function createStarIcon(): SVGSVGElement { const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('viewBox', '0 0 256 256'); svg.setAttribute('aria-hidden', 'true'); for (const [kind, d] of FAVORITE_STAR_PATHS) { const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); path.setAttribute('class', `tool-fav-${kind}`); path.setAttribute('d', d); svg.appendChild(path); } return svg; } const favoritesHint = document.createElement('div'); favoritesHint.className = 'tool-fav-hint col-span-full'; const favoritesHintText = document.createElement('span'); const favoritesHintLabel = document.createElement('strong'); favoritesHintLabel.className = 'tool-fav-hint-label'; favoritesHintLabel.textContent = t('tools:favorites.hintLabel'); favoritesHintText.append( favoritesHintLabel, document.createTextNode(` ${t('tools:favorites.hint')}`) ); const favoritesHintClose = document.createElement('button'); favoritesHintClose.type = 'button'; favoritesHintClose.className = 'tool-fav-hint-close'; favoritesHintClose.setAttribute( 'aria-label', t('tools:favorites.hideHint') ); const favoritesHintIcon = document.createElement('i'); favoritesHintIcon.className = 'ph ph-x'; favoritesHintIcon.setAttribute('aria-hidden', 'true'); favoritesHintClose.appendChild(favoritesHintIcon); favoritesHint.append(favoritesHintText, favoritesHintClose); favoritesHintClose.addEventListener('click', () => { setStoredItem('favoritesHintHidden', 'true'); favoritesHint.remove(); }); function updateFavoritesHint() { if ( favoriteToolIds.length > 0 || getStoredItem('favoritesHintHidden') === 'true' ) { favoritesHint.remove(); return; } if (favoritesHint.isConnected) return; const results = document.getElementById('search-results'); dom.toolGrid.insertBefore( favoritesHint, results ? results.nextSibling : dom.toolGrid.firstChild ); } const favoriteMenu = document.createElement('div'); favoriteMenu.className = 'tool-fav-menu'; const favoriteMenuButton = document.createElement('button'); favoriteMenuButton.type = 'button'; favoriteMenuButton.className = 'tool-fav-menu-btn'; const favoriteMenuLabel = document.createElement('span'); favoriteMenuButton.append(createStarIcon(), favoriteMenuLabel); favoriteMenu.appendChild(favoriteMenuButton); let favoriteMenuStar: HTMLElement | null = null; function openFavoriteMenu(tile: HTMLElement) { const star = tile.querySelector('.tool-fav-btn'); if (!star) return; favoriteMenuStar = star; const active = favoriteToolIds.includes(star.dataset.favId || ''); favoriteMenuButton.classList.toggle('is-favorite', active); favoriteMenuLabel.textContent = active ? t('tools:favorites.remove') : t('tools:favorites.add'); tile.appendChild(favoriteMenu); if (navigator.userActivation?.hasBeenActive) navigator.vibrate?.(10); } function closeFavoriteMenu() { favoriteMenu.remove(); favoriteMenuStar = null; } favoriteMenuButton.addEventListener('click', (event) => { event.stopPropagation(); const star = favoriteMenuStar; closeFavoriteMenu(); if (star) toggleFavorite(star); }); function updateFavoriteButton(button: HTMLElement) { const active = favoriteToolIds.includes(button.dataset.favId || ''); button.classList.toggle('is-favorite', active); button.setAttribute('aria-pressed', String(active)); const label = active ? t('tools:favorites.remove') : t('tools:favorites.add'); button.setAttribute('aria-label', label); button.title = label; } function renderFavorites() { const tools = favoriteToolIds.flatMap((id) => { const tool = availableTools.get(id); return tool ? [tool] : []; }); if (tools.length === 0) return; const group = createCategoryGroup({ name: 'Favorites', tools }); const searchBox = document.getElementById( 'search-bar' ) as HTMLInputElement | null; if (searchBox?.value.trim()) group.style.display = 'none'; const results = document.getElementById('search-results'); dom.toolGrid.insertBefore( group, results ? results.nextSibling : dom.toolGrid.firstChild ); createIcons({ icons }); } function toggleFavorite(button: HTMLElement) { const toolId = button.dataset.favId; const tool = toolId ? availableTools.get(toolId) : undefined; if (!toolId || !tool) return; const adding = !favoriteToolIds.includes(toolId); favoriteToolIds = adding ? [...favoriteToolIds, toolId] : favoriteToolIds.filter((id) => id !== toolId); setStoredItem( 'favoriteTools', JSON.stringify([...favoriteToolIds, ...unavailableFavoriteIds]) ); const anchorTop = button.getBoundingClientRect().top; const keyboardFocus = button.matches(':focus-visible'); const group = dom.toolGrid.querySelector( ':scope > .is-cat-favorites' ); let nextFocus: HTMLElement | null = null; if (adding && group) { group .querySelector('.category-tools') ?.appendChild(createToolTile(tool)); createIcons({ icons }); } else if (adding) { renderFavorites(); } else if (group) { const tile = group .querySelector(`.tool-fav-btn[data-fav-id="${CSS.escape(toolId)}"]`) ?.closest('.tool-tile'); if (tile?.contains(button)) { const neighbor = tile.nextElementSibling ?? tile.previousElementSibling; nextFocus = neighbor?.querySelector('.tool-fav-btn') ?? group.nextElementSibling?.querySelector( '.category-header' ) ?? null; } tile?.remove(); if (favoriteToolIds.length === 0) group.remove(); } dom.toolGrid .querySelectorAll('.tool-fav-btn') .forEach((other) => { if (other.dataset.favId === toolId) updateFavoriteButton(other); }); updateFavoritesHint(); if (button.isConnected) { const shift = button.getBoundingClientRect().top - anchorTop; if (shift) window.scrollBy({ top: shift, behavior: 'instant' }); } else if (keyboardFocus) { nextFocus?.focus({ preventScroll: true }); } } function createToolTile(tool: ToolCategory['tools'][number]) { const tile = document.createElement('div'); tile.className = 'tool-tile'; let toolCard: HTMLDivElement | HTMLAnchorElement; if (tool.href) { toolCard = document.createElement('a'); toolCard.href = tool.href; toolCard.className = 'tool-card block bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center no-underline hover:shadow-lg transition duration-200'; } else { toolCard = document.createElement('div'); toolCard.className = 'tool-card bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center hover:shadow-lg transition duration-200'; toolCard.dataset.toolId = getToolId(tool); } const icon = document.createElement('i'); icon.className = 'w-10 h-10 mb-3 text-indigo-400'; if (tool.icon.startsWith('ph-')) { icon.className = `ph ${tool.icon} text-4xl mb-3 text-indigo-400`; } else { icon.setAttribute('data-lucide', tool.icon); } const toolName = document.createElement('h3'); toolName.className = 'font-semibold text-white'; const toolKey = toolTranslationKeys[tool.name]; toolName.textContent = toolKey ? t(`${toolKey}.name`) : tool.name; toolCard.append(icon, toolName); if (tool.subtitle) { const toolSubtitle = document.createElement('p'); toolSubtitle.className = 'text-xs text-gray-400 mt-1 px-2'; toolSubtitle.textContent = toolKey ? t(`${toolKey}.subtitle`) : tool.subtitle; toolCard.appendChild(toolSubtitle); } tile.append(toolCard, createFavoriteButton(tool.id)); return tile; } function createCategoryGroup(category: ToolCategory): HTMLDivElement { const categoryGroup = document.createElement('div'); const categorySlug = category.name .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); categoryGroup.className = `category-group col-span-full is-cat-${categorySlug}`; const header = document.createElement('button'); header.className = 'category-header'; header.type = 'button'; const title = document.createElement('span'); const categoryKey = categoryTranslationKeys[category.name]; title.textContent = categoryKey ? t(categoryKey) : category.name; const chevron = document.createElement('i'); chevron.setAttribute('data-lucide', 'chevron-down'); chevron.className = 'category-chevron w-5 h-5 text-gray-400 transition-transform duration-300'; header.append(title, chevron); const toolsContainer = document.createElement('div'); toolsContainer.className = 'category-tools grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6'; const isCollapsed = collapsedCategories.includes(category.name); if (isCollapsed) { categoryGroup.classList.add('collapsed'); toolsContainer.style.maxHeight = '0px'; } toolsContainer.addEventListener('transitionend', (e) => { if ((e as TransitionEvent).propertyName !== 'max-height') return; if (!categoryGroup.classList.contains('collapsed')) { toolsContainer.style.maxHeight = 'none'; toolsContainer.style.overflow = 'visible'; } }); header.addEventListener('click', () => { const collapsed = categoryGroup.classList.toggle('collapsed'); if (collapsed) { toolsContainer.style.maxHeight = toolsContainer.scrollHeight + 'px'; toolsContainer.style.overflow = 'hidden'; requestAnimationFrame(() => { toolsContainer.style.maxHeight = '0px'; }); if (!collapsedCategories.includes(category.name)) { collapsedCategories.push(category.name); } } else { toolsContainer.style.overflow = 'hidden'; toolsContainer.style.maxHeight = toolsContainer.scrollHeight + 'px'; collapsedCategories = collapsedCategories.filter( (n) => n !== category.name ); } saveCollapsedCategories(); }); category.tools.forEach((tool) => { toolsContainer.appendChild(createToolTile(tool)); }); categoryGroup.append(header, toolsContainer); if (!isCollapsed) { toolsContainer.style.maxHeight = 'none'; toolsContainer.style.overflow = 'visible'; } return categoryGroup; } filteredCategories.forEach((category) => { dom.toolGrid.appendChild(createCategoryGroup(category)); }); const searchBar = document.getElementById('search-bar'); const searchResultsContainer = document.createElement('div'); searchResultsContainer.id = 'search-results'; searchResultsContainer.className = 'hidden grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6 col-span-full'; dom.toolGrid.insertBefore(searchResultsContainer, dom.toolGrid.firstChild); renderFavorites(); updateFavoritesHint(); searchBar.addEventListener('input', () => { // @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message const searchTerm = searchBar.value.toLowerCase().trim(); closeFavoriteMenu(); const categoryGroups = dom.toolGrid.querySelectorAll( ':scope > .category-group' ); if (!searchTerm) { searchResultsContainer.classList.add('hidden'); searchResultsContainer.innerHTML = ''; categoryGroups.forEach((group) => { (group as HTMLElement).style.display = ''; const toolCards = group.querySelectorAll('.tool-card'); toolCards.forEach((card) => { (card as HTMLElement).style.display = ''; }); }); return; } categoryGroups.forEach((group) => { (group as HTMLElement).style.display = 'none'; }); searchResultsContainer.innerHTML = ''; searchResultsContainer.classList.remove('hidden'); const seenToolIds = new Set(); const allTools: HTMLElement[] = []; categoryGroups.forEach((group) => { const toolCards = Array.from(group.querySelectorAll('.tool-card')); toolCards.forEach((card) => { const toolName = ( card.querySelector('h3')?.textContent || '' ).toLowerCase(); const toolSubtitle = ( card.querySelector('p')?.textContent || '' ).toLowerCase(); const toolHref = (card as HTMLAnchorElement).href || (card as HTMLElement).dataset.toolId || ''; const toolId = toolHref.split('/').pop()?.replace('.html', '') || toolName; const isMatch = toolName.includes(searchTerm) || toolSubtitle.includes(searchTerm); const isDuplicate = seenToolIds.has(toolId); if (isMatch && !isDuplicate) { seenToolIds.add(toolId); allTools.push( (card.closest('.tool-tile') ?? card).cloneNode( true ) as HTMLElement ); } }); }); allTools.forEach((tool) => { searchResultsContainer.appendChild(tool); }); createIcons({ icons }); }); window.addEventListener('keydown', function (e) { const key = e.key.toLowerCase(); const isMac = navigator.userAgent.toUpperCase().includes('MAC'); const isCtrlK = e.ctrlKey && key === 'k'; const isCmdK = isMac && e.metaKey && key === 'k'; if (isCtrlK || isCmdK) { e.preventDefault(); searchBar.focus(); } }); dom.toolGrid.addEventListener('click', (event) => { // All tools now use href and navigate directly - no modal handling needed const favoriteButton = (event.target as Element).closest( '.tool-fav-btn' ); if (favoriteButton) toggleFavorite(favoriteButton); }); let pressTimer = 0; let pressStart: { x: number; y: number } | null = null; let suppressClick = false; const touchOnly = window.matchMedia('(hover: none)'); function cancelPress() { window.clearTimeout(pressTimer); pressStart = null; } function pressedTile(target: EventTarget | null): HTMLElement | null { const element = target instanceof Element ? target : null; if (!element || element.closest('.tool-fav-btn, .tool-fav-menu')) { return null; } return element.closest('.tool-tile'); } dom.toolGrid.addEventListener('pointerdown', (event) => { suppressClick = false; if (event.pointerType !== 'touch' || !touchOnly.matches) return; const tile = pressedTile(event.target); if (!tile) return; pressStart = { x: event.clientX, y: event.clientY }; pressTimer = window.setTimeout(() => { pressStart = null; suppressClick = true; openFavoriteMenu(tile); }, 500); }); dom.toolGrid.addEventListener('pointermove', (event) => { if ( pressStart && Math.hypot(event.clientX - pressStart.x, event.clientY - pressStart.y) > 10 ) { cancelPress(); } }); dom.toolGrid.addEventListener('pointerup', cancelPress); dom.toolGrid.addEventListener('pointercancel', cancelPress); dom.toolGrid.addEventListener('contextmenu', (event) => { if (!touchOnly.matches) return; const tile = pressedTile(event.target); if (!tile) return; event.preventDefault(); cancelPress(); if (!tile.contains(favoriteMenu)) { suppressClick = true; openFavoriteMenu(tile); } }); dom.toolGrid.addEventListener( 'click', (event) => { if (!suppressClick) return; suppressClick = false; event.preventDefault(); event.stopPropagation(); }, true ); document.addEventListener('pointerdown', (event) => { if ( favoriteMenu.isConnected && !(event.target instanceof Node && favoriteMenu.contains(event.target)) ) { closeFavoriteMenu(); } }); window.addEventListener('scroll', closeFavoriteMenu, { passive: true }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') closeFavoriteMenu(); }); window.addEventListener('storage', (event) => { if ( event.key !== null && event.key !== 'favoriteTools' && event.key !== 'favoritesHintHidden' ) { return; } closeFavoriteMenu(); loadFavorites(); dom.toolGrid.querySelector(':scope > .is-cat-favorites')?.remove(); renderFavorites(); dom.toolGrid .querySelectorAll('.tool-fav-btn') .forEach(updateFavoriteButton); updateFavoritesHint(); }); } if (dom.backToGridBtn) { dom.backToGridBtn.addEventListener('click', () => switchView('grid')); } if (dom.alertOkBtn) { dom.alertOkBtn.addEventListener('click', hideAlert); } const faqAccordion = document.getElementById('faq-accordion'); if (faqAccordion) { faqAccordion.addEventListener('click', (e) => { // @ts-expect-error TS(2339) FIXME: Property 'closest' does not exist on type 'EventTa... Remove this comment to see the full error message const questionButton = e.target.closest('.faq-question'); if (!questionButton) return; const faqItem = questionButton.parentElement; const answer = faqItem.querySelector('.faq-answer'); faqItem.classList.toggle('open'); if (faqItem.classList.contains('open')) { answer.style.maxHeight = answer.scrollHeight + 'px'; } else { answer.style.maxHeight = '0px'; } }); } const faqDetails = document.querySelectorAll('details.faq-d'); if ( faqDetails.length > 0 && !window.matchMedia('(prefers-reduced-motion: reduce)').matches ) { faqDetails.forEach((detail) => { const summary = detail.querySelector('summary'); const body = detail.querySelector('.faq-d-a'); if (!summary || !body) return; let animation: Animation | null = null; summary.addEventListener('click', (event) => { event.preventDefault(); animation?.cancel(); const wasOpen = detail.open; if (!wasOpen) detail.open = true; const fullHeight = `${body.scrollHeight}px`; const fullPadding = window.getComputedStyle(body).paddingBottom; const collapsed = { height: '0px', paddingBottom: '0px', opacity: 0 }; const expanded = { height: fullHeight, paddingBottom: fullPadding, opacity: 1, }; body.style.overflow = 'hidden'; animation = body.animate( wasOpen ? [expanded, collapsed] : [collapsed, expanded], { duration: 280, easing: 'cubic-bezier(0.2, 0.7, 0.2, 1)' } ); animation.onfinish = () => { if (wasOpen) detail.open = false; body.style.overflow = ''; animation = null; }; }); }); } createIcons({ icons }); console.log('Please share our tool and share the love!'); const githubStarsElements = [ document.getElementById('github-stars-desktop'), document.getElementById('github-stars-mobile'), ]; if ( githubStarsElements.some((el) => el) && !__SIMPLE_MODE__ && !__DISABLE_GITHUB_STARS__ ) { fetch('https://api.github.com/repos/alam00000/bentopdf') .then((response) => response.json()) .then((data) => { if (data.stargazers_count !== undefined) { const formattedStars = formatStars(data.stargazers_count); githubStarsElements.forEach((el) => { if (el) el.textContent = formattedStars; }); } }) .catch(() => { githubStarsElements.forEach((el) => { if (el) el.textContent = '-'; }); }); } // Initialize Shortcuts System ShortcutsManager.init(); // Tab switching for settings modal const shortcutsTabBtn = document.getElementById('shortcuts-tab-btn'); const preferencesTabBtn = document.getElementById('preferences-tab-btn'); const shortcutsTabContent = document.getElementById('shortcuts-tab-content'); const preferencesTabContent = document.getElementById( 'preferences-tab-content' ); const shortcutsTabFooter = document.getElementById('shortcuts-tab-footer'); const preferencesTabFooter = document.getElementById( 'preferences-tab-footer' ); const resetShortcutsBtn = document.getElementById('reset-shortcuts-btn'); if (shortcutsTabBtn && preferencesTabBtn) { shortcutsTabBtn.addEventListener('click', () => { shortcutsTabBtn.classList.add('bg-indigo-600', 'text-white'); shortcutsTabBtn.classList.remove('text-gray-300'); preferencesTabBtn.classList.remove('bg-indigo-600', 'text-white'); preferencesTabBtn.classList.add('text-gray-300'); shortcutsTabContent?.classList.remove('hidden'); preferencesTabContent?.classList.add('hidden'); shortcutsTabFooter?.classList.remove('hidden'); preferencesTabFooter?.classList.add('hidden'); resetShortcutsBtn?.classList.remove('hidden'); }); preferencesTabBtn.addEventListener('click', () => { preferencesTabBtn.classList.add('bg-indigo-600', 'text-white'); preferencesTabBtn.classList.remove('text-gray-300'); shortcutsTabBtn.classList.remove('bg-indigo-600', 'text-white'); shortcutsTabBtn.classList.add('text-gray-300'); preferencesTabContent?.classList.remove('hidden'); shortcutsTabContent?.classList.add('hidden'); preferencesTabFooter?.classList.remove('hidden'); shortcutsTabFooter?.classList.add('hidden'); resetShortcutsBtn?.classList.add('hidden'); }); } // Full-width toggle functionality const fullWidthToggle = document.getElementById( 'full-width-toggle' ) as HTMLInputElement; const toolInterface = document.getElementById('tool-interface'); const savedFullWidth = getStoredItem('fullWidthMode') !== 'false'; if (fullWidthToggle) { fullWidthToggle.checked = savedFullWidth; applyFullWidthMode(savedFullWidth); } function applyFullWidthMode(enabled: boolean) { if (toolInterface) { if (enabled) { toolInterface.classList.remove('max-w-4xl'); } else { toolInterface.classList.add('max-w-4xl'); } } // Apply to all page uploaders const pageUploaders = document.querySelectorAll( '#tool-uploader, #signature-editor' ); pageUploaders.forEach((uploader) => { if (enabled) { uploader.classList.remove('max-w-2xl', 'max-w-5xl'); } else { // Restore original max-width (most are max-w-2xl, add-stamps is max-w-5xl) if ( !uploader.classList.contains('max-w-2xl') && !uploader.classList.contains('max-w-5xl') ) { uploader.classList.add('max-w-2xl'); } } }); } if (fullWidthToggle) { fullWidthToggle.addEventListener('change', (e) => { const enabled = (e.target as HTMLInputElement).checked; setStoredItem('fullWidthMode', enabled.toString()); applyFullWidthMode(enabled); }); } const compactModeToggle = document.getElementById( 'compact-mode-toggle' ) as HTMLInputElement; const savedCompactMode = getStoredItem('compactMode') === 'true'; if (compactModeToggle) { compactModeToggle.checked = savedCompactMode; } applyCompactMode(savedCompactMode); function applyCompactMode(enabled: boolean) { if (dom.toolGrid) { dom.toolGrid.classList.toggle('compact-mode', enabled); dom.toolGrid .querySelectorAll('.category-group:not(.collapsed) .category-tools') .forEach((container) => { (container as HTMLElement).style.maxHeight = 'none'; }); } } if (compactModeToggle) { compactModeToggle.addEventListener('change', (e) => { const enabled = (e.target as HTMLInputElement).checked; setStoredItem('compactMode', enabled.toString()); applyCompactMode(enabled); }); } // Shortcuts UI Handlers if (dom.openShortcutsBtn) { dom.openShortcutsBtn.addEventListener('click', () => { renderShortcutsList(); dom.shortcutsModal.classList.remove('hidden'); }); } if (dom.closeShortcutsModalBtn) { dom.closeShortcutsModalBtn.addEventListener('click', () => { dom.shortcutsModal.classList.add('hidden'); }); } // Close modal on outside click if (dom.shortcutsModal) { dom.shortcutsModal.addEventListener('click', (e) => { if (e.target === dom.shortcutsModal) { dom.shortcutsModal.classList.add('hidden'); } }); } if (dom.resetShortcutsBtn) { dom.resetShortcutsBtn.addEventListener('click', async () => { const confirmed = await showWarningModal( t('settings.warnings.resetTitle'), t('settings.warnings.resetMessage'), true ); if (confirmed) { ShortcutsManager.reset(); renderShortcutsList(); } }); } if (dom.exportShortcutsBtn) { dom.exportShortcutsBtn.addEventListener('click', () => { ShortcutsManager.exportSettings(); }); } if (dom.importShortcutsBtn) { dom.importShortcutsBtn.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file) { const reader = new FileReader(); reader.onload = async (e) => { const content = e.target?.result as string; if (ShortcutsManager.importSettings(content)) { renderShortcutsList(); await showWarningModal( t('settings.warnings.importSuccessTitle'), t('settings.warnings.importSuccessMessage'), false ); } else { await showWarningModal( t('settings.warnings.importFailTitle'), t('settings.warnings.importFailMessage'), false ); } }; reader.readAsText(file); } }; input.click(); }); } if (dom.shortcutSearch) { dom.shortcutSearch.addEventListener('input', (e) => { const term = (e.target as HTMLInputElement).value.toLowerCase(); const sections = dom.shortcutsList.querySelectorAll('.category-section'); sections.forEach((section) => { const items = section.querySelectorAll('.shortcut-item'); let visibleCount = 0; items.forEach((item) => { const text = item.textContent?.toLowerCase() || ''; if (text.includes(term)) { item.classList.remove('hidden'); visibleCount++; } else { item.classList.add('hidden'); } }); if (visibleCount === 0) { section.classList.add('hidden'); } else { section.classList.remove('hidden'); } }); }); } // Reserved shortcuts that commonly conflict with browser/OS functions const RESERVED_SHORTCUTS: Record = { 'mod+w': { mac: trText('Closes tab'), windows: trText('Closes tab') }, 'mod+t': { mac: trText('Opens new tab'), windows: trText('Opens new tab'), }, 'mod+n': { mac: trText('Opens new window'), windows: trText('Opens new window'), }, 'mod+shift+n': { mac: trText('Opens incognito window'), windows: trText('Opens incognito window'), }, 'mod+q': { mac: trText('Quits application (cannot be overridden)') }, 'mod+m': { mac: trText('Minimizes window') }, 'mod+h': { mac: trText('Hides window') }, 'mod+r': { mac: trText('Reloads page'), windows: trText('Reloads page') }, 'mod+shift+r': { mac: trText('Hard reloads page'), windows: trText('Hard reloads page'), }, 'mod+l': { mac: trText('Focuses address bar'), windows: trText('Focuses address bar'), }, 'mod+d': { mac: trText('Bookmarks page'), windows: trText('Bookmarks page'), }, 'mod+shift+t': { mac: trText('Reopens closed tab'), windows: trText('Reopens closed tab'), }, 'mod+shift+w': { mac: trText('Closes window'), windows: trText('Closes window'), }, 'mod+tab': { mac: trText('Switches tabs'), windows: trText('Switches apps'), }, 'alt+f4': { windows: trText('Closes window') }, 'ctrl+tab': { mac: trText('Switches tabs'), windows: trText('Switches tabs'), }, }; function getReservedShortcutWarning( combo: string, isMac: boolean ): string | null { const reserved = RESERVED_SHORTCUTS[combo]; if (!reserved) return null; const description = isMac ? reserved.mac : reserved.windows; if (!description) return null; return description; } function showWarningModal( title: string, message: string, confirmMode: boolean = true ): Promise { return new Promise((resolve) => { if ( !dom.warningModal || !dom.warningTitle || !dom.warningMessage || !dom.warningCancelBtn || !dom.warningConfirmBtn ) { resolve(confirmMode ? confirm(message) : (alert(message), true)); return; } dom.warningTitle.textContent = title; dom.warningMessage.innerHTML = sanitizeTranslationHtml(message); dom.warningModal.classList.remove('hidden'); dom.warningModal.classList.add('flex'); if (confirmMode) { dom.warningCancelBtn.style.display = ''; dom.warningConfirmBtn.textContent = t('warning.proceed'); } else { dom.warningCancelBtn.style.display = 'none'; dom.warningConfirmBtn.textContent = t('alert.ok'); } const handleConfirm = () => { cleanup(); resolve(true); }; const handleCancel = () => { cleanup(); resolve(false); }; const cleanup = () => { dom.warningModal?.classList.add('hidden'); dom.warningModal?.classList.remove('flex'); dom.warningConfirmBtn?.removeEventListener('click', handleConfirm); dom.warningCancelBtn?.removeEventListener('click', handleCancel); }; dom.warningConfirmBtn.addEventListener('click', handleConfirm); dom.warningCancelBtn.addEventListener('click', handleCancel); // Close on backdrop click dom.warningModal.addEventListener( 'click', (e) => { if (e.target === dom.warningModal) { if (confirmMode) { handleCancel(); } else { handleConfirm(); } } }, { once: true } ); }); } function getToolId(tool: { id?: string; href?: string }): string { if (tool.id) return tool.id; if (tool.href) { const match = tool.href.match(/\/([^/]+)\.html$/); return match ? match[1] : tool.href; } return 'unknown'; } function renderShortcutsList() { if (!dom.shortcutsList) return; dom.shortcutsList.innerHTML = ''; const allShortcuts = ShortcutsManager.getAllShortcuts(); const isMac = navigator.userAgent.toUpperCase().includes('MAC'); const shortcutCategories = categories .map((category) => ({ ...category, tools: category.tools.filter((tool) => !isToolDisabled(tool.id)), })) .filter((category) => category.tools.length > 0); const allTools = shortcutCategories.flatMap((c) => c.tools); shortcutCategories.forEach((category) => { const section = document.createElement('div'); section.className = 'category-section mb-6 last:mb-0'; const header = document.createElement('h3'); header.className = 'text-gray-400 text-xs font-bold uppercase tracking-wider mb-3 pl-1'; const categoryKey = categoryTranslationKeys[category.name]; header.textContent = categoryKey ? t(categoryKey) : category.name; section.appendChild(header); const itemsContainer = document.createElement('div'); itemsContainer.className = 'space-y-2'; section.appendChild(itemsContainer); let hasTools = false; category.tools.forEach((tool) => { hasTools = true; const toolId = getToolId(tool); const currentShortcut = allShortcuts.get(toolId) || ''; const item = document.createElement('div'); item.className = 'shortcut-item flex items-center justify-between p-3 bg-gray-900 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors'; const left = document.createElement('div'); left.className = 'flex items-center gap-3'; const icon = document.createElement('i'); if (tool.icon.startsWith('ph-')) { icon.className = `ph ${tool.icon} w-5 h-5 text-indigo-400`; } else { icon.className = 'w-5 h-5 text-indigo-400'; icon.setAttribute('data-lucide', tool.icon); } const name = document.createElement('span'); name.className = 'text-gray-200 font-medium'; const toolKey = toolTranslationKeys[tool.name]; name.textContent = toolKey ? t(`${toolKey}.name`) : tool.name; left.append(icon, name); const right = document.createElement('div'); right.className = 'relative'; const input = document.createElement('input'); input.type = 'text'; input.className = 'shortcut-input w-32 bg-gray-800 border border-gray-600 text-white text-center text-sm rounded px-2 py-1 focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none transition-all'; input.placeholder = t('settings.clickToSet'); input.value = formatShortcutDisplay(currentShortcut, isMac); input.readOnly = true; const clearBtn = document.createElement('button'); clearBtn.className = 'absolute -right-2 -top-2 bg-gray-700 hover:bg-red-600 text-white rounded-full p-0.5 hidden group-hover:block shadow-sm'; clearBtn.innerHTML = ''; if (currentShortcut) { right.classList.add('group'); } clearBtn.onclick = (e) => { e.stopPropagation(); ShortcutsManager.setShortcut(toolId, ''); renderShortcutsList(); }; input.onkeydown = async (e) => { e.preventDefault(); e.stopPropagation(); if (e.key === 'Backspace' || e.key === 'Delete') { ShortcutsManager.setShortcut(toolId, ''); renderShortcutsList(); return; } const keys: string[] = []; // On Mac: metaKey = Command, ctrlKey = Control // On Windows/Linux: metaKey is rare, ctrlKey = Ctrl if (isMac) { if (e.metaKey) keys.push('mod'); // Command on Mac if (e.ctrlKey) keys.push('ctrl'); // Control on Mac (separate from Command) } else { if (e.ctrlKey || e.metaKey) keys.push('mod'); // Ctrl on Windows/Linux } if (e.altKey) keys.push('alt'); if (e.shiftKey) keys.push('shift'); let key = e.key.toLowerCase(); if (e.altKey && e.code) { if (e.code.startsWith('Key')) { key = e.code.slice(3).toLowerCase(); } else if (e.code.startsWith('Digit')) { key = e.code.slice(5); } } const isModifier = ['control', 'shift', 'alt', 'meta'].includes(key); const isDeadKey = key === 'dead' || key.startsWith('dead'); // Ignore dead keys (used for accented characters on Mac with Option key) if (isDeadKey) { input.value = formatShortcutDisplay( ShortcutsManager.getShortcut(toolId) || '', isMac ); return; } if (!isModifier) { keys.push(key); } const combo = keys.join('+'); input.value = formatShortcutDisplay(combo, isMac); if (!isModifier) { const existingToolId = ShortcutsManager.findToolByShortcut(combo); if (existingToolId && existingToolId !== toolId) { const existingTool = allTools.find( (t) => getToolId(t) === existingToolId ); const existingToolName = existingTool?.name || existingToolId; const displayCombo = formatShortcutDisplay(combo, isMac); const existingToolKey = existingTool ? toolTranslationKeys[existingTool.name] : null; const translatedToolName = existingToolKey ? t(`${existingToolKey}.name`) : existingToolName; await showWarningModal( t('settings.warnings.alreadyInUse'), `${escapeHtml(displayCombo)} ${t('settings.warnings.assignedTo')}

` + `"${escapeHtml(translatedToolName)}"

` + t('settings.warnings.chooseDifferent'), false ); input.value = formatShortcutDisplay( ShortcutsManager.getShortcut(toolId) || '', isMac ); input.classList.remove('border-indigo-500', 'text-indigo-400'); input.blur(); return; } const reservedWarning = getReservedShortcutWarning(combo, isMac); if (reservedWarning) { const displayCombo = formatShortcutDisplay(combo, isMac); const shouldProceed = await showWarningModal( t('settings.warnings.reserved'), `${escapeHtml(displayCombo)} ${t('settings.warnings.commonlyUsed')}

` + `"${escapeHtml(reservedWarning)}"

` + `${t('settings.warnings.unreliable')}

` + t('settings.warnings.useAnyway') ); if (!shouldProceed) { // Revert display input.value = formatShortcutDisplay( ShortcutsManager.getShortcut(toolId) || '', isMac ); input.classList.remove('border-indigo-500', 'text-indigo-400'); input.blur(); return; } } ShortcutsManager.setShortcut(toolId, combo); // Re-render to update all inputs (show conflicts in real-time) renderShortcutsList(); } }; input.onkeyup = (e) => { // If the user releases a modifier without pressing a main key, revert to saved const key = e.key.toLowerCase(); if (['control', 'shift', 'alt', 'meta'].includes(key)) { const currentSaved = ShortcutsManager.getShortcut(toolId); } }; input.onfocus = () => { input.value = t('settings.pressKeys'); input.classList.add('border-indigo-500', 'text-indigo-400'); }; input.onblur = () => { input.value = formatShortcutDisplay( ShortcutsManager.getShortcut(toolId) || '', isMac ); input.classList.remove('border-indigo-500', 'text-indigo-400'); }; right.append(input); if (currentShortcut) right.append(clearBtn); item.append(left, right); itemsContainer.appendChild(item); }); if (hasTools) { dom.shortcutsList.appendChild(section); } }); createIcons({ icons }); } const scrollToTopBtn = document.getElementById('scroll-to-top-btn'); if (scrollToTopBtn) { let lastScrollY = window.scrollY; window.addEventListener('scroll', () => { const currentScrollY = window.scrollY; if (currentScrollY < lastScrollY && currentScrollY > 300) { scrollToTopBtn.classList.add('visible'); } else { scrollToTopBtn.classList.remove('visible'); } lastScrollY = currentScrollY; }); scrollToTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'instant', }); }); } // Rewrite links after all dynamic content is fully loaded rewriteLinks(); }; window.addEventListener('load', init);