Search

Traffic Analysis - Tracking Typed-in URLs

0 views
Traffic Analysis – Tracking Typed‑In URLs <body></p><h2>The Power of Typed‑In URL Data</h2> <p>When a visitor opens a browser and keys a web address directly, that single act cuts straight through the web’s complex web of links, caches, and search engines. The result is a clean signal: the user’s intent, stripped of mediation. Unlike a click that may have been influenced by page design, a banner, or an affiliate link, a typed URL reflects a deliberate choice made at the very first point of interaction.</p> <p>Because the address bar is the front door to the Internet, traffic that arrives without a referrer carries a high degree of precision. If a user types <code>www.technews.com</code> and lands on the homepage, that query tells analysts that the brand name itself was enough to motivate the visit. In contrast, click data often loses that clarity; the click may have originated from a recommendation widget, a search result, or a social media post. When you lose that context, you also lose a valuable slice of insight into brand recall and direct demand.</p> <p>Direct traffic is not a new concept, but the volume and granularity of typed URL data have grown dramatically. With the proliferation of mobile devices, autocomplete, and voice assistants, people are more likely than ever to type or speak a domain rather than click through a search result. Each typed entry becomes a data point that can be aggregated, filtered, and compared against other metrics like conversion rates or marketing spend.</p> <p>From a marketing perspective, the implications are clear. If a campaign increases brand awareness, the number of typed URLs for that brand’s domain should rise. If a competitor launches a new product, the number of typed URLs for that competitor’s site may spike as users search for that product directly. For security teams, an abrupt surge in typed URLs for a suspicious domain could flag a phishing campaign in real time. The rawness of the data, free from filtering layers, makes it especially useful for detecting anomalies and emerging trends.</p> <p>Because the information is so clean, analysts can often build predictive models that treat typed URLs as strong indicators of intent. A typed URL often precedes a conversion by a higher margin than a click-through. When combined with other signals - such as session duration or subsequent navigation paths - the raw domain name can become a key feature in machine‑learning models that forecast customer behavior.</p> <p>In short, typed‑in URLs provide a direct window into how users perceive and remember brands. They are the most straightforward proof that a brand exists in a user’s mind, and they offer a powerful, unobstructed source of insight for marketers, product managers, and security professionals alike.</p><h2>Technical Foundations: How Typed URLs Reach the Server</h2> <p>Every typed URL must undergo a series of network steps before it reaches its destination. The process starts with a DNS query, the mechanism that translates a human‑readable domain into an IP address. The user’s device contacts a local DNS resolver, often provided by the Internet service provider, and asks for the IP of the typed domain. If the resolver has a cached response, it replies instantly; otherwise, it forwards the query up the hierarchy of root, top‑level domain, and authoritative name servers.</p> <p>Once the IP is returned, the browser initiates a TCP connection to that address. For HTTPS sites, the browser performs a TLS handshake, during which the ClientHello message includes the Server Name Indication (SNI). The SNI field contains the domain name in plain text, allowing the server to present the correct certificate. Even if the rest of the traffic is encrypted, the SNI remains visible to anyone who can observe the handshake.</p> <p>In the era of DoH (DNS over HTTPS) and DoT (DNS over TLS), the DNS query itself is wrapped in encryption, making it invisible to the ISP or any intermediate node that does not terminate the TLS session. The resolver still receives the plaintext domain, but observers between the client and the resolver cannot see it. To capture typed URLs in such environments, network operators often rely on local proxies or endpoint agents that can intercept the DNS request before it leaves the device.</p> <p>Other layers of the stack also expose typed URLs. The HTTP Host header, sent in plain text even over HTTPS, carries the domain name. In addition, many browsers provide an address‑bar API that allows extensions or scripts to read the exact string a user entered. This capability is invaluable for capturing the user’s original intent before any browser‑side autocorrect or autocomplete modifications take effect.</p> <p>Because the path from the user to the server passes through multiple network nodes - device, local DNS, ISP DNS, corporate proxy, and finally the target server - each node can log or forward the domain name. Network administrators can configure logging at the resolver, at a transparent proxy, or even within a corporate firewall that inspects the SNI or Host header. The resulting data set is a time‑stamped record of every domain that was typed or resolved, along with the client’s IP address and the exact query string.</p> <p>Understanding this flow is essential for anyone building a typed‑URL analytics system. Without a clear picture of where the domain can be captured, it’s easy to miss critical data or introduce privacy gaps. By mapping out the entire journey from keystroke to server, teams can identify the best points of interception, choose the right tools, and design a pipeline that balances visibility with performance.</p><h2>From Device to Data Warehouse: Building a Capture Pipeline</h2> <p>Capturing typed URLs in a reliable, scalable way involves a combination of client‑side instrumentation and network‑level logging. The goal is to obtain a clean, time‑ordered list of domains that users typed, along with minimal contextual data needed for analysis.</p> <p>Browser extensions provide the most direct method. By tapping into the address‑bar API, an extension can read the URL the user entered as soon as the Enter key is pressed. The extension then sends the domain, a timestamp, and a unique session identifier to a remote endpoint over HTTPS. This approach guarantees that even DoH traffic never obscures the domain name, because the data is captured inside the browser itself. However, it requires user consent and works only on browsers that support the needed APIs.</p> <p>Endpoint agents offer a more network‑wide solution. Lightweight daemons installed on corporate laptops and desktops hook into the operating system’s DNS resolver. On Windows, for instance, the agent can listen to the Winsock API for outgoing DNS queries. On macOS and Linux, the agent can monitor the glibc or CoreFoundation resolver libraries. Once a DNS query is detected, the agent logs the domain, the local IP, and a monotonically increasing counter. The agent then forwards batches of logs to a central ingestion service via a secure channel. Because the agent operates locally, it can capture DoH traffic before it is encrypted by the browser.</p> <p>Transparent proxies sit in the network path and can capture the SNI field from TLS handshakes or the Host header from HTTP requests. The proxy records the domain, the client IP, and the timestamp. For HTTPS traffic, the proxy must perform TLS termination; otherwise it can only capture the SNI. A hybrid approach - using an application‑layer proxy for HTTPS and a low‑level DNS logger for DoH - provides the most comprehensive coverage.</p> <p>Once the raw data lands in the ingestion pipeline, it passes through several transformation stages. First, the domain is canonicalized: the scheme, “www.” prefix, and path are stripped, leaving only the registrable domain. Next, a GeoIP lookup enriches each record with country, city, and ISP information. The timestamp is converted to a common time zone and rounded to the nearest minute to facilitate aggregation. Finally, for privacy compliance, sensitive fields such as the client IP or a raw user identifier are hashed or removed entirely before the data is stored in the data warehouse.</p> <p>The aggregated dataset is then available for downstream analytics. Whether the data sits in a relational database, a time‑series store, or a data lake, analysts can run SQL queries, build dashboards, and feed the information into machine‑learning pipelines. The key to a successful system is a clear separation between the capture layer, which focuses on reliability and minimal intrusion, and the analytics layer, which focuses on context and interpretation.</p><h2>Turning Data Into Insight: Analytics and Business Value</h2> <p>Once you have a clean, time‑ordered list of typed URLs, the next step is to transform those raw entries into actionable intelligence. The most immediate application is measuring brand recall. By aggregating the number of unique sessions that typed a brand’s domain each day, you obtain a direct gauge of how many users actively seek the brand without clicking through a search engine or ad. A sudden uptick in direct visits can signal that a recent marketing push or public event has boosted awareness.</p> <p>Trend detection is another powerful use case. Because typed URLs are not filtered through recommendation engines or ad networks, they often surface the earliest signals of a new topic or product. For example, if a niche e‑commerce site sees a 200% rise in direct traffic to <code>veganbaking.com</code> in a single week, that spike may indicate a new recipe trend that influencers are discussing. By correlating such spikes with social media feeds or news headlines, marketers can stay ahead of the curve.</p> <p>Competitive intelligence benefits from typed‑URL analytics too. By maintaining a rolling window of direct traffic for a set of competitor domains, you can detect shifts in market share. If your site’s direct visits fall while a competitor’s rise, you may need to investigate changes in their messaging, product offering, or search engine optimization strategy.</p> <p>Security teams also use typed URLs to spot malicious activity. A high volume of queries for a domain with a known phishing history, especially if concentrated around the same time of day, should trigger an alert. Because the data is already enriched with geolocation, you can pinpoint the source of a potential botnet or compromised machine.</p> <p>Beyond surface metrics, typed‑URL logs can enrich predictive models. For example, a customer‑journey model might include a binary feature indicating whether the first interaction was a direct visit. Studies show that users who type a domain directly are more likely to convert than those who arrive via a link. By feeding this feature into a logistic regression or random forest, you can improve conversion forecasts and allocate marketing budgets more effectively.</p> <p>Finally, typed‑URL data can be visualized in compelling ways. Heat maps that show the geographic concentration of direct traffic, time‑of‑day graphs that reveal peak periods, or bar charts that compare brand recall before and after campaigns - all these visualizations help stakeholders grasp the impact quickly. When the raw numbers are paired with clear, narrative insights, they become a powerful tool for decision makers.</p><h2>Privacy, Compliance, and Ethical Use</h2> <p>Typed‑URL data is extremely granular, and that granularity comes with responsibility. In many jurisdictions, a typed URL that can be linked to a specific IP address or session ID qualifies as personal data. That means regulations like the General Data Protection Regulation (GDPR) or the California Consumer Privacy Act (CCPA) impose strict rules on how that data can be collected, processed, and stored.</p> <p>First, you must establish a lawful basis for processing. For marketing purposes, many organizations rely on “legitimate interest,” but that requires a balancing test against the user’s privacy expectations. For security monitoring, the law may allow a narrower scope. If you cannot justify a legitimate interest, you’ll need explicit user consent, typically presented through a clear banner that explains what data is collected and why.</p> <p>Transparency is non‑negotiable. Users should know that every domain they type will be logged. Provide an opt‑out mechanism - such as a browser setting or a preference page - where users can disable the capture feature. Make it as easy to opt out as it is to opt in.</p> <p>Data minimization is the next step. Don’t store the raw domain string if you can aggregate it immediately. If you must keep the string, hash it with a one‑way algorithm before it leaves the client device. Store only the hash, a timestamp, and a generic client identifier that can be purged after a set period.</p> <p>Retention policies should align with the regulatory requirements. Many frameworks allow a maximum of 90 days for personal data that is no longer needed for its original purpose. After that period, either anonymize or delete the records entirely. Automating the purge process reduces the risk of accidental data retention.</p> <p>Because the data can be used for profiling, it’s essential to audit any predictive models that use typed URLs. Check for bias that might unfairly target certain demographic groups. If your model’s predictions influence credit decisions, employment offers, or other high‑stakes outcomes, you must ensure that the model meets fairness standards and that you can explain its decisions.</p> <p>Finally, protect the data in transit and at rest. Use TLS for all network connections to your ingestion endpoint, and encrypt logs in the storage layer. Employ role‑based access controls so that only authorized analysts can view the raw data. Maintain an audit trail of who accessed or modified the logs. These technical safeguards, combined with a robust policy framework, create a balanced approach that respects privacy while unlocking valuable insights.</p></p></p></p></p></p></p></p></p></p></p></p></p></p></p></p></p></p></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/traffic-analysis" 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)';"> #traffic analysis </a> <a href="https://www.murdok.org/news/tag/typed-urls" 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)';"> #typed URLs </a> <a href="https://www.murdok.org/news/tag/direct-traffic" 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)';"> #direct traffic </a> <a href="https://www.murdok.org/news/tag/user-intent" 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)';"> #user intent </a> <a href="https://www.murdok.org/news/tag/web-analytics" 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 analytics </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/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> </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="3796"> <input type="hidden" name="article_url" value="https://www.murdok.org/news/traffic-analysis-tracking-typed-in-urls"> <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%2Ftraffic-analysis-tracking-typed-in-urls" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('facebook', 3796);" 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%2Ftraffic-analysis-tracking-typed-in-urls&text=Traffic+Analysis+-+Tracking+Typed-in+URLs" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('twitter', 3796);" 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%2Ftraffic-analysis-tracking-typed-in-urls" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('linkedin', 3796);" 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%2Ftraffic-analysis-tracking-typed-in-urls&title=Traffic+Analysis+-+Tracking+Typed-in+URLs" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('reddit', 3796);" 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=Traffic+Analysis+-+Tracking+Typed-in+URLs%20https%3A%2F%2Fwww.murdok.org%2Fnews%2Ftraffic-analysis-tracking-typed-in-urls" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('whatsapp', 3796);" 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%2Ftraffic-analysis-tracking-typed-in-urls&text=Traffic+Analysis+-+Tracking+Typed-in+URLs" target="_blank" rel="noopener noreferrer" onclick="trackSocialShare('telegram', 3796);" 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=Traffic+Analysis+-+Tracking+Typed-in+URLs&body=Traffic+Analysis+%E2%80%93+Tracking+Typed%E2%80%91In+URLs+The+Power+of+Typed%E2%80%91In+URL+Data+When+a+visitor+opens+a+browser+and+keys+a+web+address+directly%2C+that+sing...%0A%0Ahttps%3A%2F%2Fwww.murdok.org%2Fnews%2Ftraffic-analysis-tracking-typed-in-urls" onclick="trackSocialShare('email', 3796);" 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(3796);" 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%2Ftraffic-analysis-tracking-typed-in-urls" 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/a-network-of-web-sites-is-not-enough" 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_9594_1770312485_6984d3250262f_medium.jpg" alt="A Network Of Web Sites Is Not Enough!" 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)';"> A Network Of Web Sites Is Not Enough! </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> May 26, 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/ten-tips-for-your-web-site-home-page" 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_9590_1770312484_6984d324a98e6_medium.jpg" alt="TEN Tips For Your Web Site Home Page" 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)';"> TEN Tips For Your Web Site Home Page </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> May 26, 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/total-sports-online-launches-megasoccer-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_9586_1770312484_6984d3245d4c7_medium.jpg" alt="Total Sports Online Launches Megasoccer 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)';"> Total Sports Online Launches Megasoccer 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> May 26, 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-6a04ae50482aa"><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-6a04ae50482aa" 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-6a04ae50482aa"></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>