mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 12:31:09 +00:00
Добавление главной страницы о общей статистикой
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
const svg = document.getElementById("bookSvg");
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
const svgWidth = 200;
|
||||
const svgHeight = 250;
|
||||
const lineCount = 5;
|
||||
const lineDelay = 16;
|
||||
const bookWidth = 120;
|
||||
const bookHeight = 180;
|
||||
const bookX = (svgWidth - bookWidth) / 2;
|
||||
const bookY = (svgHeight - bookHeight) / 2;
|
||||
const desiredLineSpacing = 8;
|
||||
const baseLineWidth = 2;
|
||||
const maxLineWidth = 10;
|
||||
const maxLineHeight = bookHeight - 24;
|
||||
const innerPaddingX = 10;
|
||||
const appearStagger = 8;
|
||||
|
||||
let lineSpacing;
|
||||
if (lineCount > 1) {
|
||||
const maxSpan = Math.max(0, bookWidth - maxLineWidth - 2 * innerPaddingX);
|
||||
const wishSpan = desiredLineSpacing * (lineCount - 1);
|
||||
const realSpan = Math.min(wishSpan, maxSpan);
|
||||
lineSpacing = realSpan / (lineCount - 1);
|
||||
} else {
|
||||
lineSpacing = 0;
|
||||
}
|
||||
const linesSpan = lineSpacing * (lineCount - 1);
|
||||
|
||||
const rightBase = bookX + bookWidth - innerPaddingX - maxLineWidth;
|
||||
const lineStartX = rightBase - linesSpan;
|
||||
|
||||
const leftLimit = bookX + innerPaddingX;
|
||||
|
||||
let phase = 0;
|
||||
let time = 0;
|
||||
|
||||
const baseAppearDuration = 40;
|
||||
const appearDuration = baseAppearDuration + (lineCount - 1) * appearStagger;
|
||||
|
||||
const baseFlipDuration = 120;
|
||||
const flipDuration = baseFlipDuration + (lineCount - 1) * lineDelay;
|
||||
|
||||
const baseDisappearDuration = 40;
|
||||
const disappearDuration =
|
||||
baseDisappearDuration + (lineCount - 1) * appearStagger;
|
||||
|
||||
const pauseDuration = 30;
|
||||
|
||||
const book = document.createElementNS(NS, "rect");
|
||||
book.setAttribute("x", bookX);
|
||||
book.setAttribute("y", bookY);
|
||||
book.setAttribute("width", bookWidth);
|
||||
book.setAttribute("height", bookHeight);
|
||||
book.setAttribute("fill", "#374151");
|
||||
book.setAttribute("rx", "4");
|
||||
svg.appendChild(book);
|
||||
|
||||
const lines = [];
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const line = document.createElementNS(NS, "rect");
|
||||
line.setAttribute("fill", "#ffffff");
|
||||
line.setAttribute("rx", "1");
|
||||
svg.appendChild(line);
|
||||
|
||||
const baseX = lineStartX + i * lineSpacing;
|
||||
const targetX = leftLimit + i * lineSpacing;
|
||||
const moveDistance = baseX - targetX;
|
||||
|
||||
lines.push({
|
||||
el: line,
|
||||
baseX,
|
||||
targetX,
|
||||
moveDistance,
|
||||
currentX: baseX,
|
||||
width: baseLineWidth,
|
||||
height: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function easeInOutQuad(t) {
|
||||
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
||||
}
|
||||
|
||||
function easeOutQuad(t) {
|
||||
return 1 - (1 - t) * (1 - t);
|
||||
}
|
||||
|
||||
function easeInQuad(t) {
|
||||
return t * t;
|
||||
}
|
||||
|
||||
function updateLine(line) {
|
||||
const el = line.el;
|
||||
const centerY = bookY + bookHeight / 2;
|
||||
|
||||
el.setAttribute("x", line.currentX);
|
||||
el.setAttribute("y", centerY - line.height / 2);
|
||||
el.setAttribute("width", line.width);
|
||||
el.setAttribute("height", Math.max(0, line.height));
|
||||
}
|
||||
|
||||
function animateBook() {
|
||||
time++;
|
||||
|
||||
if (phase === 0) {
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const delay = (lineCount - 1 - i) * appearStagger;
|
||||
const localTime = Math.max(0, time - delay);
|
||||
const progress = Math.min(1, localTime / baseAppearDuration);
|
||||
const easedProgress = easeOutQuad(progress);
|
||||
|
||||
lines[i].height = maxLineHeight * easedProgress;
|
||||
lines[i].currentX = lines[i].baseX;
|
||||
lines[i].width = baseLineWidth;
|
||||
updateLine(lines[i]);
|
||||
}
|
||||
|
||||
if (time >= appearDuration) {
|
||||
phase = 1;
|
||||
time = 0;
|
||||
}
|
||||
} else if (phase === 1) {
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const delay = i * lineDelay;
|
||||
const localTime = Math.max(0, time - delay);
|
||||
const progress = Math.min(1, localTime / baseFlipDuration);
|
||||
|
||||
const moveProgress = easeInOutQuad(progress);
|
||||
lines[i].currentX = lines[i].baseX - lines[i].moveDistance * moveProgress;
|
||||
|
||||
const widthProgress =
|
||||
progress < 0.5
|
||||
? easeOutQuad(progress * 2)
|
||||
: 1 - easeInQuad((progress - 0.5) * 2);
|
||||
|
||||
lines[i].width =
|
||||
baseLineWidth + (maxLineWidth - baseLineWidth) * widthProgress;
|
||||
|
||||
updateLine(lines[i]);
|
||||
}
|
||||
|
||||
if (time >= flipDuration) {
|
||||
phase = 2;
|
||||
time = 0;
|
||||
}
|
||||
} else if (phase === 2) {
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const delay = (lineCount - 1 - i) * appearStagger;
|
||||
const localTime = Math.max(0, time - delay);
|
||||
const progress = Math.min(1, localTime / baseDisappearDuration);
|
||||
const easedProgress = easeInQuad(progress);
|
||||
|
||||
lines[i].height = maxLineHeight * (1 - easedProgress);
|
||||
updateLine(lines[i]);
|
||||
}
|
||||
|
||||
if (time >= disappearDuration + pauseDuration) {
|
||||
phase = 0;
|
||||
time = 0;
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
lines[i].currentX = lines[i].baseX;
|
||||
lines[i].width = baseLineWidth;
|
||||
lines[i].height = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animateBook);
|
||||
}
|
||||
|
||||
animateBook();
|
||||
|
||||
function animateCounter(element, target, duration = 2000) {
|
||||
const start = 0;
|
||||
const startTime = performance.now();
|
||||
|
||||
function update(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
const easedProgress = 1 - Math.pow(1 - progress, 3);
|
||||
const current = Math.floor(start + (target - start) * easedProgress);
|
||||
|
||||
element.textContent = current.toLocaleString("ru-RU");
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(update);
|
||||
} else {
|
||||
element.textContent = target.toLocaleString("ru-RU");
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const response = await fetch("/api/stats");
|
||||
if (!response.ok) {
|
||||
throw new Error("Ошибка загрузки статистики");
|
||||
}
|
||||
|
||||
const stats = await response.json();
|
||||
|
||||
setTimeout(() => {
|
||||
const booksEl = document.getElementById("stat-books");
|
||||
const authorsEl = document.getElementById("stat-authors");
|
||||
const genresEl = document.getElementById("stat-genres");
|
||||
const usersEl = document.getElementById("stat-users");
|
||||
|
||||
if (booksEl) {
|
||||
animateCounter(booksEl, stats.books, 1500);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (authorsEl) {
|
||||
animateCounter(authorsEl, stats.authors, 1500);
|
||||
}
|
||||
}, 150);
|
||||
|
||||
setTimeout(() => {
|
||||
if (genresEl) {
|
||||
animateCounter(genresEl, stats.genres, 1500);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
setTimeout(() => {
|
||||
if (usersEl) {
|
||||
animateCounter(usersEl, stats.users, 1500);
|
||||
}
|
||||
}, 450);
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error("Ошибка загрузки статистики:", error);
|
||||
|
||||
document.getElementById("stat-books").textContent = "—";
|
||||
document.getElementById("stat-authors").textContent = "—";
|
||||
document.getElementById("stat-genres").textContent = "—";
|
||||
document.getElementById("stat-users").textContent = "—";
|
||||
}
|
||||
}
|
||||
|
||||
function observeStatCards() {
|
||||
const cards = document.querySelectorAll(".stat-card");
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry, index) => {
|
||||
if (entry.isIntersecting) {
|
||||
setTimeout(() => {
|
||||
entry.target.classList.add("animate-fade-in");
|
||||
entry.target.style.opacity = "1";
|
||||
entry.target.style.transform = "translateY(0)";
|
||||
}, index * 100);
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1 },
|
||||
);
|
||||
|
||||
cards.forEach((card) => {
|
||||
card.style.opacity = "0";
|
||||
card.style.transform = "translateY(20px)";
|
||||
card.style.transition = "opacity 0.5s ease, transform 0.5s ease";
|
||||
observer.observe(card);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadStats();
|
||||
observeStatCards();
|
||||
});
|
||||
Reference in New Issue
Block a user