Initial commit: site code, layouts, content (images added separately)

Fixes duplicate placeholder cover image on the 3 newest blog posts,
broken aspect-ratio classes on the albums listing page, and a
non-responsive fixed sidebar on the chat page.
This commit is contained in:
Dr. Jones
2026-07-08 06:18:17 +00:00
commit d9961a76ad
80 changed files with 9438 additions and 0 deletions

403
static/js/membership.js Normal file
View File

@@ -0,0 +1,403 @@
/**
* Membership System Frontend
* Handles authentication, paywall checks, and download tracking
*/
const getApiBase = () => {
const host = window.location.hostname;
const isLocal = host === 'localhost' || host === '127.0.0.1' || host.startsWith('10.');
return isLocal ? 'http://' + host + ':5001' : window.location.origin.replace(/\/$/, '');
};
const API_BASE = getApiBase() + '/api';
let authState = {
authenticated: false,
user: null,
token: null
};
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
checkAuthStatus();
setupDownloadTracking();
setupContactForm();
});
/**
* Check authentication status
*/
async function checkAuthStatus() {
const token = localStorage.getItem('membership_token');
if (!token) {
updateAuthUI(false);
return;
}
try {
const response = await fetch(`${API_BASE}/auth/check`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
if (data.authenticated) {
authState = {
authenticated: true,
user: data,
token: token
};
updateAuthUI(true);
checkPaywallAccess();
} else {
localStorage.removeItem('membership_token');
updateAuthUI(false);
}
} catch (error) {
console.error('Auth check failed:', error);
updateAuthUI(false);
}
}
/**
* Update UI based on authentication state
*/
function updateAuthUI(authenticated) {
const loginElements = document.querySelectorAll('.membership-login');
const logoutElements = document.querySelectorAll('.membership-logout');
const memberElements = document.querySelectorAll('.membership-only');
const guestElements = document.querySelectorAll('.guest-only');
if (authenticated) {
loginElements.forEach(el => el.style.display = 'none');
logoutElements.forEach(el => el.style.display = 'block');
memberElements.forEach(el => el.style.display = 'block');
guestElements.forEach(el => el.style.display = 'none');
// Update user name display
const userNameElements = document.querySelectorAll('.user-name');
userNameElements.forEach(el => {
if (authState.user && authState.user.name) {
el.textContent = authState.user.name;
}
});
} else {
loginElements.forEach(el => el.style.display = 'block');
logoutElements.forEach(el => el.style.display = 'none');
memberElements.forEach(el => el.style.display = 'none');
guestElements.forEach(el => el.style.display = 'block');
}
}
/**
* Check paywall access and show/hide content
*/
async function checkPaywallAccess() {
if (!authState.authenticated) {
// Check download count for IP
try {
const response = await fetch(`${API_BASE}/download/count`);
const data = await response.json();
if (data.limit_reached || (data.count >= data.limit && !data.unlimited)) {
showPaywall();
}
} catch (error) {
console.error('Download count check failed:', error);
}
}
}
/**
* Show paywall message
*/
function showPaywall() {
const paywallElements = document.querySelectorAll('.paywall-message');
paywallElements.forEach(el => {
el.style.display = 'block';
});
const paywallContent = document.querySelectorAll('.paywall-content');
paywallContent.forEach(el => {
el.style.display = 'none';
});
}
/**
* Hide paywall message
*/
function hidePaywall() {
const paywallElements = document.querySelectorAll('.paywall-message');
paywallElements.forEach(el => {
el.style.display = 'none';
});
const paywallContent = document.querySelectorAll('.paywall-content');
paywallContent.forEach(el => {
el.style.display = 'block';
});
}
/**
* Setup download tracking
*/
function setupDownloadTracking() {
document.addEventListener('click', async function(e) {
const downloadLink = e.target.closest('a[download], a[href*="/library/"], a[href*="/pdfs/"]');
if (!downloadLink) return;
const filePath = downloadLink.getAttribute('href') || downloadLink.getAttribute('download');
if (!filePath) return;
// Check if user is authenticated
if (!authState.authenticated) {
// Check download count
try {
const response = await fetch(`${API_BASE}/download/count`);
const data = await response.json();
if (data.count >= data.limit && !data.unlimited) {
e.preventDefault();
alert('You have reached the free download limit. Please sign up for membership to download unlimited resources.');
window.location.href = '/membership/';
return;
}
} catch (error) {
console.error('Download check failed:', error);
}
}
// Track download
try {
await fetch(`${API_BASE}/download/track`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(authState.token ? { 'Authorization': `Bearer ${authState.token}` } : {})
},
body: JSON.stringify({ file_path: filePath })
});
} catch (error) {
console.error('Download tracking failed:', error);
// Don't prevent download if tracking fails
}
});
}
/**
* Setup contact form
*/
function setupContactForm() {
const contactForm = document.getElementById('contact-form');
if (!contactForm) return;
contactForm.addEventListener('submit', async function(e) {
e.preventDefault();
if (!authState.authenticated) {
alert('Please log in to submit the contact form.');
window.location.href = '/membership/';
return;
}
const formData = new FormData(contactForm);
const data = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message')
};
try {
const response = await fetch(`${API_BASE}/contact`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authState.token}`
},
body: JSON.stringify(data)
});
const result = await response.json();
if (response.ok) {
alert('Message sent successfully!');
contactForm.reset();
} else {
alert(result.error || 'Failed to send message');
}
} catch (error) {
console.error('Contact form error:', error);
alert('Failed to send message. Please try again.');
}
});
}
/**
* User login
*/
async function login(email, password) {
try {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('membership_token', data.token);
authState = {
authenticated: true,
user: data,
token: data.token
};
updateAuthUI(true);
hidePaywall();
return { success: true };
} else {
return { success: false, error: data.error };
}
} catch (error) {
return { success: false, error: 'Login failed. Please try again.' };
}
}
/**
* User logout
*/
async function logout() {
const token = localStorage.getItem('membership_token');
if (token) {
try {
await fetch(`${API_BASE}/auth/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
}
});
} catch (error) {
console.error('Logout error:', error);
}
}
localStorage.removeItem('membership_token');
authState = {
authenticated: false,
user: null,
token: null
};
updateAuthUI(false);
window.location.reload();
}
/**
* User registration
*/
async function register(email, password, name) {
try {
const response = await fetch(`${API_BASE}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password, name })
});
const data = await response.json();
if (response.ok) {
return { success: true, message: data.message };
} else {
return { success: false, error: data.error };
}
} catch (error) {
return { success: false, error: 'Registration failed. Please try again.' };
}
}
/**
* Submit testimonial
*/
async function submitTestimonial(content, authorName) {
if (!authState.authenticated) {
return { success: false, error: 'Please log in to submit a testimonial' };
}
try {
const response = await fetch(`${API_BASE}/testimonials`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authState.token}`
},
body: JSON.stringify({ content, author_name: authorName })
});
const data = await response.json();
if (response.ok) {
return { success: true, message: data.message };
} else {
return { success: false, error: data.error };
}
} catch (error) {
return { success: false, error: 'Failed to submit testimonial' };
}
}
/**
* Load testimonials
*/
async function loadTestimonials() {
try {
const response = await fetch(`${API_BASE}/testimonials`);
const data = await response.json();
if (response.ok) {
return { success: true, testimonials: data.testimonials };
} else {
return { success: false, error: data.error };
}
} catch (error) {
return { success: false, error: 'Failed to load testimonials' };
}
}
/**
* Check if the user has reached their download limit (guests only)
*/
async function checkDownloadLimit() {
if (authState.authenticated) {
document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'none');
document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'block');
document.querySelectorAll('.guest-only').forEach(el => el.style.display = 'none');
document.querySelectorAll('.membership-only').forEach(el => el.style.display = 'block');
return;
}
try {
const response = await fetch(`${API_BASE}/download/count`);
const data = await response.json();
if (data.count >= data.limit && !data.unlimited) {
document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'block');
document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'none');
} else {
document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'none');
document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'block');
}
} catch (error) {
console.error('Download limit check failed:', error);
}
}
// Export functions for use in Alpine.js or other scripts
window.MembershipAPI = {
login,
logout,
register,
submitTestimonial,
loadTestimonials,
checkAuthStatus,
checkDownloadLimit,
getAuthState: () => authState
};

0
static/pdfs/cleaning.pdf Normal file
View File

View File

View File

View File

0
static/pdfs/lighting.pdf Normal file
View File

View File

0
static/pdfs/pests.pdf Normal file
View File

View File

0
static/pdfs/seeds.pdf Normal file
View File

View File

5
static/robots.txt Normal file
View File

@@ -0,0 +1,5 @@
User-agent: *
Allow: /
Sitemap: https://hydrolord.thetempleofdoom.com/sitemap.xml