Добавление главной страницы о общей статистикой

This commit is contained in:
2025-12-20 01:18:47 +03:00
parent 64a46645c5
commit 961bf95af7
7 changed files with 800 additions and 122 deletions
+14 -8
View File
@@ -1,9 +1,15 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<?xml version="1.0" encoding="utf-8" ?>
<svg
width="800px"
height="800px"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.877014 7.49988C0.877014 3.84219 3.84216 0.877045 7.49985 0.877045C11.1575 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1575 14.1227 7.49985 14.1227C3.84216 14.1227 0.877014 11.1575 0.877014 7.49988ZM7.49985 1.82704C4.36683 1.82704 1.82701 4.36686 1.82701 7.49988C1.82701 8.97196 2.38774 10.3131 3.30727 11.3213C4.19074 9.94119 5.73818 9.02499 7.50023 9.02499C9.26206 9.02499 10.8093 9.94097 11.6929 11.3208C12.6121 10.3127 13.1727 8.97172 13.1727 7.49988C13.1727 4.36686 10.6328 1.82704 7.49985 1.82704ZM10.9818 11.9787C10.2839 10.7795 8.9857 9.97499 7.50023 9.97499C6.01458 9.97499 4.71624 10.7797 4.01845 11.9791C4.97952 12.7272 6.18765 13.1727 7.49985 13.1727C8.81227 13.1727 10.0206 12.727 10.9818 11.9787ZM5.14999 6.50487C5.14999 5.207 6.20212 4.15487 7.49999 4.15487C8.79786 4.15487 9.84999 5.207 9.84999 6.50487C9.84999 7.80274 8.79786 8.85487 7.49999 8.85487C6.20212 8.85487 5.14999 7.80274 5.14999 6.50487ZM7.49999 5.10487C6.72679 5.10487 6.09999 5.73167 6.09999 6.50487C6.09999 7.27807 6.72679 7.90487 7.49999 7.90487C8.27319 7.90487 8.89999 7.27807 8.89999 6.50487C8.89999 5.73167 8.27319 5.10487 7.49999 5.10487Z"
fill="#000000"
/>
</svg>
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.877014 7.49988C0.877014 3.84219 3.84216 0.877045 7.49985 0.877045C11.1575 0.877045 14.1227 3.84219 14.1227 7.49988C14.1227 11.1575 11.1575 14.1227 7.49985 14.1227C3.84216 14.1227 0.877014 11.1575 0.877014 7.49988ZM7.49985 1.82704C4.36683 1.82704 1.82701 4.36686 1.82701 7.49988C1.82701 8.97196 2.38774 10.3131 3.30727 11.3213C4.19074 9.94119 5.73818 9.02499 7.50023 9.02499C9.26206 9.02499 10.8093 9.94097 11.6929 11.3208C12.6121 10.3127 13.1727 8.97172 13.1727 7.49988C13.1727 4.36686 10.6328 1.82704 7.49985 1.82704ZM10.9818 11.9787C10.2839 10.7795 8.9857 9.97499 7.50023 9.97499C6.01458 9.97499 4.71624 10.7797 4.01845 11.9791C4.97952 12.7272 6.18765 13.1727 7.49985 13.1727C8.81227 13.1727 10.0206 12.727 10.9818 11.9787ZM5.14999 6.50487C5.14999 5.207 6.20212 4.15487 7.49999 4.15487C8.79786 4.15487 9.84999 5.207 9.84999 6.50487C9.84999 7.80274 8.79786 8.85487 7.49999 8.85487C6.20212 8.85487 5.14999 7.80274 5.14999 6.50487ZM7.49999 5.10487C6.72679 5.10487 6.09999 5.73167 6.09999 6.50487C6.09999 7.27807 6.72679 7.90487 7.49999 7.90487C8.27319 7.90487 8.89999 7.27807 8.89999 6.50487C8.89999 5.73167 8.27319 5.10487 7.49999 5.10487Z"
fill="#000000"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+274
View File
@@ -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();
});
+92 -12
View File
@@ -19,7 +19,6 @@ nav ul li a {
font-size: large;
}
/* Custom checkbox styles */
.custom-checkbox {
display: inline-block;
position: relative;
@@ -40,7 +39,7 @@ nav ul li a {
height: 18px;
width: 18px;
background-color: #fff;
border: 2px solid #d1d5db; /* gray-300 */
border: 2px solid #d1d5db;
border-radius: 4px;
transition: all 0.2s ease;
display: inline-block;
@@ -48,11 +47,11 @@ nav ul li a {
}
.custom-checkbox:hover input ~ .checkmark {
border-color: #6b7280; /* gray-500 */
border-color: #6b7280;
}
.custom-checkbox input:checked ~ .checkmark {
background-color: #6b7280; /* gray-500 */
background-color: #6b7280;
border-color: #6b7280;
}
@@ -114,12 +113,29 @@ button:disabled {
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-5px);
}
20%,
40%,
60%,
80% {
transform: translateX(5px);
}
}
#req-length, #req-upper, #req-lower, #req-digit {
#req-length,
#req-upper,
#req-lower,
#req-digit {
transition: color 0.2s ease;
}
@@ -145,7 +161,8 @@ button:disabled {
}
}
#login-tab, #register-tab {
#login-tab,
#register-tab {
font-family: "Dited", sans-serif;
letter-spacing: 1.5px;
cursor: pointer;
@@ -156,10 +173,73 @@ button:disabled {
}
@keyframes dropdownFade {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#user-arrow.rotate-180 {
transform: rotate(180deg);
}
}
.stat-number {
font-variant-numeric: tabular-nums;
}
.stat-card {
min-width: 140px;
}
@keyframes pulse-soft {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
.animate-pulse-soft {
animation: pulse-soft 2s ease-in-out infinite;
}
#bookSvg {
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.5s ease-out forwards;
}
.stat-card:hover svg {
transform: scale(1.1);
transition: transform 0.3s ease;
}
.stat-card svg {
transition: transform 0.3s ease;
}
.gradient-text {
background: linear-gradient(135deg, #374151 0%, #6b7280 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}