Initial Commit

This commit is contained in:
klein panic
2024-10-20 17:49:24 -04:00
commit 36ab4a789e
33 changed files with 8858 additions and 0 deletions

BIN
public/images/space.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

27
public/js/blog.js Normal file
View File

@@ -0,0 +1,27 @@
fetch('/blog/blog-posts')
.then(response => response.json())
.then(posts => {
const blogContainer = document.getElementById('blog-container');
posts.forEach(post => {
// Create a clickable link wrapping the entire blog preview
const blogLink = document.createElement('a');
blogLink.href = `/blog/${post.slug}`;
blogLink.className = 'blog-link'; // Optional class for styling
blogLink.style.textDecoration = 'none'; // Remove the underline from the link
const blogPreview = document.createElement('div');
blogPreview.className = 'blog-preview';
// Create the blog preview with the title, date, and preview
blogPreview.innerHTML = `
<h3 class="post-title">${post.title}</h3> <!-- Apply the pink color to the title -->
<p class="post-date">Published on ${post.date}</p> <!-- Apply the amber color to the date -->
<p class="post-preview">${post.preview}...</p> <!-- Apply the amber color to the preview -->
`;
// Append the preview div inside the link, then add the link to the container
blogLink.appendChild(blogPreview);
blogContainer.appendChild(blogLink);
});
})
.catch(error => console.error('Error fetching blog posts:', error));

View File

@@ -0,0 +1,9 @@
document.addEventListener('DOMContentLoaded', function() {
const clickableContainer = document.querySelector('.clickable-container');
if (clickableContainer) {
clickableContainer.addEventListener('click', function() {
window.location.href = 'https://kleinpanic.com/';
});
}
});

72
public/js/contact.js Normal file
View File

@@ -0,0 +1,72 @@
document.querySelector('button').addEventListener('click', function(event) {
event.preventDefault(); // Prevent form submission
// Grab the form inputs
const name = document.querySelector('input[type="text"]').value.trim();
const email = document.querySelector('input[type="email"]').value.trim();
const message = document.querySelector('textarea').value.trim();
// Simple frontend validation
if (!name || !email || !message) {
alert("Please fill in all fields.");
return;
}
// Validate email format
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
alert("Please enter a valid email address.");
return;
}
// Disable the button to prevent spamming
const sendButton = document.querySelector('button');
sendButton.disabled = true;
sendButton.innerText = "Sending...";
// Prepare data to send
const data = {
name: name,
email: email,
message: message
};
// Send data to the backend via fetch API
fetch('/contact/submit-contact-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => {
// Check if the response is OK (status code 200)
if (!response.ok) {
// If response status is not ok, attempt to get error message
return response.json().then(errData => {
throw new Error(errData.message || "Unknown error occurred.");
});
}
return response.json(); // Parse the success response JSON
})
.then(data => {
if (data.success) {
// Show success message and reset the form
alert("Message sent successfully! Thank you for reaching out.");
document.querySelector('form').reset(); // Reset the form after successful submission
} else {
// Show error message returned from server
alert(data.message || "There was an error sending your message. Please try again later.");
}
})
.catch((error) => {
// Log the error and display the error message to the user
console.error('Error:', error);
alert(error.message || "There was an error sending your message. Please try again later.");
})
.finally(() => {
// Always re-enable the button and reset the button text
sendButton.disabled = false;
sendButton.innerText = "Send Message";
});
});

64
public/js/projects.js Normal file
View File

@@ -0,0 +1,64 @@
async function fetchProjects() {
let repos = [];
let page = 1;
let perPage = 100;
try {
// Fetch all repositories using pagination
while (true) {
const response = await fetch(`/projects/fetch?page=${page}&per_page=${perPage}`);
const pageRepos = await response.json();
if (pageRepos.length === 0) {
break; // No more repos to fetch
}
repos = repos.concat(pageRepos);
page++;
}
// Function to truncate text with "..." only if necessary
function truncateText(text, maxLength) {
return text.length > maxLength ? text.slice(0, maxLength - 3) + '...' : text;
}
// Function to pad text with spaces to ensure proper column alignment
function padText(text, length) {
return text.padEnd(length, ' ');
}
// Function to dynamically pad the repo_id (install command) for alignment
function padRepoId(index) {
const id = index + 1; // Repo index starts from 1
return `/install.sh?repo_id=${id}`; // Removed the "example.com" part
}
// Column widths to match the HTML header size
const maxNameLength = 24; // Adjusted to ensure proper width
const maxDescriptionLength = 63; // Adjusted for consistency
const maxInstallCommandLength = 30; // Adjusted for the install command path
// Build table rows (without the header since it's in HTML)
const tableContent = repos.map((repo, index) => {
const name = padText(truncateText(repo.name || 'Unknown', maxNameLength), maxNameLength); // Pad and truncate name
const description = padText(truncateText(repo.description || 'No description', maxDescriptionLength), maxDescriptionLength); // Pad and truncate description
// Make the project name clickable, linking to the GitHub URL
const clickableName = `<a href="${repo.html_url}" class="clickable-repo" target="_blank">${name}</a>`;
// Curl install command dynamically generated based on the index with proper padding
const curlInstall = padText(padRepoId(index), maxInstallCommandLength);
// Return the formatted ASCII table row with the clickable project name
return `| ${clickableName} | ${description} | ${curlInstall} |`;
}).join('\n');
// Add the table rows to the pre-existing header and footer in HTML
document.getElementById('projects-table-content').innerHTML = tableContent; // Insert as HTML to preserve the clickable links
} catch (error) {
console.error('Error fetching projects:', error);
}
}
// Fetch projects when the page loads
fetchProjects();

6
public/logo.txt Normal file
View File

@@ -0,0 +1,6 @@
 _ ___ _ _____ _
| |/ / | (_) | __ \ (_)
| ' /| | ___  _ _ __ | |__) |_ _ _ __  _ ___
|  < | |/ _ \| | '_ \| ___/ _` | '_ \| |/ __|
| . \| | __/| | | | | | | (_| | | | | | (__ 
|_|\_\_|\___||_|_| |_|_| \__,_|_| |_|_|\___|

57
public/scripts/install.sh Normal file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
# A function to handle error messages and exit
error_exit() {
echo "$1" >&2
exit 1
}
# Check if repo_id was passed as an argument
if [ -z "$1" ]; then
error_exit "Error: No repo_id provided. Usage: curl https://example.com/install.sh?repo_id=X | sh"
fi
# Function to check if a command is installed
check_command() {
if ! command -v "$1" &> /dev/null; then
error_exit "Error: '$1' is not installed. Please install it and try again."
fi
}
# Check if essential dependencies are installed
check_command "curl"
check_command "git"
check_command "jq"
# GitHub API endpoint to fetch repos for a specific user (replace 'kleinpanic' with your GitHub username)
github_api_url="https://api.github.com/users/kleinpanic/repos"
# Fetch the list of repositories from GitHub
repos=$(curl -s "$github_api_url" | jq -r '.[] | "\(.id) \(.clone_url)"')
# Map repo_id to the GitHub repo URL by searching the fetched repo list
repo_url=$(echo "$repos" | grep "^$1 " | awk '{print $2}')
# Check if a valid repo_url was found for the given repo_id
if [ -z "$repo_url" ]; then
error_exit "Error: Invalid repo_id. No project found for repo_id=$1"
fi
# Clone the repository
echo "Cloning project from $repo_url ..."
git clone "$repo_url" || error_exit "Error: Failed to clone repository."
# Navigate into the project directory (assuming the repo name matches the project)
repo_name=$(basename "$repo_url" .git)
cd "$repo_name" || error_exit "Error: Failed to enter project directory."
# Perform any necessary installation steps here
if [ -f "install.sh" ]; then
echo "Running project-specific install script..."
bash install.sh || error_exit "Error: Project install script failed."
else
echo "No specific install script found. Installation complete."
fi
# Output success message
echo "Project installed successfully."

285
public/style.css Normal file
View File

@@ -0,0 +1,285 @@
/* Body styling */
body {
font-family: 'Courier New', Courier, monospace;
background-color: #000; /* Black background */
color: #fff; /* White text */
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
min-height: 100vh;
overflow-y: auto;
background: radial-gradient(circle, rgba(0, 0, 0, 0.9), #111);
scroll-behavior: smooth; /* Smooth scrolling for a better experience */
}
body .pink-text {
color: #ff4081 !important;
}
body .amber-text {
color: #ffaa00 !important;
}
/* Smooth transitions for all elements */
* {
transition: all 0.3s ease;
}
/* Centering the ASCII table and header */
.ascii-table-container {
display: flex;
justify-content: center;
width: 100%;
}
.ascii-table-wrapper {
width: 100%;
max-width: 1200px; /* Set a max width for the container */
margin: 0 auto;
padding: 20px;
text-align: center;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 12px;
box-shadow: 0 6px 12px rgba(255, 255, 255, 0.15);
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
/* Add shadow and scale effect on hover */
.ascii-table-wrapper:hover {
box-shadow: 0 12px 24px rgba(255, 255, 255, 0.3);
transform: scale(1.02);
}
/* ASCII header with gradient */
.ascii-header pre {
text-align: center;
font-size: 16px;
white-space: pre;
background: linear-gradient(135deg, #ff4081, #ff8c00, #ffd700, #00fa9a, #1e90ff, #ff4081);
-webkit-background-clip: text;
color: transparent;
animation: gradient-animation 6s ease infinite;
word-wrap: break-word; /* Ensure text doesn't overflow */
}
/* Gradient Animation */
@keyframes gradient-animation {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Title styling */
h2 {
color: #ff4081;
font-size: 28px;
margin: 10px 0;
letter-spacing: 2px;
text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.5);
}
/* ASCII table styling */
.ascii-table {
width: 100%;
max-width: 1200px;
margin: 20px 0;
padding: 0;
font-size: 14px;
table-layout: fixed;
border-collapse: collapse;
white-space: pre-wrap;
word-wrap: break-word;
border-radius: 8px; /* Rounded corners */
overflow: hidden; /* Ensure the table remains inside the container */
}
/* Table rows and cells */
.ascii-table pre {
font-size: 14px;
color: #f0f0f0;
background-color: rgba(255, 255, 255, 0.05);
padding: 10px 15px;
border-radius: 8px;
line-height: 1.5;
text-align: left;
white-space: pre-wrap;
}
/* Even out columns in the table */
.ascii-table td, .ascii-table th {
padding: 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
text-align: left;
vertical-align: top;
word-wrap: break-word;
}
/* Adjust column widths */
.ascii-table th:nth-child(1), .ascii-table td:nth-child(1) {
width: 25%;
}
.ascii-table th:nth-child(2), .ascii-table td:nth-child(2) {
width: 50%;
}
.ascii-table th:nth-child(3), .ascii-table td:nth-child(3) {
width: 25%;
}
/* Styling for clickable links */
a.clickable-repo {
color: #1e90ff;
text-decoration: none;
font-weight: bold;
transition: color 0.3s ease, background-color 0.3s ease;
}
a.clickable-repo:hover {
color: #fff;
background-color: #ff4081;
border-radius: 5px;
padding: 2px 5px;
}
/* Install command style */
.install-command {
font-family: monospace;
background-color: rgba(255, 64, 129, 0.1);
padding: 5px;
border-radius: 4px;
word-break: break-all;
}
/* Hover effect for table */
.ascii-table pre:hover {
background-color: rgba(255, 64, 129, 0.2);
box-shadow: 0 6px 12px rgba(255, 64, 129, 0.2);
}
/* Profile circle inside the box with hover effect */
.ascii-banner pre a {
color: inherit;
text-shadow: none;
}
.ascii-banner pre a:hover {
background-color: rgba(255, 255, 255, 0.2);
padding: 5px 15px;
border-radius: 10px;
}
/* Horizontal line separator */
.ascii-line {
width: 80%;
margin: 20px auto;
border: none;
height: 2px;
background: linear-gradient(to right, rgba(255, 64, 129, 0), rgba(255, 64, 129, 1), rgba(255, 64, 129, 0));
}
/* Center the entire container */
.ascii-header, .ascii-table, .ascii-banner {
text-align: center;
box-shadow: 0 6px 12px rgba(255, 255, 255, 0.15);
padding: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 12px;
transition: transform 0.5s ease, box-shadow 0.5s ease;
}
/* Hover effect for entire sections */
.ascii-header:hover, .ascii-table:hover, .ascii-banner:hover {
transform: scale(1.02);
box-shadow: 0 8px 24px rgba(255, 255, 255, 0.3);
}
/* Buttons styling */
button, input[type="submit"] {
background-color: #ff4081;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-family: inherit;
font-size: 16px;
text-transform: uppercase;
letter-spacing: 1px;
}
button:hover, input[type="submit"]:hover {
background-color: #1e90ff;
}
/* Form fields style */
input[type="text"], input[type="email"], textarea {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 10px;
margin: 10px 0;
color: #fff;
width: 100%;
max-width: 600px;
font-size: 16px;
border-radius: 5px;
}
/* Focus state for form fields */
input[type="text"]:focus, input[type="email"]:focus, textarea:focus {
border-color: #ff4081;
box-shadow: 0 0 10px rgba(255, 64, 129, 0.5);
outline: none;
}
/* Table adjustments for smaller screens */
@media (max-width: 768px) {
.ascii-table {
font-size: 12px;
}
.ascii-table th:nth-child(1), .ascii-table td:nth-child(1) {
width: 30%;
}
.ascii-table th:nth-child(2), .ascii-table td:nth-child(2) {
width: 45%;
}
.ascii-table th:nth-child(3), .ascii-table td:nth-child(3) {
width: 25%;
}
/* Hide ASCII art on smaller screens */
.ascii-art-background {
display: none;
}
button, input[type="submit"] {
font-size: 14px;
}
}
/* Larger screens adjustments */
@media (min-width: 1024px) {
.ascii-table pre {
font-size: 14px;
}
button, input[type="submit"] {
font-size: 18px;
}
}