Search

PDF Files - Your Undiscovered Gold Mine

0 views

Why PDFs Contain Untapped SEO Value

Search engines reward pages that offer rich, readable text, but PDFs are treated differently. Even though they hold the same information, most crawlers treat a PDF file as a single block of data instead of a full web page. This means that internal headings, subheadings, and keyword cues buried inside a PDF rarely surface in search results. Companies often stash manuals, specification sheets, and whitepapers in PDF format, trusting the format’s cross‑platform convenience while unknowingly hiding valuable content from search engines.

Typical corporate PDFs range from product feature sheets to detailed technical manuals. Each document is a potential goldmine of industry terminology and authoritative statements that could boost a brand’s relevance for specific search queries. Yet because these files sit on the server without an accompanying HTML skeleton, search bots treat them as static files rather than as navigable information assets. When a PDF is crawled, the bot may download the file and extract a flat list of words, but it rarely follows links inside the document or indexes the document’s structure.

What makes PDFs problematic is how search engines handle metadata. Many PDFs contain minimal or non‑standard metadata, so the bot can’t easily identify the document’s title, description, or author. Even if a PDF is discoverable through a link, the lack of structured data means the file is less likely to appear in rich snippets or knowledge panels. The result is that a document that could rank for dozens of related keywords is effectively invisible to users searching for that information.

The cost of ignoring PDF content is high. When a piece of technical whitepaper never appears in search results, potential customers cannot find it through organic search, even if they already trust the brand. The missing traffic translates into lost leads and decreased visibility for a product or service. For B2B firms, where information depth is key to building credibility, this oversight can be particularly damaging.

Beyond traffic, PDFs can carry significant keyword authority. A well‑written whitepaper filled with niche terminology naturally signals expertise to search engines. When those keywords are embedded in a searchable HTML page, they contribute to topical relevance and can help improve rankings for related queries. Turning a PDF into a web page preserves that keyword richness while exposing it to the full indexing power of the search engine.

Consider a case where a manufacturer had a PDF manual with a hundred pages of technical details. The manual was shared on industry forums and linked in several partner sites, but it never appeared in search results. After converting the PDF to a web page with proper headings, alt text, and internal links, the company saw a 40% increase in organic traffic for queries related to that product line. The improvement was not just in volume but also in the quality of visitors, as the new page attracted users actively seeking that depth of information.

Recognizing the SEO potential of PDF content is the first step toward turning these silent assets into active traffic generators. The next stage involves converting that content into a format that search engines can fully interpret and users can navigate easily.

How to Convert PDF Content into Web-Optimized Pages

Converting a PDF into an SEO‑friendly HTML page starts with extracting the raw text. Most modern PDF readers provide an export function that outputs plain text or HTML. Tools like Adobe Acrobat Pro, PDFtoWord, or free online converters can preserve paragraph breaks and headings. Once you have the raw content, import it into a content management system or a simple text editor for further refinement.

During refinement, pay attention to the document’s logical structure. PDFs often merge headings into a continuous flow, so you’ll need to separate them into distinct

,

, and

tags. This not only signals importance to search engines but also improves readability for visitors. Use the original PDF’s formatting as a guide, but adapt it to web standards by simplifying complex layouts into clean, responsive designs.

Preserving internal links is essential. If the PDF referenced other documents or sections, recreate those links in the HTML page. If the PDF includes a table of contents, convert it into an in‑page navigation bar. Each link should point to a relevant section or related resource, fostering a network of pages that search bots can traverse. Adding breadcrumb navigation also helps both users and crawlers understand the page hierarchy.

Images and graphics within the PDF must be handled carefully. Export each image in a web‑optimized format such as JPEG or PNG, and include descriptive alt text. Alt text turns images into another source of keyword relevance and ensures accessibility for screen readers. If the PDF contains charts or diagrams, consider recreating them with vector graphics to maintain scalability and clarity on all devices.

Metadata is a final, often overlooked element. Populate the HTML page’s tag with a concise, keyword‑rich description of the content. Write a compelling meta description that entices users to click from the search results. Add structured data markup like Schema.org’s Article or Book type to signal the page’s nature to search engines, which can improve rich snippet eligibility.</p> <p>Once the page is live, verify that search engines are indexing it correctly. Use Google Search Console’s URL Inspection tool to see how the page is rendered and to identify any crawl issues. Submit a sitemap that includes the new page, and monitor its performance over time. If the page ranks for its target keywords, you’ll see increased traffic; if not, tweak the heading hierarchy, internal linking, or keyword usage accordingly.</p> <p>Maintaining the SEO value of converted PDF content requires ongoing attention. Whenever you update the original PDF, redo the conversion process to keep the web page current. Cross‑link new pages to related content, and use internal anchors to strengthen the site’s topical authority. By treating PDF assets as living, searchable resources rather than static files, you unlock a steady stream of organic traffic that can significantly boost your online presence.</p></p></p></p></p></p></p></p></p></p></p></p></p></p> </div> <script> (function() { function initCopyableSections() { document.querySelectorAll('.article-content .copyable-section').forEach(function(section) { if (section.querySelector('.copyable-section__btn')) return; var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'copyable-section__btn'; btn.setAttribute('aria-label', 'Copy to clipboard'); var label = section.getAttribute('data-copy-label'); btn.textContent = label ? 'Copy ' + label : 'Copy'; section.appendChild(btn); btn.addEventListener('click', function() { var contentEl = section.querySelector('.copyable-section__content'); var text; if (contentEl) { text = contentEl.textContent.trim(); } else { var clone = section.cloneNode(true); var btnClone = clone.querySelector('.copyable-section__btn'); if (btnClone) btnClone.parentNode.removeChild(btnClone); text = clone.textContent.trim(); } if (!text) return; navigator.clipboard.writeText(text).then(function() { var t = btn.textContent; btn.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(function() { btn.textContent = t; btn.classList.remove('copied'); }, 2000); }); }); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initCopyableSections); } else { initCopyableSections(); } })(); </script> <!-- Tags --> <div class="article-section-tags sidebar-widget" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px; padding: 1.25rem; margin-bottom: 1.5rem;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border);"> <h3 style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); margin: 0; display: flex; align-items: center; gap: 0.5rem; letter-spacing: 0.01em;"> <i class="fas fa-tags" style="color: var(--github-primary); font-size: 0.875rem;"></i> Tags </h3> </div> <div style="display: flex; gap: 0.75rem; flex-wrap: wrap;"> <a href="https://www.murdok.org/news/tag/pdf" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #PDF </a> <a href="https://www.murdok.org/news/tag/seo" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #SEO </a> <a href="https://www.murdok.org/news/tag/search-engine-optimization" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #Search Engine Optimization </a> <a href="https://www.murdok.org/news/tag/digital-marketing" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #Digital Marketing </a> <a href="https://www.murdok.org/news/tag/content-strategy" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #Content Strategy </a> <a href="https://www.murdok.org/news/tag/web-crawling" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #Web Crawling </a> <a href="https://www.murdok.org/news/tag/whitepapers" style="display: inline-block; padding: 0.5rem 1rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; font-family: 'Inter', sans-serif; font-size: 0.875rem; transition: all 0.2s ease;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)';"> #Whitepapers </a> </div> </div> <!-- Correction Form --> <div class="article-section-correction sidebar-widget" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px; padding: 1.25rem; margin-bottom: 1.5rem;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border);"> <h3 style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); margin: 0; display: flex; align-items: center; gap: 0.5rem; letter-spacing: 0.01em;"> <i class="fas fa-edit" style="color: var(--github-primary); font-size: 0.875rem;"></i> Suggest a Correction </h3> </div> <p style="color: var(--github-text-secondary); font-size: 0.9375rem; margin-bottom: 1.5rem;"> Found an error or have a suggestion? Let us know and we'll review it. </p> <form method="POST" action="https://www.murdok.org/api/submit-correction" id="correctionForm" style="display: flex; flex-direction: column; gap: 1rem;"> <input type="hidden" name="article_type" value="news"> <input type="hidden" name="article_id" value="8874"> <input type="hidden" name="article_url" value="https://www.murdok.org/news/pdf-files-your-undiscovered-gold-mine"> <div> <label for="correction_name" style="display: block; font-family: 'Inter', sans-serif; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.5rem; font-size: 0.9375rem;"> Your Name <span style="color: #f43f5e;">*</span> </label> <input type="text" name="name" id="correction_name" autocomplete="name" required style="width: 100%; padding: 0.875rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; font-family: 'Inter', sans-serif; font-size: 0.9375rem;" placeholder="Your name"> </div> <div> <label for="correction_email" style="display: block; font-family: 'Inter', sans-serif; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.5rem; font-size: 0.9375rem;"> Your Email <span style="color: #f43f5e;">*</span> </label> <input type="email" name="email" id="correction_email" autocomplete="email" required style="width: 100%; padding: 0.875rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; font-family: 'Inter', sans-serif; font-size: 0.9375rem;" placeholder="your@email.com"> </div> <div> <label for="correction_message" style="display: block; font-family: 'Inter', sans-serif; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.5rem; font-size: 0.9375rem;"> Correction Details <span style="color: #f43f5e;">*</span> </label> <textarea name="message" id="correction_message" autocomplete="off" required rows="5" style="width: 100%; padding: 0.875rem; background: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; font-family: 'Inter', sans-serif; font-size: 0.9375rem; resize: vertical;" placeholder="Describe the error or suggestion..."></textarea> </div> <button type="submit" style="padding: 0.875rem 1.5rem; background: var(--github-primary); color: var(--github-bg-primary); border: none; border-radius: 8px; font-family: 'Inter', sans-serif; font-weight: 600; cursor: pointer; transition: all 0.2s ease; align-self: flex-start;" onmouseover="this.style.opacity='0.9';" onmouseout="this.style.opacity='1';"> <i class="fas fa-paper-plane" style="margin-right: 0.5rem;"></i> Submit Correction </button> </form> <div id="correctionMessage" style="display: none; margin-top: 1rem; padding: 1rem; border-radius: 8px; font-family: 'Inter', sans-serif;"></div> </div> <script> document.getElementById('correctionForm').addEventListener('submit', function(e) { e.preventDefault(); const form = this; const formData = new FormData(form); const messageDiv = document.getElementById('correctionMessage'); fetch('https://www.murdok.org/api/submit-correction', { method: 'POST', body: formData }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { if (data.success) { messageDiv.style.display = 'block'; messageDiv.style.background = 'rgba(34, 197, 94, 0.1)'; messageDiv.style.border = '1px solid var(--github-primary)'; messageDiv.style.color = 'var(--github-primary)'; messageDiv.textContent = 'Thank you! Your correction has been submitted.'; form.reset(); } else { messageDiv.style.display = 'block'; messageDiv.style.background = 'rgba(244, 63, 94, 0.1)'; messageDiv.style.border = '1px solid #f43f5e'; messageDiv.style.color = '#f43f5e'; messageDiv.textContent = data.error || 'An error occurred. Please try again.'; } }) .catch(error => { console.error('Correction submission error:', error); messageDiv.style.display = 'block'; messageDiv.style.background = 'rgba(244, 63, 94, 0.1)'; messageDiv.style.border = '1px solid #f43f5e'; messageDiv.style.color = '#f43f5e'; messageDiv.textContent = 'Network error. Please check your connection and try again.'; }); }); </script> <!-- Social Media Sharing --> <div class="article-section-share sidebar-widget" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px; padding: 1.25rem; margin-bottom: 1.5rem;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border);"> <h3 style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); margin: 0; display: flex; align-items: center; gap: 0.5rem; letter-spacing: 0.01em;"> <i class="fas fa-share-alt" style="color: var(--github-primary); font-size: 0.875rem;"></i> Share this article </h3> </div> <div style="display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center;"> <!-- Facebook --> <a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('facebook', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: #1877F2; color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share on Facebook"> <i class="fab fa-facebook-f"></i> </a> <!-- Twitter/X --> <a href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine&text=PDF+Files+-+Your+Undiscovered+Gold+Mine" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('twitter', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: #000000; color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share on X (Twitter)"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: block;"> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" fill="currentColor"/> </svg> </a> <!-- LinkedIn --> <a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('linkedin', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: #0077B5; color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share on LinkedIn"> <i class="fab fa-linkedin-in"></i> </a> <!-- Reddit --> <a href="https://reddit.com/submit?url=https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine&title=PDF+Files+-+Your+Undiscovered+Gold+Mine" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('reddit', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: #FF4500; color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share on Reddit"> <i class="fab fa-reddit-alien"></i> </a> <!-- WhatsApp --> <a href="https://wa.me/?text=PDF+Files+-+Your+Undiscovered+Gold+Mine%20https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('whatsapp', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: #25D366; color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share on WhatsApp"> <i class="fab fa-whatsapp"></i> </a> <!-- Telegram --> <a href="https://t.me/share/url?url=https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine&text=PDF+Files+-+Your+Undiscovered+Gold+Mine" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('telegram', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: #0088cc; color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share on Telegram"> <i class="fab fa-telegram-plane"></i> </a> <!-- Email --> <a href="mailto:?subject=PDF+Files+-+Your+Undiscovered+Gold+Mine&body=Why+PDFs+Contain+Untapped+SEO+Value+Search+engines+reward+pages+that+offer+rich%2C+readable+text%2C+but+PDFs+are+treated+differently.%0A%0Ahttps%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine" onclick="trackSocialShare('email', 8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: var(--github-text-secondary); color: #fff; border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem;" onmouseover="this.style.transform='translateY(-2px)';" onmouseout="this.style.transform='translateY(0)';" title="Share via Email"> <i class="fas fa-envelope"></i> </a> <!-- Copy Link --> <button onclick="copyArticleLink(8874);" style="display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: var(--github-bg-primary); border: 1px solid var(--github-border); color: var(--github-text-primary); border-radius: 8px; text-decoration: none; transition: all 0.2s ease; font-size: 1.25rem; cursor: pointer; padding: 0;" onmouseover="this.style.borderColor='var(--github-primary)'; this.style.color='var(--github-primary)'; this.style.transform='translateY(-2px)';" onmouseout="this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-primary)'; this.style.transform='translateY(0)';" title="Copy link"> <i class="fas fa-link"></i> </button> </div> </div> <!-- Comments Section --> <div class="article-section-comments" style="margin-top: 3rem; margin-bottom: 1.5rem;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border);"> <h2 style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); margin: 0; display: flex; align-items: center; gap: 0.5rem; letter-spacing: 0.01em;"> <i class="fas fa-comments" style="color: var(--github-primary); font-size: 0.875rem;"></i> Comments (0) </h2> </div> <div style="background: var(--github-bg-secondary); border: 1px solid var(--github-border); border-radius: 8px; padding: 1.25rem; margin-bottom: 1.5rem; text-align: center;"> <p style="color: var(--github-text-secondary); font-family: 'Inter', sans-serif; font-size: 0.9375rem; margin-bottom: 1rem;"> Please <a href="https://www.murdok.org/login?redirect=https%3A%2F%2Fwww.murdok.org%2Fnews%2Fpdf-files-your-undiscovered-gold-mine" style="color: var(--github-primary); text-decoration: none; font-weight: 600;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">sign in</a> to leave a comment. </p> </div> <!-- Comments List --> <div id="commentsList"> <p style="color: var(--github-text-secondary); font-family: 'Inter', sans-serif; font-size: 0.9375rem; text-align: center; padding: 2rem;"> No comments yet. Be the first to comment! </p> </div> </div> <!-- Related Posts --> <div class="article-section-related sidebar-widget" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px; padding: 1.25rem; margin-bottom: 1.5rem;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border);"> <h2 style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); margin: 0; display: flex; align-items: center; gap: 0.5rem; letter-spacing: 0.01em;"> <i class="fas fa-newspaper" style="color: var(--github-primary); font-size: 0.875rem;"></i> Related Articles </h2> </div> <ul style="list-style: none; padding: 0; margin: 0;"> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/siemens-announces-mobile-phone-search-engine" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <img src="https://www.murdok.org/assets/images/posts/default_post_9674_1770312491_6984d32b6277f_medium.jpg" alt="Siemens Announces Mobile Phone Search Engine" width="240" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"> </div> <div> <h3 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> Siemens Announces Mobile Phone Search Engine </h3> <div style="display: flex; align-items: center; gap: 1rem; font-size: 0.75rem; color: var(--github-text-muted);"> <span style="display: inline-flex; align-items: center; gap: 0.375rem;"><i class="fas fa-calendar" style="font-size: 0.6875rem;"></i> Jun 3, 2004</span> </div> </div> </a> </li> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/transitionalmedia-integrates-metamend-search-engine-optimization" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <img src="https://www.murdok.org/assets/images/posts/default_post_9661_1770312490_6984d32a59b33_medium.jpg" alt="TransitionalMedia Integrates Metamend Search Engine Optimization" width="240" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"> </div> <div> <h3 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> TransitionalMedia Integrates Metamend Search Engine Optimization </h3> <div style="display: flex; align-items: center; gap: 1rem; font-size: 0.75rem; color: var(--github-text-muted);"> <span style="display: inline-flex; align-items: center; gap: 0.375rem;"><i class="fas fa-calendar" style="font-size: 0.6875rem;"></i> Jun 3, 2004</span> </div> </div> </a> </li> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/wiki-back-link-spam-tactic" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <img src="https://www.murdok.org/assets/images/posts/default_post_9646_1770312489_6984d3292b314_medium.jpg" alt="Wiki Back Link Spam Tactic" width="240" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"> </div> <div> <h3 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> Wiki Back Link Spam Tactic </h3> <div style="display: flex; align-items: center; gap: 1rem; font-size: 0.75rem; color: var(--github-text-muted);"> <span style="display: inline-flex; align-items: center; gap: 0.375rem;"><i class="fas fa-calendar" style="font-size: 0.6875rem;"></i> Jun 2, 2004</span> </div> </div> </a> </li> </ul> </div> </article> </div> <!-- Right Sidebar - Widgets --> <div class="col-lg-3 right-sidebar-column" style="max-width: 360px; flex: 0 0 360px; min-height: 100px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; margin-top: 0; margin-bottom: 0; flex-shrink: 0;"> <div class="right-sidebar-wrapper" style="position: sticky; top: 2rem; width: 360px; padding-top: 0; padding-bottom: 1rem; padding-left: 0.75rem; padding-right: 0.75rem; margin-top: 0; margin-bottom: 0; box-sizing: border-box;"> <div class="sidebar-widget sidebar-promo-widget sidebar-promo-has-fallback" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px; padding: 1.5rem 1.25rem; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: center;"> <script type="text/template" id="sidebar-adsense-tpl-sidebar-promo-6a04b469a7b4a"><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4745231616627689"rn crossorigin="anonymous"><\/script>rn<!-- Murdok - Sidebar -->rn<ins class="adsbygoogle"rn style="display:inline-block;width:300px;height:250px"rn data-ad-client="ca-pub-4745231616627689"rn data-ad-slot="2420735192"></ins>rn<script>rn (adsbygoogle = window.adsbygoogle || []).push({});rn<\/script></script> <div id="sidebar-promo-content-sidebar-promo-6a04b469a7b4a" class="sidebar-adsense-slot sidebar-ad-slot" style="text-align: center; width: 300px; min-width: 300px; min-height: 250px; margin: 0 auto;" data-adsense-tpl="sidebar-adsense-tpl-sidebar-promo-6a04b469a7b4a"></div> <div class="sidebar-fallback-slot sidebar-ad-slot" style="text-align: center; width: 300px; min-width: 300px; min-height: 250px; margin: 0 auto;"><div style="width: 300px; height: 250px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> <a href="https://murdok.org/contact" style="color: white; text-decoration: none; text-align: center; padding: 20px;"> <div style="font-size: 24px; font-weight: bold; margin-bottom: 15px;">📢</div> <div style="font-size: 20px; font-weight: bold; margin-bottom: 10px;">Advertise Here</div> <div style="font-size: 14px; opacity: 0.9;">Reach thousands of engaged readers</div> <div style="margin-top: 15px; background: white; color: #667eea; padding: 10px 20px; border-radius: 4px; font-weight: 600;">Contact Us</div> </a></div></div> </div> <div class="sidebar-widget cms-latest-news-widget" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px; padding: 1.25rem; margin-bottom: 1.5rem; "> <h3 class="widget-title" style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 1.25rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); display: flex; align-items: center; gap: 0.5rem; letter-spacing: 0.01em;"> <i class="fas fa-blog" style="color: var(--github-primary); font-size: 0.875rem;"></i> Latest News </h3> <ul style="list-style: none; padding: 0; margin: 0;"> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/creative-poetry-prompts-specifying-meter-image-and-volta" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <picture><img src="https://www.murdok.org/assets/images/posts/creative-poetry-prompts-specifying-meter-image-and-volta-featured-grok-imagine-image-quality.jpg" alt="Creative Poetry Prompts Specifying Meter, Image, and Volta" width="120" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"></picture> </div> <div> <h4 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> Creative Poetry Prompts Specifying Meter, Image, and Volta </h4> <div style="font-size: 0.75rem; color: var(--github-text-muted);"> <i class="fas fa-calendar-alt" style="font-size: 0.6875rem; margin-right: 0.375rem;"></i>May 13, 2026 </div> </div> </a> </li> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/iterative-prompts-for-turning-messy-outlines-into-dynamic-scenes" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <picture><img src="https://www.murdok.org/assets/images/posts/iterative-prompts-for-turning-messy-outlines-into-dynamic-scenes-featured-grok-imagine-image-quality.jpg" alt="Iterative Prompts for Turning Messy Outlines into Dynamic Scenes" width="120" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"></picture> </div> <div> <h4 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> Iterative Prompts for Turning Messy Outlines into Dynamic Scenes </h4> <div style="font-size: 0.75rem; color: var(--github-text-muted);"> <i class="fas fa-calendar-alt" style="font-size: 0.6875rem; margin-right: 0.375rem;"></i>May 12, 2026 </div> </div> </a> </li> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/ai-powered-character-questionnaires-that-feel-truly-specific" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <picture><img src="https://www.murdok.org/assets/images/posts/ai-powered-character-questionnaires-that-feel-truly-specific-featured-grok-imagine-image-quality.jpg" alt="AI-Powered Character Questionnaires That Feel Truly Specific" width="120" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"></picture> </div> <div> <h4 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> AI-Powered Character Questionnaires That Feel Truly Specific </h4> <div style="font-size: 0.75rem; color: var(--github-text-muted);"> <i class="fas fa-calendar-alt" style="font-size: 0.6875rem; margin-right: 0.375rem;"></i>May 12, 2026 </div> </div> </a> </li> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/crafting-vivid-setting-details-with-constrained-prompts" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <picture><img src="https://www.murdok.org/assets/images/posts/crafting-vivid-setting-details-with-constrained-prompts-featured-grok-imagine-image-quality.jpg" alt="Crafting Vivid Setting Details with Constrained Prompts" width="120" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"></picture> </div> <div> <h4 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> Crafting Vivid Setting Details with Constrained Prompts </h4> <div style="font-size: 0.75rem; color: var(--github-text-muted);"> <i class="fas fa-calendar-alt" style="font-size: 0.6875rem; margin-right: 0.375rem;"></i>May 12, 2026 </div> </div> </a> </li> <li style="margin-bottom: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--github-border); transition: all 0.2s ease;" onmouseover="this.style.borderBottomColor='var(--github-border-hover)';" onmouseout="this.style.borderBottomColor='var(--github-border)';"> <a href="https://www.murdok.org/news/how-to-ask-an-llm-for-scene-pacing-feedback-as-a-writer" style="display: block; text-decoration: none; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.8';" onmouseout="this.style.opacity='1';"> <div style="width: 100%; height: 120px; border-radius: 6px; overflow: hidden; background: var(--github-bg-secondary); margin-bottom: 0.75rem; position: relative; display: flex; align-items: center; justify-content: center;"> <picture><img src="https://www.murdok.org/assets/images/posts/how-to-ask-an-llm-for-scene-pacing-feedback-as-a-writer-featured-grok-imagine-image-quality.jpg" alt="How to Ask an LLM for Scene Pacing Feedback as a Writer" width="120" height="120" style="width: 100%; height: 100%; object-fit: cover; display: block;" loading="lazy" decoding="async"></picture> </div> <div> <h4 style="font-family: 'Inter', sans-serif; font-size: 0.875rem; font-weight: 600; color: var(--github-text-primary); margin-bottom: 0.375rem; line-height: 1.4; transition: color 0.2s ease;" onmouseover="this.style.color='var(--github-primary)';" onmouseout="this.style.color='var(--github-text-primary)';"> How to Ask an LLM for Scene Pacing Feedback as a Writer </h4> <div style="font-size: 0.75rem; color: var(--github-text-muted);"> <i class="fas fa-calendar-alt" style="font-size: 0.6875rem; margin-right: 0.375rem;"></i>May 11, 2026 </div> </div> </a> </li> </ul> </div> </div> </div> </div> </div> <style> /* Article sections - match sidebar widget styling exactly */ .article-section-tags.sidebar-widget, .article-section-correction.sidebar-widget, .article-section-share.sidebar-widget, .article-section-comments.sidebar-widget, .article-section-related.sidebar-widget { background: var(--github-bg-primary) !important; border: 1px solid var(--github-border) !important; border-radius: 12px !important; padding: 1.25rem !important; margin-bottom: 1.5rem !important; } [data-bs-theme="dark"] .article-section-tags.sidebar-widget, [data-theme="dark"] .article-section-tags.sidebar-widget, [data-bs-theme="dark"] .article-section-correction.sidebar-widget, [data-theme="dark"] .article-section-correction.sidebar-widget, [data-bs-theme="dark"] .article-section-share.sidebar-widget, [data-theme="dark"] .article-section-share.sidebar-widget, [data-bs-theme="dark"] .article-section-comments.sidebar-widget, [data-theme="dark"] .article-section-comments.sidebar-widget, [data-bs-theme="dark"] .article-section-related.sidebar-widget, [data-theme="dark"] .article-section-related.sidebar-widget { background: #000 !important; } /* Content inside sections should match sidebar container background in dark mode */ [data-bs-theme="dark"] .article-section-tags.sidebar-widget > *, [data-theme="dark"] .article-section-tags.sidebar-widget > *, [data-bs-theme="dark"] .article-section-correction.sidebar-widget > *, [data-theme="dark"] .article-section-correction.sidebar-widget > *, [data-bs-theme="dark"] .article-section-share.sidebar-widget > *, [data-theme="dark"] .article-section-share.sidebar-widget > *, [data-bs-theme="dark"] .article-section-comments.sidebar-widget > *, [data-theme="dark"] .article-section-comments.sidebar-widget > *, [data-bs-theme="dark"] .article-section-related.sidebar-widget > *, [data-theme="dark"] .article-section-related.sidebar-widget > * { background: transparent !important; } /* Form inputs and internal containers should match sidebar background */ [data-bs-theme="dark"] .article-section-correction.sidebar-widget input, [data-theme="dark"] .article-section-correction.sidebar-widget input, [data-bs-theme="dark"] .article-section-correction.sidebar-widget textarea, [data-theme="dark"] .article-section-correction.sidebar-widget textarea, [data-bs-theme="dark"] .article-section-comments.sidebar-widget input, [data-theme="dark"] .article-section-comments.sidebar-widget input, [data-bs-theme="dark"] .article-section-comments.sidebar-widget textarea, [data-theme="dark"] .article-section-comments.sidebar-widget textarea, [data-bs-theme="dark"] .article-section-correction.sidebar-widget .comment-form-container, [data-theme="dark"] .article-section-correction.sidebar-widget .comment-form-container { background: #000 !important; } /* Internal divs with backgrounds should match sidebar */ [data-bs-theme="dark"] .article-section-comments.sidebar-widget .comment-form-container, [data-theme="dark"] .article-section-comments.sidebar-widget .comment-form-container, [data-bs-theme="dark"] .article-section-comments.sidebar-widget > div[style*="background"], [data-theme="dark"] .article-section-comments.sidebar-widget > div[style*="background"], [data-bs-theme="dark"] .article-section-related.sidebar-widget ul li div[style*="background"], [data-theme="dark"] .article-section-related.sidebar-widget ul li div[style*="background"], [data-bs-theme="dark"] .article-section-tags.sidebar-widget a[style*="background"], [data-theme="dark"] .article-section-tags.sidebar-widget a[style*="background"] { background: #000 !important; } /* Ensure all content containers match sidebar background */ [data-bs-theme="dark"] .article-section-tags.sidebar-widget > div, [data-theme="dark"] .article-section-tags.sidebar-widget > div, [data-bs-theme="dark"] .article-section-correction.sidebar-widget > div, [data-theme="dark"] .article-section-correction.sidebar-widget > div, [data-bs-theme="dark"] .article-section-share.sidebar-widget > div, [data-theme="dark"] .article-section-share.sidebar-widget > div, [data-bs-theme="dark"] .article-section-comments.sidebar-widget > div, [data-theme="dark"] .article-section-comments.sidebar-widget > div, [data-bs-theme="dark"] .article-section-related.sidebar-widget > div, [data-theme="dark"] .article-section-related.sidebar-widget > div { background: transparent !important; } /* Ensure proper flex layout for news single page - match news listing page */ .news-main-content { flex: 1 1 0 !important; min-width: 0 !important; max-width: none !important; width: auto !important; padding-left: 1.5rem !important; padding-right: 1.5rem !important; } /* Desktop layout - ensure proper flex behavior */ @media (min-width: 992px) { .row.g-4 { display: flex !important; flex-wrap: nowrap !important; align-items: flex-start !important; } .left-sidebar-column, .right-sidebar-column { flex: 0 0 auto !important; max-width: 360px !important; min-width: 0 !important; } .news-main-content { flex: 1 1 0 !important; min-width: 900px !important; /* Comfortable reading width for articles - matches typical news page width */ max-width: none !important; width: auto !important; padding-left: 1.5rem !important; padding-right: 1.5rem !important; } } /* On mobile, allow wrapping */ @media (max-width: 991px) { .row.g-4 { flex-wrap: wrap !important; } .left-sidebar-column, .right-sidebar-column { width: 100% !important; max-width: 100% !important; flex: 0 0 100% !important; } .news-main-content { width: 100% !important; max-width: 100% !important; flex: 0 0 100% !important; min-width: 0 !important; padding-left: 0 !important; padding-right: 0 !important; } } </style> <script> // Dynamically adjust main content column width based on sidebars document.addEventListener('DOMContentLoaded', function() { const mainContent = document.getElementById('news-main-content'); if (!mainContent) return; const hasLeftSidebar = document.querySelector('.left-sidebar-column') !== null; const hasRightSidebar = document.querySelector('.right-sidebar-column') !== null; const row = mainContent.closest('.row'); // Main content should always flex to fill remaining space mainContent.style.flex = '1 1 0'; mainContent.style.minWidth = '0'; // On desktop (>= 992px), prevent wrapping when sidebars are present function updateLayout() { if (window.innerWidth >= 992) { if (hasLeftSidebar || hasRightSidebar) { if (row) { row.style.flexWrap = 'nowrap'; } } } else { // On mobile, allow wrapping if (row) { row.style.flexWrap = 'wrap'; } } } // Update on load and resize updateLayout(); window.addEventListener('resize', updateLayout); }); </script> <script> // Handle hash links on page load (for any internal links) document.addEventListener('DOMContentLoaded', function() { // Add scroll offset for fixed headers const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]'); headings.forEach(heading => { heading.style.scrollMarginTop = '100px'; heading.style.scrollPaddingTop = '100px'; }); // Handle hash links on page load if (window.location.hash) { setTimeout(function() { const targetId = window.location.hash.substring(1); const targetElement = document.getElementById(targetId); if (targetElement) { const headerOffset = 100; const elementPosition = targetElement.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }, 100); } }); // Track social media shares function trackSocialShare(platform, postId) { fetch('https://www.murdok.org/api/news-share', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ post_id: postId, platform: platform }) }).catch(err => console.log('Share tracking error', err)); } // Copy article link to clipboard function copyArticleLink(postId) { const url = window.location.href; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(url).then(() => { showCopyNotification('Link copied to clipboard!'); trackSocialShare('copy', postId); }).catch(err => { fallbackCopyText(url, postId); }); } else { fallbackCopyText(url, postId); } } function fallbackCopyText(text, postId) { const input = document.createElement('input'); input.value = text; input.style.position = 'fixed'; input.style.opacity = '0'; document.body.appendChild(input); input.select(); input.setSelectionRange(0, 99999); // For mobile devices try { document.execCommand('copy'); showCopyNotification('Link copied to clipboard!'); if (postId) { trackSocialShare('copy', postId); } } catch (err) { showCopyNotification('Failed to copy link. Please copy manually: ' + text); } document.body.removeChild(input); } function showCopyNotification(message) { // Create notification element const notification = document.createElement('div'); notification.textContent = message; notification.style.cssText = 'position: fixed; bottom: 2rem; right: 2rem; background: var(--github-primary); color: var(--github-bg-primary); padding: 1rem 1.5rem; border-radius: 8px; z-index: 10000; font-family: "Inter", sans-serif; font-weight: 600; font-size: 0.9375rem; animation: slideIn 0.3s ease;'; // Add animation const style = document.createElement('style'); style.textContent = '@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }'; document.head.appendChild(style); document.body.appendChild(notification); // Remove after 3 seconds setTimeout(() => { notification.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } if (document.head.contains(style)) { document.head.removeChild(style); } }, 300); }, 3000); } // Comments functionality document.getElementById('commentForm')?.addEventListener('submit', function(e) { e.preventDefault(); const form = this; const formData = new FormData(form); const messageDiv = document.getElementById('commentMessage'); fetch('https://www.murdok.org/api/article-comment', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { messageDiv.style.display = 'block'; messageDiv.style.background = 'rgba(34, 197, 94, 0.1)'; messageDiv.style.border = '1px solid var(--github-primary)'; messageDiv.style.color = 'var(--github-primary)'; messageDiv.textContent = 'Comment posted successfully!'; form.reset(); document.getElementById('parent_comment_id').value = ''; document.getElementById('cancelReplyBtn').style.display = 'none'; // Reload page to show new comment setTimeout(() => window.location.reload(), 1000); } else { messageDiv.style.display = 'block'; messageDiv.style.background = 'rgba(244, 63, 94, 0.1)'; messageDiv.style.border = '1px solid #f43f5e'; messageDiv.style.color = '#f43f5e'; messageDiv.textContent = data.error || 'An error occurred. Please try again.'; } }) .catch(error => { console.error('Comment submission error:', error); messageDiv.style.display = 'block'; messageDiv.style.background = 'rgba(244, 63, 94, 0.1)'; messageDiv.style.border = '1px solid #f43f5e'; messageDiv.style.color = '#f43f5e'; messageDiv.textContent = 'Network error. Please check your connection and try again.'; }); }); function replyToComment(commentId, commenterName) { document.getElementById('parent_comment_id').value = commentId; document.getElementById('comment_text').placeholder = 'Reply to ' + commenterName + '...'; document.getElementById('comment_text').focus(); document.getElementById('cancelReplyBtn').style.display = 'inline-block'; document.getElementById('comment_text').scrollIntoView({ behavior: 'smooth', block: 'center' }); } function cancelReply() { document.getElementById('parent_comment_id').value = ''; document.getElementById('comment_text').placeholder = 'Write your comment here...'; document.getElementById('cancelReplyBtn').style.display = 'none'; } function deleteComment(commentId) { if (!confirm('Are you sure you want to delete this comment?')) { return; } const formData = new FormData(); formData.append('action', 'delete'); formData.append('comment_id', commentId); fetch('https://www.murdok.org/api/article-comment', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { window.location.reload(); } else { alert(data.error || 'Failed to delete comment'); } }) .catch(error => { console.error('Delete comment error:', error); alert('Network error. Please try again.'); }); } // Favorite button functionality const favoriteBtn = document.getElementById('favoriteBtn'); if (favoriteBtn) { favoriteBtn.addEventListener('click', function() { const postId = this.dataset.postId; const isFavorited = this.dataset.isFavorited === '1'; // Check if user is logged in // Show signup modal showSignupModal(); return; // Toggle favorite const formData = new FormData(); formData.append('action', 'toggle'); formData.append('post_id', postId); // Disable button during request this.disabled = true; const originalText = this.querySelector('.favorite-text').textContent; this.querySelector('.favorite-text').textContent = '...'; fetch('https://www.murdok.org/api/favorite', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { this.disabled = false; if (data.success) { // Toggle state const newIsFavorited = !isFavorited; this.dataset.isFavorited = newIsFavorited ? '1' : '0'; // Update button appearance const heartIcon = this.querySelector('i.fa-heart'); if (newIsFavorited) { this.style.background = '#dc3545'; this.style.borderColor = '#dc3545'; this.style.color = 'white'; if (heartIcon) heartIcon.style.color = 'white'; this.querySelector('.favorite-text').textContent = 'Favorited'; } else { this.style.background = 'transparent'; this.style.borderColor = '#dc3545'; this.style.color = '#f87171'; if (heartIcon) heartIcon.style.color = '#f87171'; const favoriteText = this.querySelector('.favorite-text'); if (favoriteText) { favoriteText.textContent = 'Favorite'; favoriteText.style.color = '#f87171'; } } // Show toast notification if (typeof GitHubToast !== 'undefined') { GitHubToast.success(newIsFavorited ? 'Article added to favorites' : 'Article removed from favorites'); } // Refresh favorites count in sidebar if widget exists if (typeof refreshFavoritesWidget === 'function') { refreshFavoritesWidget(); } } else { this.querySelector('.favorite-text').textContent = originalText; if (typeof GitHubToast !== 'undefined') { GitHubToast.error(data.error || 'Failed to update favorite'); } else { alert(data.error || 'Failed to update favorite'); } } }) .catch(error => { this.disabled = false; this.querySelector('.favorite-text').textContent = originalText; console.error('Favorite error:', error); if (typeof GitHubToast !== 'undefined') { GitHubToast.error('Network error. Please try again.'); } else { alert('Network error. Please try again.'); } }); }); } // Signup modal function function showSignupModal() { // Create modal if it doesn't exist let modal = document.getElementById('signupModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'signupModal'; modal.className = 'modal fade'; modal.setAttribute('tabindex', '-1'); modal.innerHTML = ` <div class="modal-dialog modal-dialog-centered"> <div class="modal-content" style="background: var(--github-bg-primary); border: 1px solid var(--github-border); border-radius: 12px;"> <div class="modal-header" style="border-bottom: 1px solid var(--github-border); padding: 1.5rem;"> <h5 class="modal-title" style="font-family: 'Inter', sans-serif; font-size: 1.5rem; font-weight: 700; color: var(--github-text-primary);"> <i class="fas fa-user-plus" style="color: var(--github-primary); margin-right: 0.5rem;"></i> Sign Up to Favorite Articles </h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" style="opacity: 1; background-color: var(--github-text-secondary); border-radius: 4px; padding: 0.5rem; cursor: pointer; filter: invert(1);" title="Close"></button> </div> <div class="modal-body" style="padding: 2rem;"> <p style="color: var(--github-text-secondary); font-family: 'Inter', sans-serif; margin-bottom: 1.5rem;"> Create an account to save your favorite articles and access them anytime. </p> <form method="POST" action="https://www.murdok.org/signup" id="modalSignupForm"> <input type="hidden" name="recaptcha_token" id="modal_recaptcha_token"> <div style="margin-bottom: 1rem;"> <label style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); display: block; margin-bottom: 0.5rem;"> Username <span style="color: #ef4444;">*</span> </label> <input type="text" name="username" required pattern="[a-zA-Z0-9_\-]{3,20}" style="width: 100%; background: var(--github-bg-secondary); border: 1px solid var(--github-border); border-radius: 8px; color: var(--github-text-primary); font-family: 'Inter', sans-serif; font-size: 0.9375rem; padding: 0.75rem 1rem;" onfocus="this.style.borderColor='var(--github-primary)';" onblur="this.style.borderColor='var(--github-border)';"> </div> <div style="margin-bottom: 1rem;"> <label style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); display: block; margin-bottom: 0.5rem;"> Email <span style="color: #ef4444;">*</span> </label> <input type="email" name="email" required style="width: 100%; background: var(--github-bg-secondary); border: 1px solid var(--github-border); border-radius: 8px; color: var(--github-text-primary); font-family: 'Inter', sans-serif; font-size: 0.9375rem; padding: 0.75rem 1rem;" onfocus="this.style.borderColor='var(--github-primary)';" onblur="this.style.borderColor='var(--github-border)';"> </div> <div style="margin-bottom: 1.5rem;"> <label style="font-family: 'Inter', sans-serif; font-size: 0.9375rem; font-weight: 600; color: var(--github-text-primary); display: block; margin-bottom: 0.5rem;"> Password <span style="color: #ef4444;">*</span> </label> <input type="password" name="password" required minlength="6" style="width: 100%; background: var(--github-bg-secondary); border: 1px solid var(--github-border); border-radius: 8px; color: var(--github-text-primary); font-family: 'Inter', sans-serif; font-size: 0.9375rem; padding: 0.75rem 1rem;" onfocus="this.style.borderColor='var(--github-primary)';" onblur="this.style.borderColor='var(--github-border)';"> </div> <button type="submit" style="width: 100%; background: var(--github-primary); color: #000000; border: 1px solid var(--github-primary); border-radius: 8px; padding: 0.875rem 1.5rem; font-family: 'Inter', sans-serif; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.2s ease;" onmouseover="this.style.opacity='0.9';" onmouseout="this.style.opacity='1';"> <i class="fas fa-user-plus" style="margin-right: 0.5rem;"></i> Create Account </button> </form> <div style="text-align: center; margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--github-border);"> <p style="margin: 0; color: var(--github-text-secondary); font-size: 0.9375rem; font-family: 'Inter', sans-serif;"> Already have an account? <a href="https://www.murdok.org/login" style="color: var(--github-primary); text-decoration: none; font-weight: 600;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';"> Sign In </a> </p> </div> </div> </div> </div> `; document.body.appendChild(modal); // Handle reCAPTCHA for modal form (function() { var recaptchaSiteKey = String("6LdJ01IsAAAAAHyuodz2yefPjZa8q9CoeQB0l_xE" || ''); if (recaptchaSiteKey === '') return; var modalForm = document.getElementById('modalSignupForm'); if (modalForm) { modalForm.addEventListener('submit', function(e) { e.preventDefault(); if (typeof grecaptcha !== 'undefined' && grecaptcha.ready) { grecaptcha.ready(function() { grecaptcha.execute(recaptchaSiteKey, { action: String('signup') }) .then(function(token) { document.getElementById('modal_recaptcha_token').value = token; modalForm.submit(); }) .catch(function(err) { document.getElementById('modal_recaptcha_token').value = ''; modalForm.submit(); }); }); } else { modalForm.submit(); } }); } })(); } // Show modal - check if Bootstrap is loaded if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { try { const bsModal = new bootstrap.Modal(modal); bsModal.show(); } catch (e) { console.error('Bootstrap Modal error:', e); // Fallback: show modal manually modal.style.display = 'block'; modal.classList.add('show'); document.body.classList.add('modal-open'); const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; document.body.appendChild(backdrop); // Close button functionality for fallback const closeBtn = modal.querySelector('.btn-close'); if (closeBtn) { closeBtn.onclick = () => { modal.style.display = 'none'; modal.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); }; } // Backdrop click to close backdrop.onclick = () => { modal.style.display = 'none'; modal.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); }; // ESC key to close const escHandler = (e) => { if (e.key === 'Escape' && modal.classList.contains('show')) { modal.style.display = 'none'; modal.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); document.body.appendChild(backdrop); } } else { // Fallback: show modal manually modal.style.display = 'block'; modal.classList.add('show'); document.body.classList.add('modal-open'); const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; document.body.appendChild(backdrop); // Close button functionality for fallback const closeBtn = modal.querySelector('.btn-close'); if (closeBtn) { closeBtn.onclick = () => { modal.style.display = 'none'; modal.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); }; } // Backdrop click to close backdrop.onclick = () => { modal.style.display = 'none'; modal.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); }; // ESC key to close const escHandler = (e) => { if (e.key === 'Escape' && modal.classList.contains('show')) { modal.style.display = 'none'; modal.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); } } </script> </main> <!-- Footer --> <footer class="border-top mt-5" style="background-color: var(--github-bg-secondary); border-color: var(--github-border) !important;"> <div class="container py-4"> <div class="row g-4"> <!-- Logo --> <div class="col-lg-3 col-md-4"> <div class="mb-3"> <div class="d-flex align-items-center mb-2"> <img src="/assets/images/uploads/logo_dark_1768218367.png" alt="Murdok" width="120" height="32" class="footer-logo-dark" style="max-width: 120px; object-fit: contain;"> </div> <p style="font-family: 'Inter', sans-serif; font-size: 0.875rem; color: var(--github-text-secondary); margin: 0; line-height: 1.5;"> Creative Writing With Artificial Intelligence </p> </div> </div> <!-- Navigation Links --> <div class="col-lg-6 col-md-4"> <div class="row g-4"> <div class="col-6"> <h3 class="fw-bold mb-3" style="color: var(--github-text-primary); text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.5px; margin: 0 0 1rem 0;"> Navigation </h3> <ul class="list-unstyled mb-0"> <li class="mb-2"><a href="/" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-home" style="width: 16px; text-align: center;"></i> Home</a></li> <li class="mb-2"><a href="/news" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-newspaper" style="width: 16px; text-align: center;"></i> News</a></li> <li class="mb-2"><a href="/wiki" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-book" style="width: 16px; text-align: center;"></i> Wiki</a></li> <li class="mb-2"><a href="/search" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-search" style="width: 16px; text-align: center;"></i> Search</a></li> </ul> </div> <div class="col-6"> <h3 class="fw-bold mb-3" style="color: var(--github-text-primary); text-transform: uppercase; font-size: 12px; letter-spacing: 0.5px; font-size: 0.75rem; margin: 0 0 1rem 0;"> Support </h3> <ul class="list-unstyled mb-0"> <li class="mb-2"><a href="/contact" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-envelope" style="width: 16px; text-align: center;"></i> Contact</a></li> <li class="mb-2"><a href="/privacy" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-shield-alt" style="width: 16px; text-align: center;"></i> Privacy</a></li> <li class="mb-2"><a href="/terms" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-file-contract" style="width: 16px; text-align: center;"></i> Terms</a></li> <li class="mb-2"><a href="/sitemap" class="text-decoration-none fw-medium d-flex align-items-center gap-2" style="color: var(--github-text-secondary); font-size: 14px; transition: color 0.2s ease;"><i class="fas fa-sitemap" style="width: 16px; text-align: center;"></i> Sitemap</a></li> </ul> </div> </div> </div> <!-- Copyright, Social Media, and Theme Toggle --> <div class="col-lg-3 col-md-4"> <div class="d-flex flex-column align-items-md-end"> <div class="d-flex align-items-center gap-2 mb-3 flex-wrap justify-content-md-end"> <a href="https://x.com/MurdokActual" target="_blank" rel="noopener noreferrer" class="text-decoration-none d-flex align-items-center justify-content-center" style="width: 36px; height: 36px; border-radius: 50%; background-color: var(--github-bg-secondary); border: 1px solid var(--github-border); color: var(--github-text-secondary); transition: all 0.2s ease;" onmouseover="this.style.backgroundColor='#000000'; this.style.borderColor='#000000'; this.style.color='white'; this.style.transform='translateY(-2px)';" onmouseout="this.style.backgroundColor='var(--github-bg-secondary)'; this.style.borderColor='var(--github-border)'; this.style.color='var(--github-text-secondary)'; this.style.transform='translateY(0)';" title="Twitter"> <i class="fab fa-x-twitter" style="font-size: 16px;"></i> </a> </div> <p class="mb-3 fw-medium text-md-end" style="color: var(--github-text-secondary); font-size: 13px;"> © 2026 Murdok. All rights reserved. </p> </div> </div> </div> </div> </footer> <!-- Bootstrap 5.3.3 JS Bundle (admin loads in header to avoid bootstrap undefined on sidebar-widgets) --> <script src="/assets/js/bootstrap.bundle.min.js" defer></script> <!-- jQuery 3.7.1 --> <script src="/assets/js/jquery.min.js" defer></script> <!-- Alpine.js --> <script src="/assets/js/alpine.min.js" defer></script> <!-- Cropper.js (only load on profile page or admin pages) - defer for Core Web Vitals --> <!-- Footer Styles --> <style> /* Footer link hover effects */ footer a { transition: color 0.2s ease, transform 0.2s ease; } footer a:hover { color: var(--github-primary) !important; transform: translateX(2px); } /* Logo hover effect */ footer img:hover { transform: scale(1.05); transition: transform 0.2s ease; } footer button:hover { background-color: var(--github-bg-secondary) !important; transform: scale(1.05); } </style> <!-- GitHub-inspired Utility Functions --> <script> // Global image error handler - provides fallback for missing images function handleImageError(img) { // Create a placeholder SVG const placeholder = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgZmlsbD0iI2Y2ZjhmYSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTQiIGZpbGw9IiM2NTZkNzYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj5JbWFnZSBub3QgYXZhaWxhYmxlPC90ZXh0Pjwvc3ZnPg=='; // Replace with placeholder if not already using it if (img.src !== placeholder) { img.onerror = null; // Prevent infinite loop img.src = placeholder; img.style.backgroundColor = 'var(--github-bg-secondary, #f6f8fa)'; img.style.display = 'block'; } else { // If placeholder also fails, hide the image img.style.display = 'none'; } } // Ensure dark theme is set (site is dark-only) document.addEventListener('DOMContentLoaded', function() { document.documentElement.setAttribute('data-bs-theme', 'dark'); }); // GitHub-inspired Toast System class GitHubToast { static show(message, type = 'info', duration = 5000) { // Ensure toast container exists let toastContainer = document.getElementById('toastContainer'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'toastContainer'; toastContainer.className = 'toast-container'; toastContainer.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px;'; document.body.appendChild(toastContainer); } const toastId = 'toast-' + Date.now(); const bgClass = type === 'success' ? 'bg-success' : type === 'error' ? 'bg-danger' : type === 'warning' ? 'bg-warning' : 'bg-primary'; const iconClass = type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-triangle' : type === 'warning' ? 'fa-exclamation-circle' : 'fa-info-circle'; const toastHTML = ` <div id="${toastId}" class="toast align-items-center text-white ${bgClass} border-0" role="alert" aria-live="assertive" aria-atomic="true" style="display: block; opacity: 0; transition: opacity 0.3s ease;"> <div class="d-flex"> <div class="toast-body"> <i class="fas ${iconClass} me-2"></i> ${this.escapeHtml(message)} </div> <button type="button" class="btn-close btn-close-white me-2 m-auto" onclick="document.getElementById('${toastId}').remove()" aria-label="Close"></button> </div> </div> `; toastContainer.insertAdjacentHTML('beforeend', toastHTML); const toastElement = document.getElementById(toastId); // Show toast with animation setTimeout(() => { toastElement.style.opacity = '1'; }, 10); // Use Bootstrap Toast if available, otherwise use fallback if (typeof bootstrap !== 'undefined' && bootstrap.Toast) { const toast = new bootstrap.Toast(toastElement, { delay: duration }); toast.show(); toastElement.addEventListener('hidden.bs.toast', () => { toastElement.remove(); }); } else { // Fallback: simple show/hide toastElement.style.opacity = '1'; // Auto-dismiss if (duration > 0) { setTimeout(() => { toastElement.style.opacity = '0'; setTimeout(() => { if (toastElement.parentNode) { toastElement.remove(); } }, 300); }, duration); } } } static escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } static success(message, duration = 5000) { this.show(message, 'success', duration); } static error(message, duration = 5000) { this.show(message, 'error', duration); } static warning(message, duration = 5000) { this.show(message, 'warning', duration); } static info(message, duration = 5000) { this.show(message, 'info', duration); } } // GitHub-inspired Modal System class GitHubModal { static show(options = {}) { const { title = '', body = '', footer = '', size = 'md', backdrop = true, keyboard = true } = options; const modalId = 'github-modal-' + Date.now(); const sizeClass = size === 'lg' ? 'modal-lg' : size === 'sm' ? 'modal-sm' : ''; const modalHTML = ` <div class="modal fade" id="${modalId}" tabindex="-1" ${backdrop ? '' : 'data-bs-backdrop="static"'} ${keyboard ? '' : 'data-bs-keyboard="false"'}> <div class="modal-dialog ${sizeClass}"> <div class="modal-content"> ${title ? `<div class="modal-header"> <h5 class="modal-title">${title}</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div>` : ''} ${body ? `<div class="modal-body">${body}</div>` : ''} ${footer ? `<div class="modal-footer">${footer}</div>` : ''} </div> </div> </div> `; document.body.insertAdjacentHTML('beforeend', modalHTML); const modalElement = document.getElementById(modalId); // Use Bootstrap Modal if available, otherwise use vanilla JS let modal; if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { modal = new bootstrap.Modal(modalElement); modalElement.addEventListener('hidden.bs.modal', () => { modalElement.remove(); }); modal.show(); } else { // Fallback: show modal manually modalElement.style.display = 'block'; modalElement.classList.add('show'); document.body.classList.add('modal-open'); const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; backdrop.onclick = () => { modalElement.style.display = 'none'; modalElement.classList.remove('show'); document.body.classList.remove('modal-open'); backdrop.remove(); modalElement.remove(); }; document.body.appendChild(backdrop); // Store backdrop reference for cleanup modalElement._backdrop = backdrop; // Close on ESC key const escHandler = (e) => { if (e.key === 'Escape' && modalElement.classList.contains('show')) { backdrop.click(); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); // Add hide method for compatibility modal = { hide: () => { backdrop.click(); }, _element: modalElement }; } return { modal, element: modalElement }; } static confirm(message, title = 'Confirm', confirmText = 'OK', cancelText = 'Cancel') { return new Promise((resolve) => { let settled = false; const finish = (value) => { if (settled) { return; } settled = true; resolve(value); }; const footer = ` <button type="button" class="btn btn-secondary github-modal-btn-cancel" data-bs-dismiss="modal">${cancelText}</button> <button type="button" class="btn btn-primary github-modal-btn-ok">${confirmText}</button> `; const { modal, element: modalElement } = this.show({ title, body: `<p class="mb-0">${message}</p>`, footer }); const confirmBtn = modalElement ? modalElement.querySelector('.github-modal-btn-ok') : null; const cancelBtn = modalElement ? modalElement.querySelector('.github-modal-btn-cancel') : null; const hideModal = () => { if (modal && typeof modal.hide === 'function') { modal.hide(); } else if (modalElement) { modalElement.style.display = 'none'; modalElement.classList.remove('show'); document.body.classList.remove('modal-open'); const back = modalElement._backdrop || document.querySelector('.modal-backdrop'); if (back) { back.remove(); } modalElement.remove(); } }; if (confirmBtn) { confirmBtn.addEventListener('click', () => { finish(true); hideModal(); }); } if (cancelBtn) { cancelBtn.addEventListener('click', () => { finish(false); hideModal(); }); } // Dismiss via X, Escape, or backdrop — only if not already confirmed/cancelled if (modalElement && typeof bootstrap !== 'undefined' && bootstrap.Modal) { modalElement.addEventListener('hidden.bs.modal', () => { finish(false); }); } else if (modalElement) { const backdrop = modalElement._backdrop || document.querySelector('.modal-backdrop'); if (backdrop) { backdrop.addEventListener('click', () => finish(false)); } } }); } } // GitHub-inspired Table Initializers class GitHubTables { static initTabulator(selector, columns, options = {}) { const defaultOptions = { layout: "fitColumns", responsiveLayout: "collapse", pagination: "local", paginationSize: 25, paginationSizeSelector: [10, 25, 50, 100], movableColumns: true, resizableRows: true, placeholder: "No data available", height: "auto" }; return new Tabulator(selector, { columns, ...defaultOptions, ...options }); } } // GitHub-inspired Form Enhancements class GitHubForms { static initSelect2(selector, options = {}) { const defaultOptions = { theme: 'bootstrap-5', width: '100%', placeholder: 'Select an option...', allowClear: true }; return $(selector).select2({ ...defaultOptions, ...options }); } static initSimpleBar(selector) { return new SimpleBar(document.querySelector(selector)); } static initCharts() { // Initialize any Chart.js instances on the page const chartCanvases = document.querySelectorAll('canvas[data-chart]'); chartCanvases.forEach(canvas => { const chartType = canvas.dataset.chart; const chartData = JSON.parse(canvas.dataset.chartData || '{}'); new Chart(canvas, { type: chartType, data: chartData, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: chartType === 'line' || chartType === 'bar' ? { y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.1)' } }, x: { grid: { display: false } } } : {} } }); }); } } // Initialize everything when DOM is ready document.addEventListener('DOMContentLoaded', function() { // Add loading states to forms document.querySelectorAll('form').forEach(form => { form.addEventListener('submit', function(e) { const submitBtn = form.querySelector('button[type="submit"], .github-btn-primary'); if (submitBtn && !submitBtn.classList.contains('no-loading')) { submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Loading...'; submitBtn.disabled = true; } }); }); // Add keyboard shortcuts document.addEventListener('keydown', function(e) { // Ctrl/Cmd + K for search focus if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); const searchInput = document.querySelector('input[type="search"], [data-search]'); if (searchInput) { searchInput.focus(); } } // Escape to close modals if (e.key === 'Escape') { if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modals = document.querySelectorAll('.modal.show'); modals.forEach(modal => { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) { bsModal.hide(); } }); } } }); }); // Make classes globally available window.GitHubToast = GitHubToast; window.GitHubModal = GitHubModal; window.GitHubTables = GitHubTables; window.GitHubForms = GitHubForms; </script> <!-- Responsive Header Search Toggle --> <script> document.addEventListener('DOMContentLoaded', function() { const searchToggle = document.getElementById('headerSearchToggle'); const searchForm = document.getElementById('headerSearchForm'); const searchInput = document.getElementById('headerSearchInput'); if (searchToggle && searchForm) { // Toggle search form on icon click searchToggle.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); searchForm.classList.toggle('expanded'); if (searchForm.classList.contains('expanded')) { searchInput.focus(); } }); // Close search form when clicking outside document.addEventListener('click', function(e) { if (!searchForm.contains(e.target) && !searchToggle.contains(e.target)) { searchForm.classList.remove('expanded'); } }); // Close search form on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && searchForm.classList.contains('expanded')) { searchForm.classList.remove('expanded'); } }); // Close search form after form submission on mobile if (searchForm) { searchForm.addEventListener('submit', function() { if (window.innerWidth <= 991) { setTimeout(function() { searchForm.classList.remove('expanded'); }, 100); } }); } } }); // Fix hamburger menu on mobile - ensure Bootstrap collapse works // Wait for Bootstrap to be available (it loads in the footer) function initHamburgerMenu() { // Check if Bootstrap is loaded, if not wait a bit and retry if (typeof bootstrap === 'undefined' || !bootstrap.Collapse) { setTimeout(initHamburgerMenu, 100); return; } // Initialize public navbar collapse const publicNavbar = document.getElementById('publicNavbar'); const publicNavbarToggle = document.getElementById('publicNavbarToggle'); if (publicNavbar && publicNavbarToggle) { // Initialize collapse instance if it doesn't exist let publicCollapse = bootstrap.Collapse.getInstance(publicNavbar); if (!publicCollapse) { publicCollapse = new bootstrap.Collapse(publicNavbar, { toggle: false }); } // Ensure the toggle button works publicNavbarToggle.addEventListener('click', function(e) { if (publicCollapse) { publicCollapse.toggle(); } }); } // Initialize admin navbar collapse const adminNavbar = document.getElementById('adminNavbar'); const adminNavbarToggle = document.querySelector('[data-bs-target="#adminNavbar"]'); if (adminNavbar && adminNavbarToggle) { // Initialize collapse instance if it doesn't exist let adminCollapse = bootstrap.Collapse.getInstance(adminNavbar); if (!adminCollapse) { adminCollapse = new bootstrap.Collapse(adminNavbar, { toggle: false }); } // Ensure the toggle button works adminNavbarToggle.addEventListener('click', function(e) { if (adminCollapse) { adminCollapse.toggle(); } }); } } // Start initialization when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initHamburgerMenu); } else { initHamburgerMenu(); } </script> </body> </html>