Initial Commit
This commit is contained in:
BIN
public/images/space.jpg
Normal file
BIN
public/images/space.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 707 KiB |
27
public/js/blog.js
Normal file
27
public/js/blog.js
Normal 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));
|
||||
9
public/js/clickableHeader.js
Normal file
9
public/js/clickableHeader.js
Normal 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
72
public/js/contact.js
Normal 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
64
public/js/projects.js
Normal 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
6
public/logo.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
[1m[38;5;203m [39m[38;5;198m_[39m[38;5;198m [39m[38;5;199m___[39m[38;5;199m [39m[38;5;164m_[39m[38;5;164m [39m[38;5;129m_____[39m[38;5;129m [39m[38;5;93m_[39m
|
||||
[38;5;198m| |[39m[38;5;199m/ /[39m[38;5;199m | [39m[38;5;164m(_)[39m[38;5;164m [39m[38;5;129m| __ \[39m[38;5;129m [39m[38;5;93m(_)[39m
|
||||
[38;5;199m| '[39m[38;5;164m /|[39m[38;5;164m | ___ [39m[38;5;129m _[39m[38;5;129m _ __ [39m[38;5;93m| |__) |[39m[38;5;93m_ _ [39m[38;5;63m_ __ [39m[38;5;63m _ [39m[38;5;33m___[39m
|
||||
[38;5;164m| [39m[38;5;129m < |[39m[38;5;129m |/ _ \[39m[38;5;93m| | '_ \[39m[38;5;63m| ___/[39m[38;5;63m _` |[39m[38;5;33m '_ \[39m[38;5;33m| |[39m[38;5;39m/ __|[39m
|
||||
[38;5;129m| .[39m[38;5;93m \|[39m[38;5;93m | __/[39m[38;5;63m| | | |[39m[38;5;33m | | |[39m[38;5;33m (_| |[39m[38;5;39m | |[39m[38;5;39m | | [39m[38;5;44m(__ [39m
|
||||
[38;5;93m|_|\[39m[38;5;63m_\_|[39m[38;5;63m\___|[39m[38;5;33m|_|_| |_|[39m[38;5;39m_| \__,_|[39m[38;5;44m_| |_|[39m[38;5;44m_|\___|[39m
|
||||
57
public/scripts/install.sh
Normal file
57
public/scripts/install.sh
Normal 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
285
public/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user