first commit

This commit is contained in:
2026-01-25 11:33:37 -04:00
commit 6f42a58ee5
148 changed files with 32974 additions and 0 deletions
+1587
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 452.265"><path fill="currentColor" d="M110.985 30.441c58.695 44.217 121.837 133.856 145.013 181.961 23.177-48.105 86.323-137.744 145.017-181.961C443.376-1.455 512-26.142 512 52.402c0 15.68-8.962 131.775-14.223 150.628-18.273 65.515-84.873 82.228-144.112 72.116 103.55 17.679 129.889 76.237 73 134.799-108.041 111.223-155.288-27.905-167.386-63.554-3.488-10.262-2.991-10.498-6.56 0-12.098 35.649-59.342 174.777-167.383 63.554-56.889-58.562-30.551-117.12 72.999-134.799-59.239 10.112-125.84-6.601-144.112-72.116C8.962 184.177 0 68.082 0 52.402 0-26.142 68.633-1.455 110.985 30.441z"/></svg>

After

Width:  |  Height:  |  Size: 668 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 103.19 122.88"><path fill="currentColor" d="M17.16 0h82.72a3.32 3.32 0 013.31 3.31v92.32c-.15 2.58-3.48 2.64-7.08 2.48H15.94c-4.98 0-9.05 4.07-9.05 9.05s4.07 9.05 9.05 9.05h80.17v-9.63h7.08v12.24c0 2.23-1.82 4.05-4.05 4.05H16.29C7.33 122.88 0 115.55 0 106.59V17.16C0 7.72 7.72 0 17.16 0zm3.19 13.4h2.86c1.46 0 2.66.97 2.66 2.15v67.47c0 1.18-1.2 2.15-2.66 2.15h-2.86c-1.46 0-2.66-.97-2.66-2.15V15.55c.01-1.19 1.2-2.15 2.66-2.15z" fill-rule="evenodd" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 529 B

+1
View File
@@ -0,0 +1 @@
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>

After

Width:  |  Height:  |  Size: 188 B

+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M15.24 2H11.3458C9.58159 1.99999 8.18418 1.99997 7.09054 2.1476C5.96501 2.29953 5.05402 2.61964 4.33559 3.34096C3.61717 4.06227 3.29833 4.97692 3.14701 6.10697C2.99997 7.205 2.99999 8.60802 3 10.3793V16.2169C3 17.725 3.91995 19.0174 5.22717 19.5592C5.15989 18.6498 5.15994 17.3737 5.16 16.312L5.16 11.3976L5.16 11.3024C5.15993 10.0207 5.15986 8.91644 5.27828 8.03211C5.40519 7.08438 5.69139 6.17592 6.4253 5.43906C7.15921 4.70219 8.06404 4.41485 9.00798 4.28743C9.88877 4.16854 10.9887 4.1686 12.2652 4.16867L12.36 4.16868H15.24L15.3348 4.16867C16.6113 4.1686 17.7088 4.16854 18.5896 4.28743C18.0627 2.94779 16.7616 2 15.24 2Z"/>
<path fill="currentColor" d="M6.6001 11.3974C6.6001 8.67119 6.6001 7.3081 7.44363 6.46118C8.28716 5.61426 9.64481 5.61426 12.3601 5.61426H15.2401C17.9554 5.61426 19.313 5.61426 20.1566 6.46118C21.0001 7.3081 21.0001 8.6712 21.0001 11.3974V16.2167C21.0001 18.9429 21.0001 20.306 20.1566 21.1529C19.313 21.9998 17.9554 21.9998 15.2401 21.9998H12.3601C9.64481 21.9998 8.28716 21.9998 7.44363 21.1529C6.6001 20.306 6.6001 18.9429 6.6001 16.2167V11.3974Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 107.41" style="enable-background:new 0 0 122.88 107.41" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style><g><path class="st0" fill="currentColor" d="M60.83,17.19C68.84,8.84,74.45,1.62,86.79,0.21c23.17-2.66,44.48,21.06,32.78,44.41 c-3.33,6.65-10.11,14.56-17.61,22.32c-8.23,8.52-17.34,16.87-23.72,23.2l-17.4,17.26L46.46,93.56C29.16,76.9,0.95,55.93,0.02,29.95 C-0.63,11.75,13.73,0.09,30.25,0.3C45.01,0.5,51.22,7.84,60.83,17.19L60.83,17.19L60.83,17.19z"/></g></svg>

After

Width:  |  Height:  |  Size: 690 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>

After

Width:  |  Height:  |  Size: 157 B

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -0.5 21 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>like [#1386]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-219.000000, -760.000000)" fill="currentColor">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M163,610.021159 L163,618.021159 C163,619.126159 163.93975,620.000159 165.1,620.000159 L167.199999,620.000159 L167.199999,608.000159 L165.1,608.000159 C163.93975,608.000159 163,608.916159 163,610.021159 M183.925446,611.355159 L182.100546,617.890159 C181.800246,619.131159 180.639996,620.000159 179.302297,620.000159 L169.299999,620.000159 L169.299999,608.021159 L171.104948,601.826159 C171.318098,600.509159 172.754498,599.625159 174.209798,600.157159 C175.080247,600.476159 175.599997,601.339159 175.599997,602.228159 L175.599997,607.021159 C175.599997,607.573159 176.070397,608.000159 176.649997,608.000159 L181.127196,608.000159 C182.974146,608.000159 184.340196,609.642159 183.925446,611.355159" id="like-[#1386]">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path fill="currentColor" d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 333333 333333" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"><path d="M44382 244427C19901 244427 6 264387 6 288717c0 24452 19893 44205 44376 44205 24556 0 44437-19754 44437-44205 0-24328-19875-44290-44437-44290zM49 113241v63895c41608 0 80707 16271 110159 45748 29422 29391 45667 68681 45667 110448h64167c0-121341-98731-220072-219993-220072v-20zM130 0v63907c148372 0 269117 120869 269117 269407l64080 6C333327 149577 183829 7 135 7l-6-6z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 620 B

+48
View File
@@ -0,0 +1,48 @@
// Load and display all blog posts
async function loadBlogPosts() {
try {
const response = await fetch('blog-posts.json');
const posts = await response.json();
const container = document.getElementById("blogPostsContainer");
// Sort posts by date (newest first)
const sortedPosts = posts.sort((a, b) => new Date(b.date) - new Date(a.date));
sortedPosts.forEach(post => {
const postElement = document.createElement('div');
postElement.className = 'blog-post';
postElement.id = `post-${post.id}`;
// Convert content newlines to paragraphs
const contentParagraphs = post.content.split('\n\n').filter(p => p.trim().length > 0);
const formattedContent = contentParagraphs.map(p => `<p>${p.trim()}</p>`).join('');
postElement.innerHTML = `
<h2 class="blog-post-title">${post.title}</h2>
<p class="blog-post-date">${post.date}</p>
<p class="blog-post-excerpt">${post.excerpt}</p>
<div class="blog-post-content">${formattedContent}</div>
<p class="blog-post-signature">-- Author --</p>
`;
container.appendChild(postElement);
});
// Scroll to post if hash is present
if (window.location.hash) {
setTimeout(() => {
const targetPost = document.querySelector(window.location.hash);
if (targetPost) {
targetPost.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 100);
}
} catch (error) {
console.error('Error loading blog posts:', error);
document.getElementById("blogPostsContainer").innerHTML =
'<p class="error">Error loading blog posts. Please try again later.</p>';
}
}
// Load posts when page loads
window.addEventListener('load', loadBlogPosts);
+138
View File
@@ -0,0 +1,138 @@
// Error messages with time-of-day awareness
// Each error code has multiple witty messages that rotate randomly
function getErrorMessage(errorCode) {
const today = new Date();
// Your timezone, your problem - we just read what your browser tells us
const hours = today.getHours();
const timeContext = hours < 6 ? 'late' : hours < 12 ? 'morning' : hours < 18 ? 'afternoon' : 'evening';
const messages = {
400: [
"Bad Request? More like bad judgment. What were you even thinking?",
"Your browser sent something that made the server question its life choices.",
"Syntax error: human detected. Please reformat your existence.",
"The server tried to parse your request. It failed. Miserably.",
"This isn't a buffet. You can't just order whatever you want.",
"Your request was so malformed, it gave the parser an existential crisis.",
"Bad request? That's putting it mildly. The server is offended.",
"The server politely declined your request. It's not you, it's... actually, it is you."
],
401: [
"Unauthorized. You shall not pass. Unless you have the password.",
"Access denied. This area requires more credentials than a spy movie.",
"You need authentication. This isn't a BYOB party.",
"Unauthorized access attempt. The server is judging you right now.",
"Credentials required. No, 'password123' doesn't count.",
"Authentication failed. Try harder. Or don't. The server doesn't care.",
"Access denied. Even the server has standards.",
"You're not authorized. The bouncer said no."
],
403: [
"Forbidden. This isn't the page you're looking for. Move along.",
"Access forbidden. The server has trust issues. Can you blame it?",
"Permission denied. The server said 'absolutely not' in seventeen languages.",
"Forbidden territory. Even VPNs can't save you now.",
"This area is off-limits. The server drew a line. You crossed it.",
"Access forbidden. The server is protecting you from yourself.",
"Permission denied. The server would rather die than show you this.",
"Forbidden. This page has restraining orders against browsers like yours.",
"Access denied. The server is gatekeeping. Deal with it.",
"This content is forbidden. The server has standards, unlike some people."
],
404: [
"Page not found. It's gone. Vanished. Poof. Like my motivation on Monday.",
"The page you seek does not exist in this dimension. Try another reality.",
"This page doesn't exist. Neither do my regrets about creating this message.",
"The server searched everywhere. Even checked under the couch.",
"Page not found. It left. Said it needed to find itself. We're all very supportive.",
"The page you're looking for has achieved enlightenment and transcended the web.",
"This page doesn't exist. It's not you, it's me. Actually, it's definitely you.",
"The server even checked the last place you'd look. Still nothing.",
"Page not found. It's in witness protection. The server knows where it is but won't tell.",
"The page escaped. Last seen heading toward the exit. We're not chasing it.",
"This page doesn't exist. The server looked twice. Still gone. Accept it.",
"The page ran away to join the circus. Good for it."
],
405: [
"Method not allowed. The server rejected your approach. Try a different strategy.",
"HTTP method denied. The server is picky about how you ask questions.",
"Method not allowed. The server prefers a more elegant approach.",
"This method isn't allowed here. The server has strict rules about manners.",
"Method rejected. The server wants you to ask nicely next time.",
"HTTP method not allowed. The server is traditional. Write a letter instead."
],
408: [
"Request timeout. You took too long. The server has moved on with its life.",
"Timeout. The server waited. And waited. And got tired of waiting.",
"Request timeout. The server gave up. You should probably do the same.",
"Too slow. The server timed out waiting for you to make up your mind.",
"Request timeout. The server has better things to do than wait for your request.",
"Timeout. The server lost interest. Can you blame it?"
],
429: [
"Slow down, turbo. The server isn't impressed by your enthusiasm.",
"Rate limit exceeded. You've been greedy. The server noticed.",
"The server is overwhelmed. By you. Specifically.",
"Rate limit. You're making too many requests. The server needs a break from you.",
"The server is putting you in timeout. Think about what you've done.",
"Rate limit exceeded. The server is judging your lack of patience.",
"Slow down. You're making too many requests. The server has trust issues now.",
"Rate limit exceeded. The server says 'no more'. It's not negotiable."
],
500: [
"We may or may not have broken something. Oops.",
"Something went wrong. We're pretending it's not our fault.",
"The server had a moment. It happens. Usually on Fridays.",
"The hamsters powering the server need a coffee break.",
"Something broke. The server is working on it. Or sleeping. Hard to tell.",
"The server made a mistake. We're all human. Well, the server isn't, but you know.",
"The server tried its best. Its best wasn't good enough today.",
"Something crashed. Probably our hopes and dreams.",
"The server is having an existential crisis. Give it a moment.",
"The server regrets to inform you that it regrets.",
"Something went wrong. The server is embarrassed. Please look away.",
"The server encountered an error while encountering an error. Meta."
],
502: [
"The upstream server is having a bad day. Join the club.",
"The server talked to another server. They're not speaking anymore.",
"The server reached out for help. The other server hung up.",
"The gateway is bad. The server would elaborate, but it's bad at explaining things.",
"The upstream server said 'no'. We're respecting its boundaries.",
"Two servers walked into a bar. Neither ordered anything useful.",
"The server called for backup. Backup didn't answer.",
"Communication failed between servers. Relationship status: it's complicated."
],
503: [
"The server is currently unavailable. Try again later. Or don't. Your choice.",
"The server is taking a mental health day.",
"The server is out of the office. It left a note. We can't read it.",
"The service is down. The server is using this time to reflect on its choices.",
"The server is temporarily unavailable. Permanently temporary.",
"The service is taking a break. Self-care is important, even for servers.",
"The server is busy. It's busy being unavailable.",
"The service is down for maintenance. Or questioning its purpose. Same thing."
],
504: [
"The server waited too long. It's done waiting.",
"The gateway timed out waiting for another server. Patience isn't infinite.",
"The server waited patiently. Then impatiently. Then gave up.",
"The gateway is having commitment issues. It won't wait forever.",
"The other server took too long. This server has places to be.",
"The gateway waited. And waited. Now it's leaving.",
"The server's patience expired before your request did.",
"Time's up. The gateway moved on. You should too."
]
};
const errorMessages = messages[errorCode] || [
`Error ${errorCode}. Something went wrong. The server is confused, and honestly, so are we.`
];
// Randomly select a message
const randomIndex = Math.floor(Math.random() * errorMessages.length);
return errorMessages[randomIndex];
}
+33
View File
@@ -0,0 +1,33 @@
// Error page script - loads error messages and updates the page
(function() {
const errorCode = parseInt(document.body.getAttribute('data-error-code') || '404', 10);
const script = document.createElement('script');
script.src = 'error-messages.js';
script.onload = function() {
const messageElement = document.getElementById('errorMessage');
if (messageElement && typeof getErrorMessage === 'function') {
messageElement.textContent = getErrorMessage(errorCode);
} else if (messageElement) {
// Fallback messages
const fallbacks = {
403: "Forbidden. This isn't the page you're looking for. Move along.",
404: "Page not found. It's gone. Vanished. Poof. Like my motivation on Monday.",
500: "Internal server error. We broke something. Oops."
};
messageElement.textContent = fallbacks[errorCode] || "Something went wrong.";
}
};
script.onerror = function() {
const messageElement = document.getElementById('errorMessage');
if (messageElement) {
const fallbacks = {
403: "Forbidden. This isn't the page you're looking for. Move along.",
404: "Page not found. It's gone. Vanished. Poof. Like my motivation on Monday.",
500: "Internal server error. We broke something. Oops."
};
messageElement.textContent = fallbacks[errorCode] || "Something went wrong.";
}
};
document.head.appendChild(script);
})();
+99
View File
@@ -0,0 +1,99 @@
// Figure out what time-based lie to tell visitors about their life choices
const determineGreet = hours => {
document.getElementById("greeting").innerText = `It's ${hours < 12 ? "Morning and Currently" : hours < 18 ? "Afternoon and Currently" : "Evening and Currently"}`;
};
// Show the relentless march of time
function displayTime(time) {
document.getElementById("time").innerHTML = time;
}
// Passive-aggressively suggest blog reading at 2-hour intervals (we're persistent, not pushy)
const determineBlogMessage = hours => {
let message;
if (hours >= 0 && hours < 2) {
message = "Late night reading? Check out my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 2 && hours < 4) {
message = "Insomniac hours? Peruse my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 4 && hours < 6) {
message = "Early bird? Catch up on my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 6 && hours < 8) {
message = "Coffee and a <a href='blog/index.html' class='blog-link'>Blog</a>?";
} else if (hours >= 8 && hours < 10) {
message = "Morning routine? Add my <a href='blog/index.html' class='blog-link'>Blog</a> to it";
} else if (hours >= 10 && hours < 12) {
message = "Mid-morning break? Read my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 12 && hours < 14) {
message = "Lunch break? Browse my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 14 && hours < 16) {
message = "Afternoon slump? Perk up with my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 16 && hours < 18) {
message = "It's a good time to read my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 18 && hours < 20) {
message = "Get comfy and read my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else if (hours >= 20 && hours < 22) {
message = "Wind down with my <a href='blog/index.html' class='blog-link'>Blog</a>";
} else {
message = "Nightcap? End your day with my <a href='blog/index.html' class='blog-link'>Blog</a>";
}
document.getElementById("blogMessage").innerHTML = message;
};
// Tick tock, tick tock - time marches on regardless of your location
setInterval(function () {
const today = new Date();
const time = today.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
displayTime(time);
}, 1000);
// Fetch the freshest posts to lure readers into the content trap
async function loadRecentPosts() {
try {
const response = await fetch('blog/api/posts.php');
const posts = await response.json();
const recentPostsContainer = document.getElementById("recentPosts");
// Only the top 3 - we're not monsters showing everything at once
const recentPosts = posts.slice(0, 3);
recentPosts.forEach(post => {
const postLink = document.createElement('a');
postLink.href = `blog/post.html?id=${post.id}`;
postLink.textContent = `${post.date} - ${post.title}`;
recentPostsContainer.appendChild(postLink);
});
} catch (error) {
console.error('Error loading blog posts:', error);
// Silence is golden when things break
}
}
// Boot up the whole circus when the page loads
window.addEventListener('load', (event) => {
const today = new Date();
const time = today.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Your timezone, your problem - we just display what your browser tells us
const localHours = today.getHours();
determineGreet(localHours);
determineBlogMessage(localHours);
displayTime(time);
loadRecentPosts();
// updateWeather(); // Weather is overrated anyway
});
// Keep harassing visitors about the blog every 60 seconds
setInterval(function () {
const today = new Date();
const localHours = today.getHours();
determineBlogMessage(localHours);
}, 60000);
+7
View File
@@ -0,0 +1,7 @@
// Apply theme immediately to prevent flash
(function() {
const theme = localStorage.getItem('theme') ||
(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
+130
View File
@@ -0,0 +1,130 @@
// Theme system - helping visitors avoid retinal damage since 2024
(function() {
'use strict';
const html = document.documentElement;
function getStoredTheme() {
// Check everywhere for theme preferences - we're thorough like that
if (typeof localStorage !== 'undefined') {
const stored = localStorage.getItem('theme');
if (stored === 'dark' || stored === 'light') {
return stored;
}
}
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'simple_style' && (value === 'dark' || value === 'light')) {
return value;
}
if (name === 'theme' && (value === 'dark' || value === 'light')) {
return value;
}
}
// When in doubt, trust the OS (they know what they're doing... probably)
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
function setThemeEverywhere(theme) {
// Scatter theme preferences like seeds in the wind - something's gotta stick
const cookieOptions = `path=/; max-age=${60 * 60 * 24 * 365 * 5}; SameSite=Lax`;
// localStorage for the modern folks
if (typeof localStorage !== 'undefined') {
localStorage.setItem('theme', theme);
}
// Cookies for SearXNG (it's picky about naming conventions)
document.cookie = `simple_style=${theme}; ${cookieOptions}`;
// More cookies - redundancy is our middle name
document.cookie = `theme=${theme}; ${cookieOptions}`;
// Data attribute for immediate gratification
html.setAttribute('data-theme', theme);
// Don't forget the icon - visual feedback matters
updateToggleIcon(theme);
}
function getTheme() {
return getStoredTheme();
}
function setTheme(theme) {
setThemeEverywhere(theme);
}
function updateToggleIcon(theme) {
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
const moonIcon = themeToggle.querySelector('.theme-icon-moon');
const sunIcon = themeToggle.querySelector('.theme-icon-sun');
if (moonIcon && sunIcon) {
if (theme === 'dark') {
moonIcon.style.display = 'none';
sunIcon.style.display = 'block';
} else {
moonIcon.style.display = 'block';
sunIcon.style.display = 'none';
}
}
}
}
function toggleTheme() {
const currentTheme = html.getAttribute('data-theme') || getTheme();
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
}
function initTheme() {
const theme = getTheme();
setTheme(theme);
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.removeEventListener('click', toggleTheme); // No double-clicking shenanigans
themeToggle.addEventListener('click', toggleTheme);
}
}
// Dance the cookie shuffle before searching - SearXNG has opinions
const searchForm = document.getElementById('search');
if (searchForm) {
searchForm.addEventListener('submit', function(e) {
const theme = getTheme();
// Out with the old cookie
document.cookie = 'theme=; path=/; max-age=0; SameSite=Lax';
// In with SearXNG's preferred flavor
const cookieOptions = `path=/; max-age=${60 * 60 * 24 * 365 * 5}; SameSite=Lax`;
document.cookie = `simple_style=${theme}; ${cookieOptions}`;
});
}
// Get this party started
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTheme);
} else {
initTheme();
}
// Listen for OS theme changes - stay responsive to user preferences
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', function(e) {
// Only auto-switch if they haven't explicitly chosen - consent matters
if (!localStorage.getItem('theme') && !document.cookie.includes('simple_style') && !document.cookie.includes('theme=')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
}
}
})();