mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 04:31:09 +00:00
Добавление главной страницы о общей статистикой
This commit is contained in:
@@ -33,7 +33,7 @@ def get_info(app) -> Dict:
|
|||||||
@router.get("/", include_in_schema=False)
|
@router.get("/", include_in_schema=False)
|
||||||
async def root(request: Request, app=Depends(lambda: get_app())):
|
async def root(request: Request, app=Depends(lambda: get_app())):
|
||||||
"""Эндпоинт главной страницы"""
|
"""Эндпоинт главной страницы"""
|
||||||
return RedirectResponse("/books")
|
return templates.TemplateResponse(request, "index.html", get_info(app))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/books", include_in_schema=False)
|
@router.get("/books", include_in_schema=False)
|
||||||
@@ -85,7 +85,7 @@ async def api_info(app=Depends(lambda: get_app())):
|
|||||||
description="Возвращает статистическую информацию о системе",
|
description="Возвращает статистическую информацию о системе",
|
||||||
)
|
)
|
||||||
async def api_stats(session: Session = Depends(get_session)):
|
async def api_stats(session: Session = Depends(get_session)):
|
||||||
"""Эндпоинт стстистика системы"""
|
"""Эндпоинт стстистики системы"""
|
||||||
authors = select(func.count()).select_from(Author)
|
authors = select(func.count()).select_from(Author)
|
||||||
books = select(func.count()).select_from(Book)
|
books = select(func.count()).select_from(Book)
|
||||||
genres = select(func.count()).select_from(Genre)
|
genres = select(func.count()).select_from(Genre)
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
<?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">
|
<svg
|
||||||
|
width="800px"
|
||||||
|
height="800px"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -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();
|
||||||
|
});
|
||||||
@@ -19,7 +19,6 @@ nav ul li a {
|
|||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom checkbox styles */
|
|
||||||
.custom-checkbox {
|
.custom-checkbox {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -40,7 +39,7 @@ nav ul li a {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 2px solid #d1d5db; /* gray-300 */
|
border: 2px solid #d1d5db;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -48,11 +47,11 @@ nav ul li a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.custom-checkbox:hover input ~ .checkmark {
|
.custom-checkbox:hover input ~ .checkmark {
|
||||||
border-color: #6b7280; /* gray-500 */
|
border-color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-checkbox input:checked ~ .checkmark {
|
.custom-checkbox input:checked ~ .checkmark {
|
||||||
background-color: #6b7280; /* gray-500 */
|
background-color: #6b7280;
|
||||||
border-color: #6b7280;
|
border-color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +113,29 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shake {
|
@keyframes shake {
|
||||||
0%, 100% { transform: translateX(0); }
|
0%,
|
||||||
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
|
100% {
|
||||||
20%, 40%, 60%, 80% { transform: translateX(5px); }
|
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;
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +161,8 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#login-tab, #register-tab {
|
#login-tab,
|
||||||
|
#register-tab {
|
||||||
font-family: "Dited", sans-serif;
|
font-family: "Dited", sans-serif;
|
||||||
letter-spacing: 1.5px;
|
letter-spacing: 1.5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -156,10 +173,73 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes dropdownFade {
|
@keyframes dropdownFade {
|
||||||
from { opacity: 0; transform: translateY(-5px); }
|
from {
|
||||||
to { opacity: 1; transform: translateY(0); }
|
opacity: 0;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-arrow.rotate-180 {
|
#user-arrow.rotate-180 {
|
||||||
transform: rotate(180deg);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
<!-- templates/auth.html -->
|
{% extends "base.html" %} {% block title %}LiB - Авторизация{% endblock %} {%
|
||||||
{% extends "base.html" %}
|
block content %}
|
||||||
|
|
||||||
{% block title %}LiB - Авторизация{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="flex flex-1 items-center justify-center p-4">
|
<div class="flex flex-1 items-center justify-center p-4">
|
||||||
<div class="w-full max-w-md">
|
<div class="w-full max-w-md">
|
||||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||||
<div class="flex border-b border-gray-200">
|
<div class="flex border-b border-gray-200">
|
||||||
<button type="button" id="login-tab" class="flex-1 py-4 text-center font-medium transition duration-200 text-gray-700 bg-gray-50 border-b-2 border-gray-500">Вход</button>
|
<button
|
||||||
<button type="button" id="register-tab" class="flex-1 py-4 text-center font-medium transition duration-200 text-gray-400 hover:text-gray-600">Регистрация</button>
|
type="button"
|
||||||
|
id="login-tab"
|
||||||
|
class="flex-1 py-4 text-center font-medium transition duration-200 text-gray-700 bg-gray-50 border-b-2 border-gray-500"
|
||||||
|
>
|
||||||
|
Вход
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="register-tab"
|
||||||
|
class="flex-1 py-4 text-center font-medium transition duration-200 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
Регистрация
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="login-form" class="p-6">
|
<form id="login-form" class="p-6">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="login-username" class="block text-sm font-medium text-gray-700 mb-2">Имя пользователя</label>
|
<label
|
||||||
|
for="login-username"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Имя пользователя</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="login-username"
|
id="login-username"
|
||||||
@@ -24,9 +36,12 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="login-password" class="block text-sm font-medium text-gray-700 mb-2">Пароль</label>
|
<label
|
||||||
|
for="login-password"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Пароль</label
|
||||||
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@@ -41,37 +56,77 @@
|
|||||||
class="toggle-password absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
class="toggle-password absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
onclick="togglePassword(this)"
|
onclick="togglePassword(this)"
|
||||||
>
|
>
|
||||||
<svg class="eye-open w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
class="eye-open w-5 h-5"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="eye-closed w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"></path>
|
class="eye-closed w-5 h-5 hidden"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<label class="custom-checkbox flex items-center text-sm text-gray-600">
|
<label
|
||||||
|
class="custom-checkbox flex items-center text-sm text-gray-600"
|
||||||
|
>
|
||||||
<input type="checkbox" id="remember-me" />
|
<input type="checkbox" id="remember-me" />
|
||||||
<span class="checkmark"></span>Запомнить меня
|
<span class="checkmark"></span>Запомнить меня
|
||||||
</label>
|
</label>
|
||||||
<a href="#" class="text-sm text-gray-500 hover:text-gray-700 transition">Забыли пароль?</a>
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-sm text-gray-500 hover:text-gray-700 transition"
|
||||||
|
>Забыли пароль?</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
<div id="login-error" class="hidden mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-lg text-sm"></div>
|
id="login-error"
|
||||||
|
class="hidden mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-lg text-sm"
|
||||||
|
></div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
id="login-submit"
|
id="login-submit"
|
||||||
class="w-full bg-gray-500 text-white py-3 px-4 rounded-lg hover:bg-gray-600 transition duration-200 font-medium"
|
class="w-full bg-gray-500 text-white py-3 px-4 rounded-lg hover:bg-gray-600 transition duration-200 font-medium"
|
||||||
>Войти</button>
|
>
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<form
|
||||||
<form id="register-form" class="p-6 hidden" onsubmit="return handleRegister(event)">
|
id="register-form"
|
||||||
|
class="p-6 hidden"
|
||||||
|
onsubmit="return handleRegister(event);"
|
||||||
|
>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="register-username" class="block text-sm font-medium text-gray-700 mb-2">Имя пользователя</label>
|
<label
|
||||||
|
for="register-username"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Имя пользователя</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="register-username"
|
id="register-username"
|
||||||
@@ -83,9 +138,12 @@
|
|||||||
maxlength="50"
|
maxlength="50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="register-email" class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
<label
|
||||||
|
for="register-email"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Email</label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
id="register-email"
|
id="register-email"
|
||||||
@@ -95,9 +153,15 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="register-fullname" class="block text-sm font-medium text-gray-700 mb-2">Полное имя <span class="text-gray-400">(необязательно)</span></label>
|
<label
|
||||||
|
for="register-fullname"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Полное имя
|
||||||
|
<span class="text-gray-400"
|
||||||
|
>(необязательно)</span
|
||||||
|
></label
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="register-fullname"
|
id="register-fullname"
|
||||||
@@ -107,9 +171,12 @@
|
|||||||
maxlength="100"
|
maxlength="100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="register-password" class="block text-sm font-medium text-gray-700 mb-2">Пароль</label>
|
<label
|
||||||
|
for="register-password"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Пароль</label
|
||||||
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@@ -126,25 +193,61 @@
|
|||||||
class="toggle-password absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
class="toggle-password absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
onclick="togglePassword(this)"
|
onclick="togglePassword(this)"
|
||||||
>
|
>
|
||||||
<svg class="eye-open w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
class="eye-open w-5 h-5"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="eye-closed w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"></path>
|
class="eye-closed w-5 h-5 hidden"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="h-1 w-full bg-gray-200 rounded-full overflow-hidden">
|
<div
|
||||||
<div id="password-strength-bar" class="h-full w-0 transition-all duration-300"></div>
|
class="h-1 w-full bg-gray-200 rounded-full overflow-hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="password-strength-bar"
|
||||||
|
class="h-full w-0 transition-all duration-300"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<p id="password-strength-text" class="text-xs mt-1 text-gray-500"></p>
|
<p
|
||||||
|
id="password-strength-text"
|
||||||
|
class="text-xs mt-1 text-gray-500"
|
||||||
|
></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="register-password-confirm" class="block text-sm font-medium text-gray-700 mb-2">Подтвердите пароль</label>
|
<label
|
||||||
|
for="register-password-confirm"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-2"
|
||||||
|
>Подтвердите пароль</label
|
||||||
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@@ -159,22 +262,55 @@
|
|||||||
class="toggle-password absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
class="toggle-password absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
onclick="togglePassword(this)"
|
onclick="togglePassword(this)"
|
||||||
>
|
>
|
||||||
<svg class="eye-open w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
class="eye-open w-5 h-5"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="eye-closed w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"></path>
|
class="eye-closed w-5 h-5 hidden"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p id="password-match-error" class="text-xs mt-1 text-red-500 hidden">Пароли не совпадают</p>
|
<p
|
||||||
|
id="password-match-error"
|
||||||
|
class="text-xs mt-1 text-red-500 hidden"
|
||||||
|
>
|
||||||
|
Пароли не совпадают
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
<div id="register-error" class="hidden mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-lg text-sm"></div>
|
id="register-error"
|
||||||
|
class="hidden mb-4 p-3 bg-red-100 border border-red-300 text-red-700 rounded-lg text-sm"
|
||||||
<div id="register-success" class="hidden mb-4 p-3 bg-green-100 border border-green-300 text-green-700 rounded-lg text-sm"></div>
|
></div>
|
||||||
|
<div
|
||||||
|
id="register-success"
|
||||||
|
class="hidden mb-4 p-3 bg-green-100 border border-green-300 text-green-700 rounded-lg text-sm"
|
||||||
|
></div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
id="register-submit"
|
id="register-submit"
|
||||||
@@ -186,8 +322,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script type="text/javascript" src="/static/auth.js"></script>
|
<script type="text/javascript" src="/static/auth.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -5,7 +5,6 @@ content %}
|
|||||||
class="w-1/4 bg-white p-4 rounded-lg shadow-md mr-4 h-fit resize-x overflow-auto min-w-64 max-w-96"
|
class="w-1/4 bg-white p-4 rounded-lg shadow-md mr-4 h-fit resize-x overflow-auto min-w-64 max-w-96"
|
||||||
>
|
>
|
||||||
<h2 class="text-xl font-semibold mb-4">Поиск</h2>
|
<h2 class="text-xl font-semibold mb-4">Поиск</h2>
|
||||||
|
|
||||||
<div class="relative mb-4">
|
<div class="relative mb-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -29,9 +28,7 @@ content %}
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold mb-4">Фильтры</h2>
|
<h2 class="text-xl font-semibold mb-4">Фильтры</h2>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h3 class="font-medium mb-2">Авторы</h3>
|
<h3 class="font-medium mb-2">Авторы</h3>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -52,12 +49,10 @@ content %}
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h3 class="font-medium mb-2">Жанры</h3>
|
<h3 class="font-medium mb-2">Жанры</h3>
|
||||||
<ul id="genres-list" class="max-h-60 overflow-y-auto"></ul>
|
<ul id="genres-list" class="max-h-60 overflow-y-auto"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="apply-filters-btn"
|
id="apply-filters-btn"
|
||||||
class="w-full bg-gray-500 text-white py-2 px-4 rounded-lg hover:bg-gray-600 transition duration-200 mb-2"
|
class="w-full bg-gray-500 text-white py-2 px-4 rounded-lg hover:bg-gray-600 transition duration-200 mb-2"
|
||||||
@@ -70,19 +65,13 @@ content %}
|
|||||||
>
|
>
|
||||||
Сбросить фильтры
|
Сбросить фильтры
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Счётчик результатов -->
|
|
||||||
<div
|
<div
|
||||||
id="results-counter"
|
id="results-counter"
|
||||||
class="mt-4 text-center text-sm text-gray-500"
|
class="mt-4 text-center text-sm text-gray-500"
|
||||||
></div>
|
></div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="flex-1">
|
<main class="flex-1">
|
||||||
<div id="books-container">
|
<div id="books-container"></div>
|
||||||
<!-- Книги будут загружены через JS -->
|
|
||||||
</div>
|
|
||||||
<!-- Пагинация добавляется динамически после books-container -->
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
{% extends "base.html" %} {% block title %}LiB - Библиотека{% endblock %} {%
|
||||||
|
block content %}
|
||||||
|
<div class="flex flex-1 items-center justify-center p-4">
|
||||||
|
<div class="w-full max-w-4xl">
|
||||||
|
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||||
|
<div class="text-center py-8 border-b border-gray-200">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-800 mb-2">
|
||||||
|
Добро пожаловать в LiB
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-500">Ваша персональная библиотека книг</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-8">
|
||||||
|
<div
|
||||||
|
class="flex flex-col lg:flex-row items-center justify-center gap-12"
|
||||||
|
>
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg
|
||||||
|
id="bookSvg"
|
||||||
|
width="400"
|
||||||
|
height="500"
|
||||||
|
viewBox="0 0 200 250"
|
||||||
|
class="drop-shadow-lg"
|
||||||
|
></svg>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-6">
|
||||||
|
<div
|
||||||
|
class="stat-card bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-6 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="mb-3">
|
||||||
|
<svg
|
||||||
|
class="w-10 h-10 mx-auto text-gray-600"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="stat-books"
|
||||||
|
class="text-4xl font-bold text-gray-800 mb-1 stat-number"
|
||||||
|
data-target="0"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 font-medium">
|
||||||
|
Книг
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="stat-card bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-6 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="mb-3">
|
||||||
|
<svg
|
||||||
|
class="w-10 h-10 mx-auto text-gray-600"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="stat-authors"
|
||||||
|
class="text-4xl font-bold text-gray-800 mb-1 stat-number"
|
||||||
|
data-target="0"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 font-medium">
|
||||||
|
Авторов
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="stat-card bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-6 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="mb-3">
|
||||||
|
<svg
|
||||||
|
class="w-10 h-10 mx-auto text-gray-600"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="stat-genres"
|
||||||
|
class="text-4xl font-bold text-gray-800 mb-1 stat-number"
|
||||||
|
data-target="0"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 font-medium">
|
||||||
|
Жанров
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="stat-card bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-6 text-center transform transition-all duration-300 hover:scale-105 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="mb-3">
|
||||||
|
<svg
|
||||||
|
class="w-10 h-10 mx-auto text-gray-600"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="stat-users"
|
||||||
|
class="text-4xl font-bold text-gray-800 mb-1 stat-number"
|
||||||
|
data-target="0"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 font-medium">
|
||||||
|
Пользователей
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-8 pb-8">
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a
|
||||||
|
href="/books"
|
||||||
|
class="inline-flex items-center justify-center px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition duration-200 font-medium shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-5 h-5 mr-2"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Смотреть книги
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/authors"
|
||||||
|
class="inline-flex items-center justify-center px-6 py-3 bg-white text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition duration-200 font-medium shadow-sm hover:shadow-md transform hover:-translate-y-0.5"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-5 h-5 mr-2"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Все авторы
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 text-center text-gray-400 text-sm">
|
||||||
|
<p>LiB — Библиотека. Создано с ❤️</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %} {% block scripts %}
|
||||||
|
<script type="text/javascript" src="/static/index.js"></script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user