first commit

This commit is contained in:
spetznas
2026-04-13 11:39:05 +02:00
commit b0124c3102
53 changed files with 5999 additions and 0 deletions

339
public/js/kawaii.js Executable file
View File

@@ -0,0 +1,339 @@
/**
* Kawaii Theme JavaScript
* Modern and cool interactions for Hugo theme
*/
(function() {
'use strict';
// Theme toggle functionality
function initThemeToggle() {
const themeToggle = document.querySelector('.kawaii-theme-toggle');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
// Get saved theme or default to system preference
let currentTheme = localStorage.getItem('kawaii-theme') || (prefersDark.matches ? 'dark' : 'light');
// Apply theme
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('kawaii-theme', theme);
currentTheme = theme;
}
// Initialize theme
applyTheme(currentTheme);
// Theme toggle click handler
if (themeToggle) {
themeToggle.addEventListener('click', () => {
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
applyTheme(newTheme);
// Add a small animation effect
document.body.style.transition = 'background-color 0.3s ease';
setTimeout(() => {
document.body.style.transition = '';
}, 300);
});
}
// Listen for system theme changes
prefersDark.addEventListener('change', (e) => {
if (!localStorage.getItem('kawaii-theme')) {
applyTheme(e.matches ? 'dark' : 'light');
}
});
}
// Mobile menu functionality
function initMobileMenu() {
const mobileToggle = document.querySelector('.kawaii-mobile-toggle');
const navMenu = document.querySelector('.kawaii-nav-menu');
if (mobileToggle && navMenu) {
mobileToggle.addEventListener('click', () => {
const isExpanded = mobileToggle.getAttribute('aria-expanded') === 'true';
mobileToggle.setAttribute('aria-expanded', !isExpanded);
navMenu.classList.toggle('kawaii-nav-open');
// Animate hamburger
mobileToggle.classList.toggle('kawaii-mobile-active');
});
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (!mobileToggle.contains(e.target) && !navMenu.contains(e.target)) {
mobileToggle.setAttribute('aria-expanded', 'false');
navMenu.classList.remove('kawaii-nav-open');
mobileToggle.classList.remove('kawaii-mobile-active');
}
});
}
}
// Smooth scroll for anchor links
function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const target = document.querySelector(this.getAttribute('href'));
if (target) {
e.preventDefault();
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
}
// Add scroll-based header styling
function initScrollHeader() {
const header = document.querySelector('.kawaii-header');
let lastScroll = 0;
if (header) {
window.addEventListener('scroll', () => {
const currentScroll = window.pageYOffset;
if (currentScroll > 100) {
header.classList.add('kawaii-header-scrolled');
} else {
header.classList.remove('kawaii-header-scrolled');
}
// Hide/show header on scroll
if (currentScroll > lastScroll && currentScroll > 200) {
header.classList.add('kawaii-header-hidden');
} else {
header.classList.remove('kawaii-header-hidden');
}
lastScroll = currentScroll;
});
}
}
// Animate elements on scroll
function initScrollAnimations() {
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('kawaii-animate-in');
}
});
}, observerOptions);
// Observe cards and articles
document.querySelectorAll('.kawaii-post-card, .kawaii-feature-card, .kawaii-article').forEach(el => {
observer.observe(el);
});
}
// Add reading progress indicator for articles
function initReadingProgress() {
const article = document.querySelector('.kawaii-article');
if (!article) return;
const progressBar = document.createElement('div');
progressBar.className = 'kawaii-reading-progress';
progressBar.innerHTML = '<div class="kawaii-reading-progress-bar"></div>';
document.body.appendChild(progressBar);
const progressBarFill = progressBar.querySelector('.kawaii-reading-progress-bar');
window.addEventListener('scroll', () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = (scrollTop / docHeight) * 100;
progressBarFill.style.width = Math.min(100, Math.max(0, scrollPercent)) + '%';
});
}
// Add copy code functionality
function initCodeCopy() {
document.querySelectorAll('pre code').forEach(codeBlock => {
const pre = codeBlock.parentElement;
const button = document.createElement('button');
button.className = 'kawaii-copy-code';
button.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
Copy
`;
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(codeBlock.textContent);
button.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="20,6 9,17 4,12"></polyline>
</svg>
Copied!
`;
setTimeout(() => {
button.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
Copy
`;
}, 2000);
} catch (err) {
console.error('Failed to copy code:', err);
}
});
pre.style.position = 'relative';
pre.appendChild(button);
});
}
// Search functionality (basic implementation)
function initSearch() {
const searchToggle = document.querySelector('.kawaii-search-toggle');
if (!searchToggle) return;
const searchModal = document.createElement('div');
searchModal.className = 'kawaii-search-modal';
searchModal.innerHTML = `
<div class="kawaii-search-overlay">
<div class="kawaii-search-container">
<input type="text" class="kawaii-search-input" placeholder="Search posts..." />
<div class="kawaii-search-results"></div>
<button class="kawaii-search-close">×</button>
</div>
</div>
`;
document.body.appendChild(searchModal);
const searchInput = searchModal.querySelector('.kawaii-search-input');
const searchResults = searchModal.querySelector('.kawaii-search-results');
const searchClose = searchModal.querySelector('.kawaii-search-close');
searchToggle.addEventListener('click', () => {
searchModal.classList.add('kawaii-search-open');
searchInput.focus();
});
searchClose.addEventListener('click', () => {
searchModal.classList.remove('kawaii-search-open');
});
searchModal.addEventListener('click', (e) => {
if (e.target === searchModal.querySelector('.kawaii-search-overlay')) {
searchModal.classList.remove('kawaii-search-open');
}
});
// Simple search implementation (you might want to integrate with a search service)
let searchData = [];
// Collect searchable content
document.querySelectorAll('.kawaii-post-card').forEach(card => {
const title = card.querySelector('.kawaii-card-title a')?.textContent || '';
const description = card.querySelector('.kawaii-card-description')?.textContent || '';
const link = card.querySelector('.kawaii-card-title a')?.href || '';
if (title && link) {
searchData.push({ title, description, link });
}
});
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase().trim();
if (query.length < 2) {
searchResults.innerHTML = '';
return;
}
const filteredResults = searchData.filter(item =>
item.title.toLowerCase().includes(query) ||
item.description.toLowerCase().includes(query)
);
if (filteredResults.length === 0) {
searchResults.innerHTML = '<div class="kawaii-search-no-results">No results found</div>';
} else {
searchResults.innerHTML = filteredResults.map(item => `
<a href="${item.link}" class="kawaii-search-result">
<div class="kawaii-search-result-title">${item.title}</div>
<div class="kawaii-search-result-description">${item.description}</div>
</a>
`).join('');
}
});
// Handle keyboard navigation
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
searchModal.classList.remove('kawaii-search-open');
}
});
}
// Add floating action button for back to top
function initBackToTop() {
const backToTop = document.createElement('button');
backToTop.className = 'kawaii-back-to-top';
backToTop.innerHTML = `
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5,12 12,5 19,12"></polyline>
</svg>
`;
backToTop.setAttribute('aria-label', 'Back to top');
document.body.appendChild(backToTop);
backToTop.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
window.addEventListener('scroll', () => {
if (window.scrollY > 300) {
backToTop.classList.add('kawaii-back-to-top-visible');
} else {
backToTop.classList.remove('kawaii-back-to-top-visible');
}
});
}
// Initialize everything when DOM is loaded
function init() {
initThemeToggle();
initMobileMenu();
initSmoothScroll();
initScrollHeader();
initScrollAnimations();
initReadingProgress();
initCodeCopy();
initSearch();
initBackToTop();
}
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();