document.addEventListener('DOMContentLoaded', () => { const sidebar = document.getElementById('sidebar'); const closeBtn = document.getElementById('close-sidebar'); const trigger = document.getElementById('read-more-trigger'); const body = document.body; let manualClose = false; // --- Shy Logo Logic --- const logo = document.querySelector('.header-logo img'); const header = document.querySelector('.main-header'); if (logo && header) { let offsetX = 0, offsetY = 0; let velX = 0, velY = 0; let mouseX = 0, mouseY = 0; let chaseStarted = false; let approaching = false; let stillTimer = null; let animating = false; const FLEE_RADIUS = 130; const FLEE_FORCE = 18; const FRICTION = 0.82; const APPROACH_SPEED = 0.035; const STILL_DELAY = 1000; function logoCenter() { const r = logo.getBoundingClientRect(); return { x: r.left + r.width / 2, y: r.top + r.height / 2 }; } function headerBounds() { const hr = header.getBoundingClientRect(); const lr = logo.getBoundingClientRect(); return { minX: -(lr.left - hr.left) + 8, maxX: (hr.right - lr.right) - 8, minY: -(lr.top - hr.top) + 8, maxY: (hr.bottom - lr.bottom) - 8 }; } function clamp(x, y) { const b = headerBounds(); return { x: Math.max(b.minX, Math.min(b.maxX, x)), y: Math.max(b.minY, Math.min(b.maxY, y)) }; } function applyTransform() { logo.style.transform = `translate(${offsetX}px, ${offsetY}px)`; } function tick() { if (approaching) { // Gently approach where the mouse is const c = logoCenter(); const targetX = mouseX - (c.x - offsetX); const targetY = mouseY - (c.y - offsetY); velX += (targetX - offsetX) * APPROACH_SPEED; velY += (targetY - offsetY) * APPROACH_SPEED; velX *= 0.85; velY *= 0.85; } velX *= FRICTION; velY *= FRICTION; offsetX += velX; offsetY += velY; const c = clamp(offsetX, offsetY); // Bounce off header edges if (c.x !== offsetX) velX *= -0.3; if (c.y !== offsetY) velY *= -0.3; offsetX = c.x; offsetY = c.y; applyTransform(); const moving = Math.abs(velX) > 0.1 || Math.abs(velY) > 0.1; const farFromHome = Math.abs(offsetX) > 0.5 || Math.abs(offsetY) > 0.5; if (moving || approaching || farFromHome) { requestAnimationFrame(tick); } else { animating = false; } } function startAnim() { if (!animating) { animating = true; requestAnimationFrame(tick); } } header.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; const c = logoCenter(); const dx = c.x - mouseX; const dy = c.y - mouseY; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < FLEE_RADIUS) { chaseStarted = true; approaching = false; // Flee away from cursor const angle = Math.atan2(dy, dx); const force = ((FLEE_RADIUS - dist) / FLEE_RADIUS) * FLEE_FORCE; velX += Math.cos(angle) * force; velY += Math.sin(angle) * force; startAnim(); } // Reset the "stopped chasing" timer clearTimeout(stillTimer); stillTimer = setTimeout(() => { if (chaseStarted) { approaching = true; startAnim(); } }, STILL_DELAY); }); header.addEventListener('mouseleave', () => { clearTimeout(stillTimer); approaching = false; chaseStarted = false; // Drift home velX += -offsetX * 0.08; velY += -offsetY * 0.08; startAnim(); }); } // --- Sidebar Logic --- function checkSidebarVisibility() { if (manualClose) return; const scrollTop = window.scrollY; const windowHeight = window.innerHeight; const docHeight = document.documentElement.scrollHeight; // "Sidebar fix": Check if page is short (content fits in window or close to it) // If content is short, sidebar should be visible immediately (if desktop) if (docHeight <= windowHeight * 1.2 && window.innerWidth > 900) { sidebar.classList.add('visible'); sidebar.style.opacity = 1; sidebar.style.pointerEvents = 'auto'; return; } // Scroll Logic for longer pages if (scrollTop + windowHeight > docHeight * 0.9) { sidebar.classList.add('visible'); trigger.classList.add('visible'); // Dynamic Opacity near bottom const remaining = docHeight - (scrollTop + windowHeight); const threshold = docHeight * 0.1; if (remaining < threshold) { const opacity = 1 - (remaining / threshold); sidebar.style.opacity = Math.min(Math.max(opacity, 0), 1); if (opacity > 0) { sidebar.style.pointerEvents = 'auto'; } else { sidebar.style.pointerEvents = 'none'; } } else { // Should be redundant with visibility check but safe } } else { // Hide if scrolled up? // "It just is gone then... manual close... refresh if want it back" // But scroll behavior implies transient visibility. // We'll hide it if we scroll back up, unless it's short content. sidebar.style.opacity = 0; sidebar.style.pointerEvents = 'none'; } } window.addEventListener('scroll', checkSidebarVisibility); window.addEventListener('resize', checkSidebarVisibility); // Initial check setTimeout(checkSidebarVisibility, 100); // Slight delay for rendering // Mobile Trigger trigger.addEventListener('click', () => { sidebar.classList.add('force-visible'); sidebar.style.opacity = 1; sidebar.style.pointerEvents = 'auto'; }); // Close Button closeBtn.addEventListener('click', () => { sidebar.classList.remove('visible'); sidebar.classList.remove('force-visible'); sidebar.style.opacity = 0; sidebar.style.pointerEvents = 'none'; manualClose = true; trigger.style.display = 'none'; }); // --- Font Tribute: Carter & Connare --- const TRIBUTE_FONTS = ['Georgia', 'Tahoma', 'Comic Sans MS', 'Trebuchet MS']; function tributeTextNodes() { const content = document.querySelector('.main-content'); if (!content) return []; const nodes = []; const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, { acceptNode: (n) => { const el = n.parentElement; if (n.textContent.trim().length < 2) return NodeFilter.FILTER_REJECT; if (['SCRIPT','STYLE','CODE','PRE'].includes(el.tagName)) return NodeFilter.FILTER_REJECT; if (el.classList.contains('font-tribute')) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); while (walker.nextNode()) nodes.push(walker.currentNode); return nodes; } function sprinkleFont() { const nodes = tributeTextNodes(); if (!nodes.length) return; const node = nodes[Math.floor(Math.random() * nodes.length)]; const text = node.textContent; // Find a non-whitespace character let pos, tries = 0; do { pos = Math.floor(Math.random() * text.length); } while (text[pos].trim() === '' && ++tries < 20); if (text[pos].trim() === '') return; const font = TRIBUTE_FONTS[Math.floor(Math.random() * TRIBUTE_FONTS.length)]; const span = document.createElement('span'); span.textContent = text[pos]; span.style.fontFamily = '"' + font + '"'; span.className = 'font-tribute'; const before = document.createTextNode(text.slice(0, pos)); const after = document.createTextNode(text.slice(pos + 1)); node.parentNode.insertBefore(before, node); node.parentNode.insertBefore(span, node); node.parentNode.insertBefore(after, node); node.parentNode.removeChild(node); } // Initial sprinkle: 3 letters for (let i = 0; i < 3; i++) sprinkleFont(); // Occasionally, mid-read, one more letter quietly changes (function scheduleNext() { setTimeout(() => { sprinkleFont(); scheduleNext(); }, 12000 + Math.random() * 30000); // every 12-42 seconds })(); // --- Dark Mode Logic --- // Create Toggle Button const sidebarContent = document.querySelector('.sidebar-content'); const themeBtn = document.createElement('button'); themeBtn.id = 'theme-toggle'; themeBtn.textContent = 'Toggle Theme'; sidebarContent.appendChild(themeBtn); function setDarkMode(enable) { if (enable) { body.classList.add('dark-mode'); themeBtn.textContent = 'Switch to Light Mode'; } else { body.classList.remove('dark-mode'); themeBtn.textContent = 'Switch to Dark Mode'; } } function getCookie(name) { const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); return v ? v[2] : null; } function setCookie(name, value, hours) { const d = new Date(); d.setTime(d.getTime() + (hours * 60 * 60 * 1000)); document.cookie = name + "=" + value + ";path=/;expires=" + d.toUTCString(); } // Init Dark Mode const cookieTheme = getCookie('theme'); if (cookieTheme === 'dark') { setDarkMode(true); } else if (cookieTheme === 'light') { setDarkMode(false); } else { // Time based auto-detect const hour = new Date().getHours(); if (hour < 6 || hour >= 18) { setDarkMode(true); } else { setDarkMode(false); } } themeBtn.addEventListener('click', () => { const isDark = body.classList.contains('dark-mode'); setDarkMode(!isDark); setCookie('theme', !isDark ? 'dark' : 'light', 24); }); });