This commit is contained in:
Dev
2025-09-15 04:02:11 +03:00
commit fc86288f06
24 changed files with 2938 additions and 0 deletions

131
web/static/js/main.js Normal file
View File

@@ -0,0 +1,131 @@
class BlogApp {
constructor() {
this.init();
}
init() {
this.setupSmoothScrolling();
this.setupLazyLoading();
this.setupSearch();
this.setupAnimations();
}
setupSmoothScrolling() {
const links = document.querySelectorAll('a[href^="#"]');
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = document.querySelector(link.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
}
setupLazyLoading() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
}
setupSearch() {
const searchInput = document.getElementById('search-input');
if (searchInput) {
searchInput.addEventListener('input', this.debounce((e) => {
this.performSearch(e.target.value);
}, 300));
}
}
setupAnimations() {
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('animate-in');
}
});
}, observerOptions);
const animatedElements = document.querySelectorAll('.post-card, .post, .page');
animatedElements.forEach(el => {
el.classList.add('animate-ready');
observer.observe(el);
});
}
performSearch(query) {
if (query.length < 2) return;
const posts = document.querySelectorAll('.post-card');
posts.forEach(post => {
const title = post.querySelector('.post-title').textContent.toLowerCase();
const excerpt = post.querySelector('.post-excerpt').textContent.toLowerCase();
const tags = Array.from(post.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase());
const matches = title.includes(query.toLowerCase()) ||
excerpt.includes(query.toLowerCase()) ||
tags.some(tag => tag.includes(query.toLowerCase()));
post.style.display = matches ? 'block' : 'none';
});
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
}
document.addEventListener('DOMContentLoaded', () => {
new BlogApp();
});
const style = document.createElement('style');
style.textContent = `
.animate-ready {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-in {
opacity: 1;
transform: translateY(0);
}
.lazy {
opacity: 0;
transition: opacity 0.3s ease;
}
.lazy.loaded {
opacity: 1;
}
`;
document.head.appendChild(style);

62
web/static/js/theme.js Normal file
View File

@@ -0,0 +1,62 @@
class ThemeManager {
constructor() {
this.theme = this.getStoredTheme() || this.getSystemTheme();
this.init();
}
init() {
this.applyTheme(this.theme);
this.bindEvents();
}
getStoredTheme() {
return localStorage.getItem('theme');
}
getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
this.updateThemeIcon(theme);
localStorage.setItem('theme', theme);
}
updateThemeIcon(theme) {
const themeIcon = document.querySelector('.theme-icon');
if (themeIcon) {
themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
}
}
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
this.applyTheme(this.theme);
this.setCookie('theme', this.theme, 365);
}
setCookie(name, value, days) {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`;
}
bindEvents() {
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => this.toggleTheme());
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!this.getStoredTheme()) {
this.theme = e.matches ? 'dark' : 'light';
this.applyTheme(this.theme);
}
});
}
}
document.addEventListener('DOMContentLoaded', () => {
new ThemeManager();
});