diff --git a/assets/css/tailwind.css b/assets/css/tailwind.css
index ed99427..54cede7 100644
--- a/assets/css/tailwind.css
+++ b/assets/css/tailwind.css
@@ -36,6 +36,8 @@ html[data-theme='light'] {
--text-tertiary: var(--color-gray-700);
}
+[x-cloak] { display: none !important; }
+
@layer base {
body {
background-color: var(--bg-primary);
@@ -66,6 +68,10 @@ html[data-theme='light'] {
}
}
+.kg-image-card img, .post-content img {
+ @apply cursor-pointer transition-opacity duration-200 hover:opacity-90;
+}
+
@keyframes fadeInUp {
from {
opacity: 0;
diff --git a/assets/js/main.js b/assets/js/main.js
index 7eba9c8..b7b0c02 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,216 +1,35 @@
// Main JavaScript file for Palina theme
+// Using HTMX and Alpine.js for enhanced functionality
+
document.addEventListener('DOMContentLoaded', () => {
console.log('Palina theme loaded!');
- const htmlElement = document.documentElement;
- // Remove 'hidden' class from html to prevent FOUC
- htmlElement.classList.remove('hidden');
-
- // Staggered fade-in animation for post grid (initial load)
- const initialGridItems = document.querySelectorAll('.post-grid-item');
- initialGridItems.forEach((item, index) => {
- item.style.animationDelay = `${index * 100}ms`;
- item.classList.add('animate-fadeInUp');
- });
-
- // Lightbox for post images
- const lightbox = document.getElementById('lightbox');
- if (lightbox) {
- const lightboxImage = document.getElementById('lightbox-image');
- const lightboxClose = document.getElementById('lightbox-close');
-
- const setupLightboxImage = (image) => {
- image.style.cursor = 'pointer';
- image.addEventListener('click', () => {
- lightbox.classList.remove('hidden');
- lightbox.classList.add('show');
- lightboxImage.src = image.src;
- });
- };
-
- // Initial setup for existing images
- document.querySelectorAll('.kg-image-card img, .post-content img').forEach(setupLightboxImage);
-
-
- lightboxClose.addEventListener('click', () => {
- lightbox.classList.add('hidden');
- lightbox.classList.remove('show');
- lightboxImage.src = '';
+ // Staggered fade-in animation for post grid
+ const animateGridItems = (container = document) => {
+ const gridItems = container.querySelectorAll('.post-grid-item:not(.animated)');
+ gridItems.forEach((item, index) => {
+ item.style.animationDelay = `${index * 100}ms`;
+ item.classList.add('animate-fadeInUp');
+ item.classList.add('animated');
});
-
- lightbox.addEventListener('click', (e) => {
- if (e.target.id !== 'lightbox-image') {
- lightbox.classList.add('hidden');
- lightbox.classList.remove('show');
- lightboxImage.src = '';
- }
- });
- }
-
- // Theme Switcher
- const themeToggle = document.getElementById('theme-toggle');
- const sunIcon = document.getElementById('theme-toggle-sun-icon');
- const moonIcon = document.getElementById('theme-toggle-moon-icon');
-
- const setThemeIcons = (theme) => {
- if (sunIcon && moonIcon) {
- if (theme === 'dark') {
- sunIcon.classList.remove('hidden');
- moonIcon.classList.add('hidden');
- } else {
- sunIcon.classList.add('hidden');
- moonIcon.classList.remove('hidden');
- }
- }
};
- const currentTheme = localStorage.getItem('theme');
- if (currentTheme) {
- htmlElement.setAttribute('data-theme', currentTheme);
- setThemeIcons(currentTheme);
- } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
- // Default to light theme if system preference is light
- htmlElement.setAttribute('data-theme', 'light');
- setThemeIcons('light');
- } else {
- // Default to dark theme if no preference
- htmlElement.setAttribute('data-theme', 'dark');
- setThemeIcons('dark');
- }
+ // Initial animation
+ animateGridItems();
- if (themeToggle) {
- themeToggle.addEventListener('click', () => {
- let newTheme = htmlElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
- htmlElement.setAttribute('data-theme', newTheme);
- localStorage.setItem('theme', newTheme);
- setThemeIcons(newTheme);
- });
- }
-
- // Mobile Menu
- const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
- const mobileMenuClose = document.getElementById('mobile-menu-close');
- const mobileMenu = document.getElementById('mobile-menu');
-
- if (mobileMenuToggle && mobileMenu && mobileMenuClose) {
- mobileMenuToggle.addEventListener('click', () => {
- mobileMenu.classList.remove('-translate-x-full');
- mobileMenu.classList.add('translate-x-0');
- });
-
- mobileMenuClose.addEventListener('click', () => {
- mobileMenu.classList.remove('translate-x-0');
- mobileMenu.classList.add('-translate-x-full');
- });
-
- // Close menu if a link is clicked
- const mobileNavLinks = mobileMenu.querySelectorAll('a');
- mobileNavLinks.forEach(link => {
- link.addEventListener('click', () => {
- mobileMenu.classList.remove('translate-x-0');
- mobileMenu.classList.add('-translate-x-full');
- });
- });
- }
-
- // Infinite Scroll
- const postsContainer = document.getElementById('posts-container');
- const loadingSpinner = document.getElementById('loading-spinner');
-
- if (postsContainer && loadingSpinner) {
- let currentPage = 1;
- let isLoading = false;
- let hasMorePosts = true;
-
- // Retrieve API key and URL from global GhostConfig object
- if (typeof window.GhostConfig === 'undefined' || !window.GhostConfig.ghostApiKey || !window.GhostConfig.ghostApiUrl) {
- console.error('Ghost Content API Key or URL not found in window.GhostConfig. Infinite scroll will not work.');
- return;
+ // Re-run animation and other setup when HTMX settles new content
+ document.body.addEventListener('htmx:afterSettle', (event) => {
+ // If the settled element is the posts-container or contains grid items
+ if (event.detail.target.id === 'posts-container' || event.detail.target.querySelector('.post-grid-item')) {
+ animateGridItems(event.detail.target);
}
- const GHOST_API_KEY = window.GhostConfig.ghostApiKey;
- const GHOST_API_URL = window.GhostConfig.ghostApiUrl;
+ // If it was a full page boost, we might need to reset some things
+ if (event.detail.boosted) {
+ animateGridItems();
+ }
+ });
- const fetchPosts = async () => {
- if (isLoading || !hasMorePosts) return;
-
- isLoading = true;
- loadingSpinner.classList.remove('hidden');
-
- currentPage++;
- const postsPerPage = 12; // Matches posts_per_page in package.json config
- const url = `${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=${postsPerPage}&page=${currentPage}&include=tags,authors`;
-
- try {
- const response = await fetch(url);
- const data = await response.json();
-
- const newPosts = data.posts;
- const pagination = data.meta.pagination;
-
- if (newPosts.length > 0) {
- newPosts.forEach((post, index) => {
- const article = document.createElement('article');
- article.className = 'post-grid-item opacity-0 relative bg-[var(--bg-secondary)] rounded-lg shadow-lg overflow-hidden group';
-
- // Basic rendering of a post for infinite scroll.
- // This should ideally match the structure in index.hbs
- // For simplicity, I'm reconstructing it here.
- article.innerHTML = `
-
- ${post.feature_image ? `
-
- ` : `
-
${post.title}
-