[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes">
<title>Дневник · два лица</title>
<!-- Подключаем шрифты -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;600;700&family=Marck+Script&family=Bad+Script&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
min-height: 100vh;
background: #1c1410;
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
perspective: 2600px;
}
.book-wrapper {
width: 100%;
max-width: 800px;
border-radius: 8px 24px 24px 8px;
background: #2b1b12;
padding: 14px 14px 14px 10px;
background-image: linear-gradient(145deg, #3f2a1b 0%, #1f130b 80%);
}
.book-interior {
background: #2b1d13;
border-radius: 6px 20px 20px 6px;
padding: 20px 20px 20px 14px;
box-shadow: inset 0 0 15px #0a0603, inset 0 0 0 1px #63482e;
}
.flip-scene {
position: relative;
width: 100%;
height: 650px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
transform-style: preserve-3d;
}
.page {
position: absolute;
width: 90%;
max-width: 620px;
height: 620px;
border-radius: 0 8px 8px 0;
box-shadow:
0 10px 20px rgba(0,0,0,0.7),
-10px 6px 18px rgba(0,0,0,0.8),
inset 0 -3px 8px rgba(0,0,0,0.6);
padding: 35px 30px 30px 40px;
border-left: 4px solid #4f3824;
border-right: 1px solid #4d3621;
font-weight: 400;
line-height: 1.8;
word-break: break-word;
backface-visibility: hidden;
transform-style: preserve-3d;
transition: transform 0.8s cubic-bezier(0.45, 0.05, 0.2, 0.95);
cursor: default;
transform-origin: left center;
display: flex;
flex-direction: column;
}
/* Тип первый (зелёный) — Marck Script */
.page.character-one {
background:
radial-gradient(circle at 25% 25%, rgba(50, 80, 50, 0.4), transparent 60%),
linear-gradient(160deg, #3b2f25, #1f3a2a);
color: #e4dbc6;
font-family: 'Marck Script', cursive;
border-left-color: #5b6e4b;
}
.page.character-one .page-lines {
background-image: repeating-linear-gradient(transparent 0px, transparent 30px, #9b8a6b 30px, #9b8a6b 31px);
opacity: 0.2;
}
/* Тип второй (красный) — Bad Script */
.page.character-two {
background:
radial-gradient(circle at 70% 30%, rgba(120, 0, 0, 0.25), transparent 60%),
linear-gradient(#130c0c, #0b0505);
color: #f1e7e7;
font-family: 'Bad Script', cursive;
border-left-color: #7a3a3a;
}
.page.character-two .page-lines {
background-image: repeating-linear-gradient(transparent 0px, transparent 30px, #a55a5a 30px, #a55a5a 31px);
opacity: 0.2;
}
.page-content {
position: relative;
z-index: 5;
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
}
.date-plate {
font-size: 28px;
padding: 8px 24px;
margin-bottom: 15px;
display: inline-block;
letter-spacing: 2px;
text-transform: uppercase;
background: rgba(0,0,0,0.3);
backdrop-filter: blur(2px);
border: 1px solid currentColor;
align-self: flex-start;
font-family: 'Caveat', cursive;
flex-shrink: 0;
}
.character-one .date-plate {
background: rgba(50,70,40,0.5);
border-color: rgba(100,120,80,0.8);
color: #f1f0e6;
box-shadow: 0 6px 18px rgba(0,0,0,0.12);
}
.character-two .date-plate {
background: linear-gradient(90deg, #6e0f0f, #9b1c1c);
border: none;
color: #fff;
box-shadow: 0 0 20px rgba(180,0,0,0.4), 0 10px 30px rgba(0,0,0,0.6);
}
.entry-bubble {
flex: 1 1 auto;
min-height: 0;
border-radius: 26px;
padding: 25px 30px;
overflow-y: auto;
scrollbar-width: thin;
box-sizing: border-box;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: rgba(200, 180, 150, 0.3);
border-radius: 10px;
border: 2px solid rgba(0, 0, 0, 0.2);
}
&::-webkit-scrollbar-thumb:hover {
background: rgba(200, 180, 150, 0.5);
}
}
.character-one .entry-bubble {
background: #3a4a36;
box-shadow: 0 12px 28px rgba(0,0,0,0.2), inset 0 0 35px rgba(0,0,0,0.08);
color: #e4dbc6;
scrollbar-color: #9b8a6b #2a3a26;
}
.character-two .entry-bubble {
background: rgba(15,0,0,0.6);
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 20px 50px rgba(0,0,0,0.8), inset 0 0 50px rgba(150,0,0,0.15);
color: #f1e7e7;
scrollbar-color: #a55a5a #2a0a0a;
}
.entry {
font-size: 22px;
line-height: 1.7;
white-space: pre-wrap;
user-select: text;
padding-right: 5px;
}
/* Специфичные настройки для каждого рукописного шрифта */
.character-one .entry {
font-size: 24px;
letter-spacing: 0.5px;
word-spacing: 2px;
}
.character-two .entry {
font-size: 22px;
line-height: 1.9;
font-weight: 400;
font-style: normal;
}
/* текстурный слой */
.page::before {
content: '';
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" viewBox="0 0 160 160"><filter id="noise2"><feTurbulence baseFrequency="0.9 1.2" numOctaves="2" /><feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.25 0"/></filter><rect width="160" height="160" filter="url(%23noise2)" opacity="0.25"/></svg>');
opacity: 0.2;
pointer-events: none;
border-radius: inherit;
mix-blend-mode: multiply;
}
.page-lines {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
z-index: 2;
}
/* Убираем номер страницы и уголок */
.page-num, .page::after {
display: none;
}
.page.flipped-left {
transform: rotateY(-170deg) translateZ(8px);
box-shadow: -20px 8px 30px rgba(0,0,0,0.9);
filter: brightness(0.75) sepia(0.3);
z-index: 5;
}
.page.front-right {
transform: rotateY(0deg) translateZ(30px);
z-index: 30;
box-shadow: 0 15px 35px rgba(0,0,0,0.9), -10px 10px 25px rgba(0,0,0,0.8);
}
.page.stack-left {
transform: rotateY(0deg) translateX(-100%) translateZ(1px);
opacity: 0;
pointer-events: none;
transition: none;
box-shadow: none;
}
.gutter {
position: absolute;
width: 14px;
height: 100%;
left: 45%;
top: 0;
background: radial-gradient(ellipse at center, #3a281c 0%, #1b110a 100%);
transform: translateX(-50%) rotateY(20deg);
box-shadow: -3px 0 12px #0f0803, inset 0 0 6px #634b33;
z-index: 50;
pointer-events: none;
border-radius: 20%;
}
.nav-panel {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
margin-top: 30px;
margin-bottom: 10px;
}
.nav-btn {
background: #6b1e1e;
border: 3px solid #2f140e;
font-size: 2.1rem;
font-weight: bold;
width: 70px;
height: 70px;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 8px 0 #2e110b, 0 0 15px #520000;
transition: 0.08s linear;
color: #f0cfb0;
display: inline-flex;
align-items: center;
justify-content: center;
font-family: 'Caveat', monospace;
text-shadow: 0 0 5px #ffb273;
}
.nav-btn:active {
transform: translateY(6px);
box-shadow: 0 2px 0 #2e110b, 0 0 20px #a13030;
}
.nav-btn.disabled {
opacity: 0.3;
pointer-events: none;
box-shadow: 0 4px 0 #2e110b;
transform: translateY(4px);
filter: grayscale(0.7);
}
.page-indicator {
background: #281e16;
padding: 12px 32px;
border-radius: 60px;
font-size: 2rem;
font-weight: bold;
color: #dbbc96;
box-shadow: inset 0 3px 10px #0e0804, 0 8px 0 #3e261b, 0 0 15px #4d1f1f;
font-family: 'Caveat', cursive;
border: 1px solid #7b4f31;
min-width: 170px;
text-align: center;
text-shadow: 0 0 8px #ab5f2c;
}
.footer-note {
text-align: center;
color: #ac8a67;
margin-top: 12px;
font-family: 'Caveat';
font-size: 1.4rem;
letter-spacing: 2px;
text-shadow: 0 0 8px #3b1c0c;
}
/* Адаптация под мобилки */
@media (max-width: 600px) {
.book-wrapper {
padding: 8px;
border-radius: 4px 16px 16px 4px;
}
.flip-scene {
height: 550px;
}
.page {
height: 520px;
padding: 20px 20px 20px 25px;
}
.entry-bubble {
padding: 15px;
}
.nav-btn {
width: 55px;
height: 55px;
font-size: 1.8rem;
}
.page-indicator {
padding: 8px 16px;
font-size: 1.5rem;
min-width: 130px;
}
.date-plate {
font-size: 22px;
padding: 5px 18px;
}
.character-one .entry {
font-size: 20px;
}
.character-two .entry {
font-size: 18px;
}
}
@media (max-width: 400px) {
.flip-scene {
height: 500px;
}
.page {
height: 470px;
padding: 15px;
}
.entry-bubble {
padding: 12px;
}
.character-one .entry {
font-size: 18px;
}
.character-two .entry {
font-size: 16px;
}
}
</style>
</head>
<body>
<div class="book-wrapper">
<div class="book-interior">
<div class="flip-scene" id="flipScene">
<div class="gutter"></div>
</div>
<div class="nav-panel">
<button class="nav-btn" id="prevPageBtn" aria-label="предыдущая страница">◀</button>
<span class="page-indicator" id="indicator">стр. 1 / 10</span>
<button class="nav-btn" id="nextPageBtn" aria-label="следующая страница">▶</button>
</div>
<div class="footer-note">✦ memento mori ✦</div>
</div>
</div>
<script>
(function() {
const pagesData = [
{
type: 'character-one',
date: '12 октября',
text: 'Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.\n\nLorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor.\n\nЗарегистрирован: 2013-12-25\nПриглашений: 0\nСообщений: 63\nУважение: +1\nПозитив: +0\n\nПровел на форуме:\n21 час 23 минуты\nIP: 159.148.58.10\n\nАктивен 2 часа 17 минут'
},
{
type: 'character-two',
date: '13 октября',
text: 'Сегодня ночью спал просто отвратительно. По ощущениям будто кто-то подкинул мне в голову взрывчатку - подкрался, пока я дремал, открыл черепную крышку, сбросил туда всякой дряни, закрыл и убежал со всех ног. Ментальный подкидыш, взрывоопасная бредятина. Причем сон начинался просто чудесно - я, кажется, был на задании или просто развлекался славной "охотой", я отчетливо помню, как пролезал в чье-то тело, подслушивал чужой пульс, окунался в чужие крики, но потом.. потом я полностью потерял контроль над своим телом. Нет, я продолжил начатое, но будто бы уже не по своей воле, я наносил своей жертве увечья, но не там, где хотел их нанести, и пытал ее не теми способами, которыми всегда предпочитал.. Из дирижёра я превратился в обычного зрителя, пусть и посаженного в первых рядах. Очень гадкое ощущение, когда ты, вроде бы, делаешь все то, что хотел сделать изначально, но по чьей-то указке, по чьему-то велению, в совершенно ином, не согласованном с тобой, виде. Я потратил три тысячелетия на то, чтобы двинуть хотя бы пальцем, чтобы сделать хоть что-то самостоятельно, а не лицезреть парализованным истуканом, как льются реки крови из вспоротых глоток. Творить нечто настолько упоительно сладкое и грешное, но не по доброй воле - самое мерзкое, самое унизительное и святотатственное, что можно придумать. Грешить нужно осознанно. По велению своего выбора, своего решения, своей тяги, иначе это просто кукольная постановка, где ты болтаешься на невидимых ниточках, где ты и не убийца даже, а лишь инструмент того, кто в действительности является убийцей. Заснуть снова я так и не смог, однако днем был весьма бодр. Подумаешь, скверный сон. И не такое случалось.'
},
{ type: 'character-one', date: '14 октября', text: 'Сегодня был продуктивный день. Удалось закончить старые дела и даже немного поразмышлять о будущем. Кажется, я на верном пути. Нужно записать несколько идей:\n\n- создать новый ритуал\n- пересмотреть старые записи\n- подготовить ингредиенты' },
{ type: 'character-two', date: '15 октября', text: 'Опять этот кошмар. Я снова видел себя со стороны, будто марионетку. Невыносимо чувствовать, что твоими руками кто-то управляет. Три тысячелетия борьбы за свободу воли — и такие сны. Грешить нужно осознанно, иначе это просто кукольный театр. Надо будет завтра поискать способ защитить разум.' },
{ type: 'character-one', date: '16 октября', text: 'Список дел:\n• собрать травы\n• наточить перья\n• написать письмо\n• зажечь свечи\n\nПогода сегодня под стать настроению — туман и лёгкий дождь. Самое время для размышлений.' },
{ type: 'character-two', date: '17 октября', text: 'Вспомнил один старый рецепт эликсира:\n— корень дягиля\n— полынь\n— железный купорос (щепотка)\nНастаивать всю ночь на растущей луне.\n\nНадо будет проверить, работает ли. И заодно записать ощущения.' },
{ type: 'character-one', date: '18 октября', text: 'Сегодня листал старые записи. Интересно, как меняется почерк и мысли со временем. Некоторые идеи кажутся сейчас наивными, но в них есть своя прелесть. Цитата на сегодня: "Мы сами создаём тени, которые нас пугают".' },
{ type: 'character-two', date: '19 октября', text: 'Ночь выдалась беспокойной. Снова эти видения. Но на этот раз я смог чуть дольше удержать контроль. Прогресс. Возможно, если тренироваться, смогу полностью управлять снами. Надо записывать каждую деталь.' },
{ type: 'character-one', date: '20 февраля', text: 'За окном туман. В комнате пахнет воском и старыми бумагами. Сегодня особенный день. Нужно вспомнить все ритуалы и ничего не упустить. Время течёт так быстро.' },
{ type: 'character-two', date: '21 октября', text: 'Последняя запись в этом блокноте. Книга закрывается, но истории остаются. Помни о свете даже в темноте. И помни: твоя воля принадлежит только тебе.' }
];
const totalPages = pagesData.length;
let currentPageIndex = 0;
const scene = document.getElementById('flipScene');
const indicator = document.getElementById('indicator');
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
function buildPages() {
scene.innerHTML = '';
const gutterDiv = document.createElement('div');
gutterDiv.className = 'gutter';
scene.appendChild(gutterDiv);
pagesData.forEach((data, index) => {
const pageDiv = document.createElement('div');
pageDiv.className = `page ${data.type}`;
pageDiv.setAttribute('data-index', index);
const lines = document.createElement('div');
lines.className = 'page-lines';
const contentDiv = document.createElement('div');
contentDiv.className = 'page-content';
const datePlate = document.createElement('div');
datePlate.className = 'date-plate';
datePlate.innerText = data.date;
const bubbleDiv = document.createElement('div');
bubbleDiv.className = 'entry-bubble';
const textDiv = document.createElement('div');
textDiv.className = 'entry';
textDiv.innerText = data.text;
bubbleDiv.appendChild(textDiv);
contentDiv.appendChild(datePlate);
contentDiv.appendChild(bubbleDiv);
pageDiv.appendChild(lines);
pageDiv.appendChild(contentDiv);
scene.appendChild(pageDiv);
});
updatePagesState();
}
function updatePagesState() {
const pages = document.querySelectorAll('.page');
pages.forEach((page, idx) => {
page.classList.remove('front-right', 'flipped-left', 'stack-left');
if (idx === currentPageIndex) {
page.classList.add('front-right');
} else if (idx < currentPageIndex) {
page.classList.add('flipped-left');
} else {
page.classList.add('stack-left');
}
});
indicator.innerText = `стр. ${currentPageIndex + 1} / ${totalPages}`;
prevBtn.classList.toggle('disabled', currentPageIndex === 0);
nextBtn.classList.toggle('disabled', currentPageIndex === totalPages - 1);
}
function nextPage() {
if (currentPageIndex >= totalPages - 1) return;
const currentPage = document.querySelector(`.page[data-index="${currentPageIndex}"]`);
const nextPageElem = document.querySelector(`.page[data-index="${currentPageIndex + 1}"]`);
if (!currentPage || !nextPageElem) return;
nextPageElem.classList.remove('stack-left');
void currentPage.offsetWidth;
currentPage.classList.remove('front-right');
currentPage.classList.add('flipped-left');
nextPageElem.classList.remove('stack-left');
nextPageElem.classList.add('front-right');
currentPageIndex++;
const pages = document.querySelectorAll('.page');
pages.forEach((page, idx) => {
if (idx < currentPageIndex) {
page.classList.remove('front-right', 'stack-left');
page.classList.add('flipped-left');
} else if (idx === currentPageIndex) {
// уже обработано
} else {
page.classList.remove('front-right', 'flipped-left');
page.classList.add('stack-left');
}
});
indicator.innerText = `стр. ${currentPageIndex + 1} / ${totalPages}`;
prevBtn.classList.toggle('disabled', currentPageIndex === 0);
nextBtn.classList.toggle('disabled', currentPageIndex === totalPages - 1);
}
function prevPage() {
if (currentPageIndex <= 0) return;
const currentPage = document.querySelector(`.page[data-index="${currentPageIndex}"]`);
const prevPageElem = document.querySelector(`.page[data-index="${currentPageIndex - 1}"]`);
if (!currentPage || !prevPageElem) return;
prevPageElem.classList.remove('flipped-left', 'stack-left');
void currentPage.offsetWidth;
currentPage.classList.remove('front-right');
currentPage.classList.add('flipped-left');
prevPageElem.classList.remove('flipped-left', 'stack-left');
prevPageElem.classList.add('front-right');
currentPageIndex--;
const pages = document.querySelectorAll('.page');
pages.forEach((page, idx) => {
if (idx < currentPageIndex) {
page.classList.remove('front-right', 'stack-left');
page.classList.add('flipped-left');
} else if (idx === currentPageIndex) {
// front-right
} else if (idx > currentPageIndex) {
page.classList.remove('front-right', 'flipped-left');
page.classList.add('stack-left');
}
});
indicator.innerText = `стр. ${currentPageIndex + 1} / ${totalPages}`;
prevBtn.classList.toggle('disabled', currentPageIndex === 0);
nextBtn.classList.toggle('disabled', currentPageIndex === totalPages - 1);
}
buildPages();
prevBtn.addEventListener('click', prevPage);
nextBtn.addEventListener('click', nextPage);
})();
</script>
</body>
</html>[/html]