Search

How to Use Your Google API Key as Your Secret Weapon: Part Two

2 views

Why an RSS Feed Matters for Your Site

Imagine a single line of code that lets anyone on the web subscribe to every headline you publish. That line of code is an RSS feed, and it turns your website into a real-time news source. When you create an RSS feed, search engines, news aggregators, and other sites can pull your content automatically, keeping your pages indexed more often and making it easier for people to find you through feeds rather than a search query.

For site owners who are tired of the endless effort required to keep newsletters up to date, an RSS feed offers a simpler alternative. Users click a button, choose an aggregator like Feedly or Inoreader, and receive your latest posts without you having to send a separate email each time. The result is higher engagement, less maintenance, and a steady stream of new visitors who are already interested in what you publish.

Another advantage is that RSS does not limit you to your own content. You can curate links from other blogs, news outlets, or podcasts that fit your niche and include them in the same feed. By becoming the go-to hub for a specific topic, you create a value proposition that keeps people coming back. If no other site in your industry is doing this, the first mover advantage becomes real. You’ll quickly be viewed as the go-to source for up‑to‑date information.

Google’s indexing algorithms reward sites that provide fresh, relevant content on a consistent basis. By feeding its crawlers with a new entry every time you publish, you can influence how often Google visits your pages. Over time, this can improve the ranking of keywords tied to your niche, which in turn attracts even more organic traffic.

Because many news sites host their feeds on high‑authority domains, including a link to your feed can generate backlinks that boost your own domain’s authority. Even a single backlink from a well‑ranked site can have a measurable effect on your search visibility, making the effort to set up an RSS feed a low‑risk investment with potentially high payoff.

Finally, RSS feeds reduce the friction of content discovery. If a user is already subscribed to a feed that includes your posts, they’ll see your content in their aggregator’s stream without having to visit your site directly. That exposure can translate into click‑throughs, social shares, and eventually new subscribers or customers.

Step‑by‑Step: Creating an RSS Feed with Your Google API Key

The first step is to generate a free Google API key. Go to the Google Cloud Console, create a new project, and enable the “Google Custom Search JSON API” or “Google Programmable Search Engine” depending on your needs. After enabling the API, navigate to the Credentials page and click “Create Credentials.” Choose “API key” and copy the key that appears. Store it in a secure place because you’ll need it to make authenticated requests from your server or a script.

Next, decide how you’ll pull content from your site. Most content management systems (CMS) like WordPress, Drupal, or Joomla already expose an RSS feed for posts. If your CMS doesn’t provide one, you can generate it manually with a script that reads your database and outputs XML in the RSS 2.0 format. The XML must include elements such as , , <link />, <description>, and <item> blocks for each article, each with a <title>, <link />, <pubDate>, and <description> child. Keep the XML well‑formed; any syntax errors can cause feed readers to ignore your feed.</p> <p>Once your feed source is ready, use the Google API key to enrich the feed with search or related‑content data. For example, you can call the Custom Search API to find the latest news stories about a keyword you want to highlight. The API returns JSON containing titles, links, and snippets. Embed this information as additional <item> elements in your RSS XML or display it alongside your posts on the website. Because the API key is free and allows a generous quota, you can run these calls on a schedule - say, every hour - to keep your feed fresh without hitting limits.</p> <p>Host the RSS file on a URL that is easy to remember, such as https://yourdomain.com/feed.xml. Make sure the file is publicly accessible and that the server returns the correct MIME type (application/rss+xml). Add a <link /> tag in the <head> of your home page pointing to the feed, so browsers and feed readers can discover it automatically.</p> <p>Verify the feed’s validity with an online validator like <a href="https://validator.w3.org/feed'>W3C Feed Validation Service</a>. Feed validators catch errors such as missing required tags or incorrect dates. Fix any reported problems before you share the feed publicly. A clean feed reduces the chance that readers will encounter issues when subscribing.</p> <p>After the feed is live, test it by subscribing to it in a feed reader or using a browser extension that shows the feed’s contents. Confirm that new posts appear promptly and that the content displays correctly. If you’re using the Custom Search API, check that the additional items are present and that the URLs point to valid pages. Once satisfied, you’re ready to promote the feed.</p><h2>Promoting Your Feed to Drive Traffic</h2> <p>The most effective way to attract readers is to list your feed on high‑traffic directories that specialize in aggregating content for specific niches. Sites such as Feedburner, Bloglovin, or niche‑specific directories accept RSS submissions and display them to users who search for topics of interest. Because these directories already have a built‑in audience, placing your feed there gives you immediate visibility.</p> <p>In addition to directories, add a prominent “Subscribe” button to your site’s sidebar or footer. Use clear labeling like “Get the latest headlines” or “Subscribe via RSS.” When visitors click the button, you can offer the feed link in a pop‑up that also encourages them to follow your social accounts or sign up for your newsletter. This cross‑promotion increases the chances that a casual reader becomes a regular follower.</p> <p>Another tactic is to embed your feed on related blogs or podcasts. If you collaborate with a partner site, ask them to display your feed in their sidebar. In return, offer to feature their content in yours. Such reciprocal arrangements expand reach on both sides and create a network effect that keeps both audiences engaged.</p> <p>Track the performance of your feed with analytics tools. Google Analytics allows you to create a filter that captures traffic from feed readers by inspecting the referrer or user agent. Once you have the data, you can determine which directories or partners are driving the most visits, and adjust your promotion strategy accordingly.</p> <p>Finally, consider adding a call‑to‑action in each feed item that invites readers to visit a related page or download a resource. For example, a post about a new study could link to a downloadable PDF or a video discussion. These additional touchpoints convert passive readers into active participants, driving deeper engagement and, ultimately, revenue.</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/google-api" 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)';"> #Google API </a> <a href="https://www.murdok.org/news/tag/rss-feed" 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)';"> #RSS feed </a> <a href="https://www.murdok.org/news/tag/web-development" 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 development </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/content-syndication" 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 syndication </a> <a href="https://www.murdok.org/news/tag/search-engine-indexing" 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 indexing </a> <a href="https://www.murdok.org/news/tag/news-aggregator" 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)';"> #news aggregator </a> <a href="https://www.murdok.org/news/tag/site-owners" 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)';"> #site owners </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="9790"> <input type="hidden" name="article_url" value="https://www.murdok.org/news/how-to-use-your-google-api-key-as-your-secret-weapon-part-two"> <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%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('facebook', 9790);" 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%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two&text=How+to+Use+Your+Google+API+Key+as+Your+Secret+Weapon%3A+Part+Two" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('twitter', 9790);" 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%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('linkedin', 9790);" 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%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two&title=How+to+Use+Your+Google+API+Key+as+Your+Secret+Weapon%3A+Part+Two" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('reddit', 9790);" 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=How+to+Use+Your+Google+API+Key+as+Your+Secret+Weapon%3A+Part+Two%20https%3A%2F%2Fwww.murdok.org%2Fnews%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('whatsapp', 9790);" 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%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two&text=How+to+Use+Your+Google+API+Key+as+Your+Secret+Weapon%3A+Part+Two" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('telegram', 9790);" 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=How+to+Use+Your+Google+API+Key+as+Your+Secret+Weapon%3A+Part+Two&body=Why+an+RSS+Feed+Matters+for+Your+Site+Imagine+a+single+line+of+code+that+lets+anyone+on+the+web+subscribe+to+every+headline+you+publish.%0A%0Ahttps%3A%2F%2Fwww.murdok.org%2Fnews%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two" onclick="trackSocialShare('email', 9790);" 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(9790);" 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%2Fhow-to-use-your-google-api-key-as-your-secret-weapon-part-two" 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/reciprocal-linking-overload-any-old-link-back-will-do" 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_10080_1770312523_6984d34bebed8_medium.jpg" alt="Reciprocal Linking Overload-Any Old Link Back Will Do?" 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)';"> Reciprocal Linking Overload-Any Old Link Back Will Do? </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> Jul 19, 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/mobile-search-slow-to-catch-on" 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_10070_1770312523_6984d34b2bc5e_medium.jpg" alt="Mobile Search Slow to Catch On" 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)';"> Mobile Search Slow to Catch On </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> Jul 19, 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/search-engine-optimization-tips" 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_10068_1770312523_6984d34b04647_medium.jpg" alt="Search Engine Optimization Tips" 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)';"> Search Engine Optimization Tips </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-clock" style="font-size: 0.6875rem;"></i> 3 min read</span> <span style="display: inline-flex; align-items: center; gap: 0.375rem;"><i class="fas fa-calendar" style="font-size: 0.6875rem;"></i> Jul 16, 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-6a04d76870cfe"><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-6a04d76870cfe" 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-6a04d76870cfe"></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/memoir-writers-using-ai-ethically-for-memory-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/memoir-writers-using-ai-ethically-for-memory-prompts-featured-grok-imagine-image-quality.jpg" alt="Memoir Writers Using AI Ethically for Memory 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)';"> Memoir Writers Using AI Ethically for Memory 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 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/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> </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>