Files
orthjugend.de/public/js/kawaii.js
2026-04-13 11:39:05 +02:00

339 lines
12 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();
}
})();