Коты-Воители — Библиотека
by SparkDragon182126 lines35.9 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Коты-Воители — Библиотека</title>
<style>
*{
box-sizing:border-box;
-webkit-tap-highlight-color:transparent;
}
html,body{
width:100%;
height:100%;
margin:0;
overflow:hidden;
font-family:Georgia,"Times New Roman",serif;
color:white;
}
body{
background:
linear-gradient(rgba(0,0,0,.25),rgba(0,0,0,.45)),
url("https://i.pinimg.com/474x/49/3e/8d/493e8df28f338759e6e1bd99e910ae96.jpg")
center/cover fixed;
}
button{
font-family:inherit;
}
.hidden{
display:none!important;
}
/* =========================
БИБЛИОТЕКА
========================= */
#library{
position:absolute;
inset:0;
display:flex;
flex-direction:column;
align-items:center;
overflow:hidden;
background:rgba(0,0,0,.12);
}
.libraryTitle{
margin-top:25px;
position:relative;
z-index:20;
text-align:center;
font-size:clamp(28px,7vw,52px);
letter-spacing:2px;
text-shadow:
0 4px 18px #000,
0 0 30px #000;
}
.librarySubtitle{
margin-top:6px;
position:relative;
z-index:20;
color:#e4d5b4;
font-size:clamp(14px,3.5vw,19px);
text-shadow:0 2px 8px #000;
}
/* =========================
КАРУСЕЛЬ
========================= */
.carousel{
position:relative;
width:100%;
height:min(65vh,620px);
margin-top:10px;
display:flex;
align-items:center;
justify-content:center;
perspective:1400px;
touch-action:pan-y;
}
.bookCard{
position:absolute;
width:min(45vw,245px);
height:min(62vw,360px);
border-radius:9px;
overflow:hidden;
background:#161616;
box-shadow:
0 25px 60px rgba(0,0,0,.85),
0 0 0 1px rgba(255,255,255,.12);
user-select:none;
transition:
transform .7s cubic-bezier(.16,1,.3,1),
opacity .55s ease,
filter .55s ease,
box-shadow .7s ease;
will-change:transform,opacity,filter;
}
.bookCard img{
display:block;
width:100%;
height:100%;
object-fit:cover;
transition:
transform .8s cubic-bezier(.16,1,.3,1),
filter .7s ease;
}
/* затемнение снизу */
.bookCard::after{
content:"";
position:absolute;
inset:0;
background:
linear-gradient(
to bottom,
transparent 48%,
rgba(0,0,0,.85)
);
pointer-events:none;
}
/* =========================
АНИМАЦИЯ ВЫБОРА
========================= */
.bookCard.selectedBook{
animation:
selectedBookAnimation
.85s
cubic-bezier(.16,1,.3,1);
box-shadow:
0 35px 80px rgba(0,0,0,.95),
0 0 35px rgba(255,235,180,.35),
0 0 90px rgba(255,235,180,.15);
}
.bookCard.selectedBook img{
animation:
selectedCoverZoom
.85s
cubic-bezier(.16,1,.3,1);
}
.bookCard.selectedBook::before{
content:"";
position:absolute;
z-index:10;
inset:-35%;
background:
linear-gradient(
115deg,
transparent 25%,
rgba(255,255,255,0) 42%,
rgba(255,255,255,.55) 50%,
rgba(255,255,255,0) 58%,
transparent 75%
);
transform:
translateX(-130%)
rotate(8deg);
animation:
bookLightSweep
1s
ease-out
forwards;
pointer-events:none;
}
@keyframes selectedBookAnimation{
0%{
filter:brightness(1);
}
25%{
filter:brightness(1.15);
}
50%{
filter:brightness(1.25);
}
75%{
filter:brightness(1.08);
}
100%{
filter:brightness(1);
}
}
@keyframes selectedCoverZoom{
0%{
transform:scale(1);
}
45%{
transform:scale(1.055);
}
100%{
transform:scale(1);
}
}
@keyframes bookLightSweep{
0%{
transform:
translateX(-130%)
rotate(8deg);
opacity:0;
}
20%{
opacity:1;
}
65%{
opacity:.8;
}
100%{
transform:
translateX(130%)
rotate(8deg);
opacity:0;
}
}
/* =========================
ИНФОРМАЦИЯ КНИГИ
========================= */
.bookInfo{
position:absolute;
z-index:15;
left:0;
right:0;
bottom:0;
padding:18px 12px;
text-align:center;
}
.bookInfo strong{
display:block;
font-size:clamp(15px,3.5vw,21px);
text-shadow:0 2px 8px #000;
}
.bookInfo span{
display:block;
margin-top:5px;
color:#ddd;
font-size:12px;
}
/* =========================
КНОПКИ
========================= */
.controls{
position:absolute;
z-index:30;
left:0;
right:0;
bottom:22px;
display:flex;
justify-content:center;
align-items:center;
gap:12px;
}
.controlButton,
.openButton{
color:white;
border:1px solid rgba(255,255,255,.25);
border-radius:10px;
background:
linear-gradient(
180deg,
rgba(255,255,255,.16),
rgba(255,255,255,.05)
);
box-shadow:
0 8px 25px rgba(0,0,0,.5);
transition:.2s;
}
.controlButton{
width:48px;
height:48px;
font-size:28px;
}
.openButton{
height:48px;
min-width:190px;
padding:0 20px;
font-size:14px;
font-weight:bold;
letter-spacing:1px;
}
.controlButton:active,
.openButton:active{
transform:scale(.94);
}
/* =========================
ОТКРЫТИЕ
========================= */
#openingScene{
position:absolute;
z-index:100;
inset:0;
display:flex;
justify-content:center;
align-items:center;
perspective:1600px;
background:
linear-gradient(rgba(0,0,0,.25),rgba(0,0,0,.55)),
url("https://i.pinimg.com/474x/49/3e/8d/493e8df28f338759e6e1bd99e910ae96.jpg")
center/cover;
}
.openingBook{
position:relative;
width:min(65vw,400px);
height:min(82vh,560px);
transform-style:preserve-3d;
animation:
bookZoom
.9s
forwards;
}
.openingPages{
position:absolute;
inset:3px;
border-radius:5px 14px 14px 5px;
background:
repeating-linear-gradient(
90deg,
#eadbb9 0,
#eadbb9 2px,
#d4c19b 3px,
#eadbb9 5px
);
box-shadow:
inset 0 0 40px rgba(70,40,15,.35);
}
.openingCover{
position:absolute;
inset:0;
overflow:hidden;
border-radius:5px 14px 14px 5px;
transform-origin:left center;
backface-visibility:hidden;
z-index:5;
animation:
coverOpen
1.25s
1.05s
forwards;
}
.openingCover img{
width:100%;
height:100%;
object-fit:cover;
}
.openingSpine{
position:absolute;
z-index:10;
left:0;
top:0;
bottom:0;
width:14px;
background:
linear-gradient(
to right,
#1a120b,
#725033,
#25170d
);
}
.openingGlow{
position:absolute;
left:50%;
top:50%;
width:20px;
height:20px;
transform:
translate(-50%,-50%);
border-radius:50%;
box-shadow:
0 0 30px 20px rgba(255,226,150,.4),
0 0 100px 50px rgba(255,226,150,.2);
opacity:0;
animation:
glowBook
1.2s
1.3s
forwards;
}
@keyframes bookZoom{
from{
transform:scale(.45);
}
to{
transform:scale(1);
}
}
@keyframes coverOpen{
from{
transform:rotateY(0deg);
}
to{
transform:rotateY(-125deg);
}
}
@keyframes glowBook{
0%{
opacity:0;
transform:
translate(-50%,-50%)
scale(.2);
}
50%{
opacity:1;
}
100%{
opacity:0;
transform:
translate(-50%,-50%)
scale(8);
}
}
/* =========================
ЧИТАТЕЛЬ
========================= */
#reader{
position:absolute;
inset:0;
background:
linear-gradient(rgba(0,0,0,.4),rgba(0,0,0,.58)),
url("https://i.pinimg.com/474x/49/3e/8d/493e8df28f338759e6e1bd99e910ae96.jpg")
center/cover fixed;
}
.readerTop{
position:absolute;
z-index:20;
top:0;
left:0;
right:0;
height:62px;
display:flex;
align-items:center;
justify-content:space-between;
padding:0 13px;
background:
linear-gradient(
rgba(0,0,0,.75),
rgba(0,0,0,.1)
);
}
.topButton{
width:42px;
height:42px;
border-radius:9px;
border:1px solid rgba(255,255,255,.2);
color:white;
background:rgba(0,0,0,.4);
font-size:20px;
}
.readerTitle{
flex:1;
text-align:center;
padding:0 10px;
font-size:clamp(15px,4vw,22px);
font-weight:bold;
overflow:hidden;
white-space:nowrap;
text-overflow:ellipsis;
}
/* =========================
СТРАНИЦЫ
========================= */
#pagesContainer{
position:absolute;
inset:0;
}
.page{
position:absolute;
left:50%;
top:50%;
width:min(92vw,760px);
height:min(84vh,880px);
transform:
translate(-50%,-50%);
padding:
42px
clamp(28px,7vw,72px)
105px;
overflow-y:auto;
border-radius:5px 14px 14px 5px;
color:#332417;
background-color:#e7d5ae;
background-image:
linear-gradient(
rgba(232,213,174,.64),
rgba(232,213,174,.64)
),
url("https://img.magnific.com/free-photo/white-paper-texture_1194-2301.jpg");
background-size:cover;
box-shadow:
0 30px 70px rgba(0,0,0,.85),
inset 0 0 40px rgba(60,35,12,.3);
opacity:0;
pointer-events:none;
transition:
transform .5s,
opacity .4s;
}
.page.active{
opacity:1;
pointer-events:auto;
}
.page.previous{
transform:
translate(-130%,-50%)
rotateY(12deg);
}
.page.next{
transform:
translate(30%,-50%)
rotateY(-12deg);
}
.pageNumber{
text-align:center;
font-family:Arial,sans-serif;
font-size:14px;
letter-spacing:2px;
color:#806747;
margin-bottom:24px;
}
.page h1{
margin-top:0;
text-align:center;
font-size:clamp(32px,7vw,50px);
line-height:1.15;
}
.page h2{
text-align:center;
font-size:clamp(22px,5vw,28px);
color:#72583a;
line-height:1.3;
}
.page p{
font-size:clamp(21px,5vw,27px);
line-height:1.8;
text-align:left;
margin:
0 0 27px;
}
.pageDivider{
width:90px;
height:2px;
margin:20px auto 30px;
background:
linear-gradient(
90deg,
transparent,
#806747,
transparent
);
}
/* =========================
НАВИГАЦИЯ
========================= */
.readerBottom{
position:absolute;
z-index:30;
left:50%;
bottom:15px;
transform:translateX(-50%);
display:flex;
align-items:center;
justify-content:center;
gap:12px;
width:min(95vw,650px);
}
.navButton{
width:54px;
height:48px;
border-radius:10px;
border:1px solid rgba(255,255,255,.22);
background:rgba(0,0,0,.55);
color:white;
font-size:24px;
}
.navButton:disabled{
opacity:.3;
}
.pageCounter{
min-width:100px;
text-align:center;
font-family:Arial,sans-serif;
font-size:14px;
}
/* =========================
ГЛАВЫ
========================= */
#chapterMenu{
position:absolute;
z-index:80;
inset:0;
display:flex;
justify-content:center;
align-items:center;
background:rgba(0,0,0,.72);
backdrop-filter:blur(7px);
}
.chapterPanel{
width:min(90vw,500px);
padding:25px;
border-radius:15px;
background:
linear-gradient(
145deg,
#27372b,
#101610
);
border:1px solid rgba(255,255,255,.16);
box-shadow:0 30px 80px #000;
}
.chapterPanel h2{
text-align:center;
}
#chapterList{
display:flex;
flex-direction:column;
gap:10px;
}
.chapterButton,
.closeChapter{
min-height:50px;
border-radius:10px;
border:1px solid rgba(255,255,255,.2);
color:white;
background:rgba(255,255,255,.08);
font-size:17px;
}
.closeChapter{
width:100%;
margin-top:15px;
}
/* =========================
FADE
========================= */
#fade{
position:absolute;
z-index:200;
inset:0;
background:#000;
opacity:0;
pointer-events:none;
transition:opacity .35s;
}
@media(max-width:600px){
.libraryTitle{
margin-top:20px;
}
.carousel{
height:62vh;
}
.bookCard{
width:48vw;
height:66vw;
}
.controls{
bottom:18px;
}
.openButton{
min-width:165px;
font-size:13px;
}
.page{
width:94vw;
height:81vh;
padding:
30px
25px
95px;
}
.page p{
font-size:22px;
line-height:1.78;
}
}
</style>
</head>
<body>
<section id="library">
<div class="libraryTitle">
КОТЫ-ВОИТЕЛИ
</div>
<div class="librarySubtitle">
Библиотека лесных историй
</div>
<div class="carousel" id="carousel"></div>
<div class="controls">
<button class="controlButton" id="prevBook">
‹
</button>
<button class="openButton" id="openBook">
ОТКРЫТЬ КНИГУ
</button>
<button class="controlButton" id="nextBook">
›
</button>
</div>
</section>
<section id="openingScene" class="hidden">
<div class="openingBook">
<div class="openingPages"></div>
<div class="openingSpine"></div>
<div class="openingCover">
<img id="openingCoverImage" src="" alt="">
</div>
<div class="openingGlow"></div>
</div>
</section>
<section id="reader" class="hidden">
<div class="readerTop">
<button class="topButton" id="backLibrary">
×
</button>
<div class="readerTitle" id="readerTitle"></div>
<button class="topButton" id="chapterOpen">
≡
</button>
</div>
<div id="pagesContainer"></div>
<div class="readerBottom">
<button class="navButton" id="prevPage">
‹
</button>
<div class="pageCounter" id="pageCounter">
1 / 20
</div>
<button class="navButton" id="nextPage">
›
</button>
</div>
</section>
<section id="chapterMenu" class="hidden">
<div class="chapterPanel">
<h2>Содержание</h2>
<div id="chapterList"></div>
<button class="closeChapter" id="closeChapter">
ЗАКРЫТЬ
</button>
</div>
</section>
<div id="fade"></div>
<script>
/*
==================================================
КНИГИ
ВАЖНО:
Я заменил проблемную систему загрузки так,
чтобы браузер не показывал стандартный значок
сломавшейся картинки.
==================================================
*/
const books = [
{
title:"Коты-Воители",
description:"Тайна лесной тропы",
/*
Основная картинка
*/
cover:
"https://i.pinimg.com/736x/4d/8f/0e/4d8f0e05dab88dddf1d0771058fe9585.jpg"
},
{
title:"Тайна старой книги",
description:"Следы прошлого",
cover:
"https://content2.rozetka.com.ua/goods/images/big/10959855.png"
},
{
title:"Стань диким!",
description:"За границей леса",
cover:
"https://static.wikia.nocookie.net/warriors-cats/images/a/a7/%D0%A1%D1%82%D0%B0%D0%BD%D1%8C_%D0%B4%D0%B8%D0%BA%D0%B8%D0%BC_%D1%80%D1%83%D1%81.jpeg/revision/latest?cb=20240627130511&path-prefix=ru"
},
{
title:"Into the Wild",
description:"Новая тропа",
cover:
"https://static.yakaboo.ua/media/catalog/product/8/1/81djsapwmgl._sl1500_.jpg"
},
{
title:"Сны Воробья",
description:"Шёпот сновидений",
cover:
"https://cdn.litres.ru/pub/c/cover_415/71771065"
},
{
title:"Восход солнца",
description:"Перед первым светом",
cover:
"https://static.wikia.nocookie.net/warriors-cats/images/8/8c/%D0%92%D0%BE%D1%81%D1%85%D0%BE%D0%B4_%D1%81%D0%BE%D0%BB%D0%BD%D1%86%D0%B0_%D0%BE%D0%B1%D0%BB%D0%BE%D0%B6%D0%BA%D0%B0.jpg/revision/latest?cb=20240627135014&path-prefix=ru"
},
{
title:"The Prophecies Begin",
description:"Знак на небе",
cover:
"https://cover.imglib.info/uploads/cover/warriors-the-prophecies-begin/cover/qH0n6PAHNX4G_250x350.jpg"
},
{
title:"Lost Stars",
description:"Когда исчезают звёзды",
cover:
"https://www.adazing.com/wp-content/uploads/2023/06/Warrior-Cats-Book-Covers-Lost-Stars.png"
}
];
/* =========================
ИСТОРИИ
========================= */
const storyTemplates = [
[
"Лес просыпается после долгой ночи.\n\nТы выходишь из лагеря и замечаешь свежие следы на тропе.\n\nСледы приводят к реке, где возле старого дерева лежит белое перо.",
"На коре дерева появляется странный серебристый знак.\n\nТы решаешь рассказать о находке старшему воину.\n\nВ лагере никто не знает, кому принадлежит знак.",
"Ты возвращаешься к реке вместе с товарищем.\n\nПод корнями дерева обнаруживается старая каменная пластинка.\n\nНа пластинке нарисована карта леса.",
"Карта показывает дорогу к далёкому холму.\n\nВы отправляетесь туда ранним утром.\n\nНа вершине холма лежат десятки белых перьев.",
"Ветер поднимает их и показывает направление.\n\nСледуя за перьями, вы находите каменный круг.\n\nВ центре круга находится знакомый серебряный знак.",
"Вы понимаете, что все подсказки связаны.\n\nПоследняя линия на камне указывает обратно к лагерю.\n\nСтарший воин объясняет, что лес иногда оставляет подсказки.",
"Ночью серебряный знак снова появляется на пере.\n\nТы понимаешь, что эта тайна ещё не закончилась.",
"На следующее утро ты снова отправляешься к реке.\n\nНа этот раз возле воды появляется новая отметка.",
"Отметка указывает в сторону старого оврага.\n\nТы осторожно спускаешься вниз.",
"На дне оврага лежит ещё один камень.\n\nНа нём изображён тот же серебряный знак.",
"Ты слышишь шелест листьев.\n\nВетер быстро проносится между деревьями.",
"Когда всё стихает, рядом оказывается маленькое белое перо.\n\nТы понимаешь, что кто-то оставляет тебе подсказки.",
"Следующая подсказка находится у старой сосны.\n\nПод корнями дерева спрятана небольшая деревянная пластинка.",
"На пластинке изображена тропа.\n\nОна ведёт туда, где лес становится особенно густым.",
"Ты проходишь между деревьями и находишь старую поляну.\n\nВ центре поляны стоит одинокий камень.",
"На камне появляется слабое серебряное свечение.\n\nТы подходишь ближе.",
"Свечение исчезает, оставляя после себя новый знак.\n\nТеперь ты знаешь, куда идти дальше.",
"Ты возвращаешься в лагерь до наступления темноты.\n\nСтарший внимательно рассматривает рисунок.",
"Он говорит, что подобный знак уже встречался в старых историях.\n\nНо никто не знает, кто оставляет эти подсказки.",
"Ты смотришь на лес.\n\nКажется, настоящая история только начинается."
]
];
/*
Создаём остальные книги на основе первой
истории, чтобы интерфейс всех 8 книг работал.
*/
const stories = [];
for(let i=0;i<8;i++){
const source =
storyTemplates[0];
const newStory =
source.map(
(text,index)=>
text
.replace(
"серебряный",
i===1
? "золотой"
: i===2
? "лунный"
: i===3
? "старый"
: i===4
? "светящийся"
: i===5
? "лесной"
: i===6
? "звёздный"
: i===7
? "таинственный"
: "серебряный"
)
);
stories.push(newStory);
}
/* =========================
ГЛАВЫ
========================= */
const chapterNames = [
"Глава 1 — Первые следы",
"Глава 2 — Тайна леса",
"Глава 3 — Дорога дальше",
"Глава 4 — Последняя подсказка"
];
let selectedBook = 0;
let currentPage = 0;
/* =========================
ЭЛЕМЕНТЫ
========================= */
const carousel =
document.getElementById("carousel");
const library =
document.getElementById("library");
const openingScene =
document.getElementById("openingScene");
const openingCoverImage =
document.getElementById("openingCoverImage");
const reader =
document.getElementById("reader");
const readerTitle =
document.getElementById("readerTitle");
const pagesContainer =
document.getElementById("pagesContainer");
const pageCounter =
document.getElementById("pageCounter");
const chapterMenu =
document.getElementById("chapterMenu");
const chapterList =
document.getElementById("chapterList");
const fade =
document.getElementById("fade");
/* =========================
БЕЗОПАСНАЯ КАРТИНКА
========================= */
function createBookImage(src,alt){
const img =
document.createElement("img");
img.alt = alt || "";
img.src = src;
/*
Если сайт с картинкой не разрешил загрузку,
убираем сломанный значок.
*/
img.onerror = function(){
this.onerror = null;
this.style.display = "none";
const fallback =
document.createElement("div");
fallback.style.position = "absolute";
fallback.style.inset = "0";
fallback.style.display = "flex";
fallback.style.alignItems = "center";
fallback.style.justifyContent = "center";
fallback.style.textAlign = "center";
fallback.style.padding = "25px";
fallback.style.color = "#d9c9a5";
fallback.style.fontSize = "18px";
fallback.style.background =
"linear-gradient(145deg,#242018,#0d0d0b)";
fallback.textContent =
"Обложка\nвременно недоступна";
this.parentElement.appendChild(
fallback
);
};
return img;
}
/* =========================
КАРУСЕЛЬ
========================= */
function renderCarousel(){
carousel.innerHTML = "";
books.forEach((book,index)=>{
const card =
document.createElement("div");
card.className =
"bookCard";
const img =
createBookImage(
book.cover,
book.title
);
card.appendChild(img);
const info =
document.createElement("div");
info.className =
"bookInfo";
info.innerHTML = `
<strong>${book.title}</strong>
<span>${book.description}</span>
`;
card.appendChild(info);
card.addEventListener(
"click",
()=>{
if(selectedBook === index){
card.classList.remove(
"selectedBook"
);
void card.offsetWidth;
card.classList.add(
"selectedBook"
);
return;
}
selectedBook = index;
renderCarousel();
requestAnimationFrame(()=>{
const selected =
document.querySelectorAll(
".bookCard"
)[index];
if(selected){
selected.classList.add(
"selectedBook"
);
}
});
}
);
carousel.appendChild(card);
});
const cards =
[...document.querySelectorAll(".bookCard")];
const total =
books.length;
cards.forEach((card,index)=>{
let diff =
index-selectedBook;
if(diff > total/2)
diff -= total;
if(diff < -total/2)
diff += total;
const abs =
Math.abs(diff);
card.style.opacity =
abs>3
? "0"
: String(1-abs*.18);
card.style.pointerEvents =
abs>3
? "none"
: "auto";
const x =
diff*48;
const z =
-abs*120;
const rot =
diff*-18;
const scale =
diff===0
? 1
: .82;
card.style.transform = `
translateX(${x}%)
translateZ(${z}px)
rotateY(${rot}deg)
scale(${scale})
`;
card.style.zIndex =
String(10-abs);
card.style.filter =
diff===0
? "brightness(1.12)"
: "brightness(.7)";
});
}
/* =========================
ОТКРЫТЬ КНИГУ
========================= */
function openSelectedBook(){
openingCoverImage.style.display =
"block";
openingCoverImage.src =
books[selectedBook].cover;
openingCoverImage.onerror =
function(){
this.onerror=null;
this.style.display="none";
};
openingScene.classList.remove(
"hidden"
);
const book =
document.querySelector(
".openingBook"
);
book.style.animation="none";
void book.offsetWidth;
book.style.animation=
"bookZoom .9s forwards";
setTimeout(()=>{
openingScene.classList.add(
"hidden"
);
library.classList.add(
"hidden"
);
reader.classList.remove(
"hidden"
);
createPages();
},2300);
}
/* =========================
СТРАНИЦЫ
========================= */
function getChapterName(index){
if(index<5)
return chapterNames[0];
if(index<10)
return chapterNames[1];
if(index<15)
return chapterNames[2];
return chapterNames[3];
}
function createPages(){
pagesContainer.innerHTML="";
const story =
stories[selectedBook];
story.forEach((text,index)=>{
const page =
document.createElement("article");
page.className="page";
if(index===0)
page.classList.add("active");
const number =
document.createElement("div");
number.className="pageNumber";
number.textContent =
"СТРАНИЦА "+(index+1);
page.appendChild(number);
const heading =
document.createElement("h2");
heading.textContent =
getChapterName(index);
page.appendChild(heading);
const divider =
document.createElement("div");
divider.className="pageDivider";
page.appendChild(divider);
if(index===0){
const title =
document.createElement("h1");
title.textContent =
books[selectedBook].title;
page.insertBefore(
title,
heading
);
}
text
.split(/\n\s*\n/)
.forEach(paragraph=>{
const p =
document.createElement("p");
p.textContent =
paragraph;
page.appendChild(p);
});
pagesContainer.appendChild(page);
});
currentPage=0;
readerTitle.textContent =
books[selectedBook].title;
updateReaderButtons();
}
/* =========================
ПЕРЕКЛЮЧЕНИЕ СТРАНИЦ
========================= */
function showPage(index){
const pages =
[...document.querySelectorAll(".page")];
if(!pages.length)
return;
index =
Math.max(
0,
Math.min(
index,
pages.length-1
)
);
currentPage=index;
pages.forEach((page,i)=>{
page.classList.remove(
"active",
"previous",
"next"
);
if(i===currentPage){
page.classList.add("active");
page.scrollTop=0;
}
else if(i<currentPage){
page.classList.add("previous");
}
else{
page.classList.add("next");
}
});
updateReaderButtons();
}
function changePage(direction){
showPage(
currentPage+direction
);
}
function updateReaderButtons(){
const total =
stories[selectedBook].length;
document.getElementById(
"prevPage"
).disabled =
currentPage<=0;
document.getElementById(
"nextPage"
).disabled =
currentPage>=total-1;
pageCounter.textContent =
`${currentPage+1} / ${total}`;
}
/* =========================
ГЛАВЫ
========================= */
function buildChapterMenu(){
chapterList.innerHTML="";
chapterNames.forEach((name,index)=>{
const button =
document.createElement("button");
button.className =
"chapterButton";
button.textContent =
name;
button.onclick=()=>{
showPage(index*5);
chapterMenu.classList.add(
"hidden"
);
};
chapterList.appendChild(button);
});
}
/* =========================
НАЗАД
========================= */
function backToLibrary(){
fade.style.opacity="1";
setTimeout(()=>{
reader.classList.add(
"hidden"
);
library.classList.remove(
"hidden"
);
fade.style.opacity="0";
renderCarousel();
},300);
}
/* =========================
КНОПКИ КАРУСЕЛИ
========================= */
document
.getElementById("prevBook")
.onclick=()=>{
selectedBook--;
if(selectedBook<0)
selectedBook=books.length-1;
renderCarousel();
requestAnimationFrame(()=>{
const card =
document.querySelectorAll(
".bookCard"
)[selectedBook];
if(card)
card.classList.add(
"selectedBook"
);
});
};
document
.getElementById("nextBook")
.onclick=()=>{
selectedBook++;
if(selectedBook>=books.length)
selectedBook=0;
renderCarousel();
requestAnimationFrame(()=>{
const card =
document.querySelectorAll(
".bookCard"
)[selectedBook];
if(card)
card.classList.add(
"selectedBook"
);
});
};
document
.getElementById("openBook")
.onclick =
openSelectedBook;
document
.getElementById("prevPage")
.onclick =
()=>changePage(-1);
document
.getElementById("nextPage")
.onclick =
()=>changePage(1);
document
.getElementById("backLibrary")
.onclick =
backToLibrary;
document
.getElementById("chapterOpen")
.onclick =
()=>{
chapterMenu.classList.remove(
"hidden"
);
};
document
.getElementById("closeChapter")
.onclick =
()=>{
chapterMenu.classList.add(
"hidden"
);
};
/* =========================
КЛАВИАТУРА
========================= */
document.addEventListener(
"keydown",
event=>{
if(reader.classList.contains("hidden"))
return;
if(event.key==="ArrowRight")
changePage(1);
if(event.key==="ArrowLeft")
changePage(-1);
if(event.key==="Escape")
backToLibrary();
}
);
/* =========================
СВАЙП СТРАНИЦ
========================= */
let startX=0;
let startY=0;
document.addEventListener(
"touchstart",
event=>{
startX =
event.touches[0].clientX;
startY =
event.touches[0].clientY;
},
{passive:true}
);
document.addEventListener(
"touchend",
event=>{
if(reader.classList.contains("hidden"))
return;
const endX =
event.changedTouches[0].clientX;
const endY =
event.changedTouches[0].clientY;
const dx =
endX-startX;
const dy =
endY-startY;
if(
Math.abs(dx)>55 &&
Math.abs(dx)>Math.abs(dy)
){
if(dx<0)
changePage(1);
else
changePage(-1);
}
},
{passive:true}
);
/* =========================
СВАЙП КНИГ
========================= */
let carouselStartX=0;
carousel.addEventListener(
"touchstart",
event=>{
carouselStartX =
event.touches[0].clientX;
},
{passive:true}
);
carousel.addEventListener(
"touchend",
event=>{
const endX =
event.changedTouches[0].clientX;
const dx =
endX-carouselStartX;
if(Math.abs(dx)<45)
return;
if(dx<0){
selectedBook++;
if(selectedBook>=books.length)
selectedBook=0;
}
else{
selectedBook--;
if(selectedBook<0)
selectedBook=books.length-1;
}
renderCarousel();
requestAnimationFrame(()=>{
const card =
document.querySelectorAll(
".bookCard"
)[selectedBook];
if(card)
card.classList.add(
"selectedBook"
);
});
},
{passive:true}
);
/* =========================
ЗАПУСК
========================= */
buildChapterMenu();
renderCarousel();
</script>
</body>
</html>Game Source: Коты-Воители — Библиотека
Creator: SparkDragon18
Libraries: none
Complexity: complex (2126 lines, 35.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-sparkdragon18" to link back to the original. Then publish at arcadelab.ai/publish.