<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"/>
<meta name="theme-color" content="#5a7d6f"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="default"/>
<title>10 принципов здоровых отношений</title>
<style>
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
margin: 0; padding: 0;
background: #f4efe6; color: #2b3a33;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
overscroll-behavior: none;
}
body {
user-select: none; -webkit-user-select: none;
-webkit-touch-callout: none;
}
input, textarea { user-select: text; -webkit-user-select: text; }
.screen {
position: fixed; inset: 0;
overflow-y: auto; -webkit-overflow-scrolling: touch;
background: #f4efe6;
display: none; flex-direction: column;
}
.screen.active { display: flex; }
/* ---------- Активация ---------- */
#screen-activation {
justify-content: center; align-items: center;
padding: 32px 24px;
background: linear-gradient(160deg, #eef2ec 0%, #f4efe6 45%, #f0e6d8 100%);
}
.act-card {
width: 100%; max-width: 420px; background: #fff;
border-radius: 24px; padding: 36px 28px 32px;
box-shadow: 0 20px 60px rgba(58,80,70,.12);
text-align: center;
}
.act-logo {
width: 72px; height: 72px; border-radius: 50%;
background: linear-gradient(135deg, #5a7d6f, #7ba390);
margin: 0 auto 20px;
display: flex; align-items: center; justify-content: center;
color: #fff; font-size: 32px;
box-shadow: 0 8px 20px rgba(90,125,111,.35);
}
h1.act-title { font-size: 22px; margin: 0 0 8px; letter-spacing: -.3px; }
.act-sub { color: #7a8a82; font-size: 14px; margin: 0 0 28px; line-height: 1.5; }
.act-input {
width: 100%; padding: 16px;
border: 2px solid #e3ddd2; border-radius: 14px;
font-size: 16px; text-align: center; letter-spacing: 2px;
text-transform: uppercase; outline: none;
transition: border-color .2s; background: #faf8f4;
}
.act-input:focus { border-color: #5a7d6f; background: #fff; }
.act-btn {
width: 100%; margin-top: 14px; padding: 16px;
border: none; border-radius: 14px;
background: #5a7d6f; color: #fff;
font-size: 16px; font-weight: 600; cursor: pointer;
transition: transform .1s, background .2s;
}
.act-btn:active { transform: scale(.98); background: #456155; }
.act-note { margin-top: 18px; font-size: 12px; color: #a8b0aa; line-height: 1.5; }
.act-error { color: #b45a4a; font-size: 13px; margin-top: 10px; min-height: 18px; }
/* ---------- Библиотека ---------- */
.lib-header {
padding: 24px 20px 16px;
background: linear-gradient(180deg, #eef2ec, #f4efe6);
position: sticky; top: 0; z-index: 10;
}
.lib-title { font-size: 20px; margin: 0 0 4px; letter-spacing: -.3px; }
.lib-sub { font-size: 13px; color: #7a8a82; margin: 0 0 14px; }
.progress-wrap { background: #e3ddd2; border-radius: 999px; height: 8px; overflow: hidden; }
.progress-bar {
height: 100%; width: 0;
background: linear-gradient(90deg, #5a7d6f, #8fb8a5);
transition: width .4s ease; border-radius: 999px;
}
.lib-list { padding: 8px 16px 40px; display: flex; flex-direction: column; gap: 12px; }
.block-card {
background: #fff; border-radius: 18px; padding: 18px;
display: flex; gap: 14px; align-items: flex-start;
box-shadow: 0 4px 14px rgba(58,80,70,.06);
cursor: pointer; transition: transform .12s, box-shadow .2s;
border: 1px solid rgba(0,0,0,.02);
}
.block-card:active { transform: scale(.985); }
.block-card.locked { cursor: default; opacity: .62; box-shadow: none; }
.block-num {
flex: 0 0 40px; height: 40px; border-radius: 12px;
background: #eef2ec; color: #5a7d6f;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 16px;
}
.block-card.locked .block-num { background: #ece7de; color: #a8b0aa; font-size: 15px; }
.block-body { flex: 1; min-width: 0; }
.block-title { font-size: 15px; font-weight: 600; margin: 0 0 4px; line-height: 1.35; }
.block-status { font-size: 12px; color: #7a8a82; }
.block-status.open { color: #5a7d6f; font-weight: 600; }
.lib-footer { text-align: center; padding: 0 20px 30px; font-size: 11px; color: #b8bfb9; }
/* ---------- Читалка ---------- */
.reader-top {
position: sticky; top: 0; z-index: 10;
display: flex; align-items: center; gap: 12px;
padding: 14px 16px;
background: rgba(244,239,230,.92);
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(0,0,0,.05);
}
.back-btn {
width: 40px; height: 40px; border-radius: 50%;
border: none; background: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 20px; color: #2b3a33; cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,.06);
}
.reader-top-title { font-size: 13px; font-weight: 600; color: #7a8a82;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.reader-content {
padding: 24px 22px 80px;
max-width: 720px; margin: 0 auto; width: 100%;
font-family: Georgia, "Times New Roman", serif;
font-size: 17px; line-height: 1.75; color: #2b3a33;
}
.reader-content .chapter-num {
font-size: 12px; letter-spacing: 2px; text-transform: uppercase;
color: #c9a882; font-weight: 700; margin-bottom: 10px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.reader-content h2 {
font-size: 24px; margin: 0 0 14px; letter-spacing: -.4px; line-height: 1.3;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.reader-content h3 {
font-size: 16px; margin: 28px 0 10px; color: #456155; letter-spacing: -.2px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.reader-content p { margin: 0 0 16px; }
.reader-content .lead { font-size: 19px; line-height: 1.6; color: #3b4a43; font-style: italic; }
.reader-content ul { padding-left: 20px; margin: 0 0 18px; }
.reader-content li { margin-bottom: 10px; }
.reader-content .practice {
background: #eef2ec; border-left: 3px solid #5a7d6f;
border-radius: 0 14px 14px 0; padding: 16px 18px; margin: 22px 0;
}
.reader-content .practice h3 { margin-top: 0; color: #456155; }
.reader-content .practice p:last-child,
.reader-content .practice ul:last-child { margin-bottom: 0; }
.reader-content .note {
font-size: 15px; color: #6b7a72; background: #faf6ef;
padding: 12px 16px; border-radius: 12px;
}
.reader-content .placeholder { color: #a8b0aa; font-style: italic; }
.reader-content .all-open {
margin-top: 40px; text-align: center; font-size: 14px; color: #7a8a82;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
</style>
</head>
<body>
<div id="app">
<!-- ЭКРАН АКТИВАЦИИ -->
<section id="screen-activation" class="screen active">
<div class="act-card">
<div class="act-logo">❤</div>
<h1 class="act-title">10 принципов<br>здоровых отношений</h1>
<p class="act-sub">Ежедневная практика на 10 дней.<br>Каждый день открывается один принцип.</p>
<input id="act-input" class="act-input" type="text" inputmode="text"
autocomplete="off" autocapitalize="characters" spellcheck="false"
placeholder="КОД АКТИВАЦИИ"/>
<div id="act-error" class="act-error"></div>
<button id="act-btn" class="act-btn">Активировать</button>
<p class="act-note">Код выдаётся при покупке.<br>Один код — одно устройство.</p>
</div>
</section>
<!-- БИБЛИОТЕКА -->
<section id="screen-library" class="screen">
<div class="lib-header">
<h1 id="lib-title" class="lib-title">10 принципов</h1>
<p id="lib-sub" class="lib-sub">Открыто 1 из 10</p>
<div class="progress-wrap"><div id="progress-bar" class="progress-bar"></div></div>
</div>
<div id="lib-list" class="lib-list"></div>
<div class="lib-footer">Долгое нажатие на заголовок — сброс (демо)</div>
</section>
<!-- ЧИТАЛКА -->
<section id="screen-reader" class="screen">
<div class="reader-top">
<button id="back-btn" class="back-btn" aria-label="Назад">←</button>
<div id="reader-top-title" class="reader-top-title"></div>
</div>
<div id="reader-body" class="reader-content"></div>
</section>
</div>
<script>
(function () {
'use strict';
/* =========================================================
ЗАЩИТА ОТ КОПИРОВАНИЯ
========================================================= */
['copy','cut','paste','contextmenu','dragstart','selectstart'].forEach(function (ev) {
document.addEventListener(ev, function (e) { e.preventDefault(); }, { passive: false });
});
document.addEventListener('keydown', function (e) {
var k = (e.key || '').toLowerCase();
if ((e.ctrlKey || e.metaKey) && ['c','x','s','u','a','p'].indexOf(k) !== -1) {
e.preventDefault();
}
if (e.key === 'F12') e.preventDefault();
if (e.ctrlKey && e.shiftKey && ['i','j','c'].indexOf(k) !== -1) e.preventDefault();
if (e.metaKey && e.altKey && ['i','j','c'].indexOf(k) !== -1) e.preventDefault();
});
/* =========================================================
КОНТЕНТ (10 БЛОКОВ)
========================================================= */
var BLOCKS = [
{
title: 'Принцип присутствия',
subtitle: 'Быть рядом — значит быть вниманием',
content: '<p class="placeholder">[Текст блока будет добавлен автором.]</p>' +
'<p>Присутствие — это не физическое нахождение рядом, а включённое внимание. Когда вы рядом, но в телефоне, партнёр чувствует себя одиноким вдвоём.</p>' +
'<div class="practice"><h3>Практика</h3><p>10 минут в день — без телефона, без задач, только разговор или молчание вдвоём.</p></div>'
},
{
title: 'Принцип слушания',
subtitle: 'Слышать, а не готовить ответ',
content: '<p class="placeholder">[Текст блока будет добавлен автором.]</p>' +
'<p>Слушать — значит отложить свою реплику и постараться понять, что человек чувствует, а не только что он говорит.</p>' +
'<div class="practice"><h3>Практика</h3><p>Прежде чем ответить, перескажите своими словами то, что услышали: «Правильно ли я понял, что ты…»</p></div>'
},
{
title: 'Принцип честности',
subtitle: 'Говорить правду бережно',
content: '<p class="placeholder">[Текст блока будет добавлен автором.]</p>' +
'<p>Честность без бережности превращается в жестокость. Бережность без честности — в недосказанность. Задача — говорить правду о своих чувствах, не обвиняя.</p>' +
'<div class="practice"><h3>Практика</h3><p>Используйте «Я-сообщения»: «Я чувствую…, когда…, потому что мне важно…»</p></div>'
},
{
title: 'Принцип границ',
subtitle: 'Уважать своё и чужое',
content: '<p class="placeholder">[Текст блока будет добавлен автором.]</p>' +
'<p>Границы — это не стены, а двери с замком изнутри. Здоровая граница говорит: «Это моя территория, и я решаю, кого впустить».</p>' +
'<div class="practice"><h3>Практика</h3><p>Сформулируйте три вещи, которые для вас неприемлемы, и три — которые вы готовы дать партнёру.</p></div>'
},
{
title: 'Конфликт — сигнал, а не угроза',
subtitle: 'Спорить, оставаясь в связи',
content:
'<p class="lead">Конфликт — это не угроза, а сигнал о том, что что-то важное не замечено, не высказано или нарушена граница.</p>' +
'<p>Если вы избегаете ссор, вы избегаете правды. Если вы разрушительно ссоритесь, вы теряете контакт. Задача — научиться спорить, оставаясь в связи: дышать, не переходить на личности, делать паузы, возвращаться к теме.</p>' +
'<h3>Правила здорового конфликта</h3>' +
'<ul>' +
'<li>Не повышайте голос (это реакция на страх).</li>' +
'<li>Не вспоминайте прошлое — только здесь и сейчас.</li>' +
'<li>Если чувствуете, что «закипаете», скажите: «Мне нужно 5 минут, чтобы успокоиться, я вернусь».</li>' +
'<li>После ссоры обязательно вернитесь к ней, чтобы понять, что произошло на уровне чувств.</li>' +
'</ul>' +
'<div class="practice"><h3>Практика</h3>' +
'<p>Выберите безопасную тему для «тренировочной» ссоры (куда поставить мебель). Спорьте 5 минут по правилам, потом остановитесь и обсудите, что вы чувствовали в теле.</p></div>'
},
{
title: 'Принцип ответственности за свои чувства',
subtitle: '«Никто не обязан делать меня счастливым»',
content:
'<h3>Суть</h3>' +
'<p>Партнёр не может и не должен отвечать за ваше настроение, самооценку или душевное равновесие. Это ваша территория. Когда вы говорите «ты меня разозлил», вы отдаёте контроль над своими чувствами другому. На самом деле ваш гнев — это ваша реакция, основанная на вашей истории, ожиданиях и потребностях. Признание этого даёт свободу: вы перестаёте быть жертвой и начинаете выбирать, как реагировать.</p>' +
'<h3>Пример</h3>' +
'<p>Не «Ты меня бесишь, когда не моешь посуду», а «Я чувствую раздражение, потому что для меня важна чистота. Я прошу тебя мыть посуду сразу, но я понимаю, что это моя ценность, а не твоя обязанность».</p>' +
'<h3>Что мешает</h3>' +
'<p>Привычка обвинять, детская позиция «меня должны любить и заботиться».</p>' +
'<div class="practice"><h3>Практика</h3>' +
'<p>В течение дня, когда вы злитесь или обижаетесь на партнёра, скажите себе: «Это моя реакция. Что я могу сделать, чтобы мне стало легче?» — и сделайте это (подышите, выйдите на улицу, напишите письмо, которое не отправите).</p></div>'
},
{
title: 'Принцип благодарности и признания',
subtitle: '«Замечать хорошее каждый день»',
content:
'<h3>Суть</h3>' +
'<p>Наш мозг запрограммирован искать угрозы, поэтому мы легко фиксируем ошибки партнёра и не замечаем его вклада. Благодарность — это сознательная настройка на свет. Она не требует повода: можно поблагодарить за улыбку, за то, что он включил чай, за то, как он относится к детям. Регулярная благодарность меняет климат отношений.</p>' +
'<div class="practice"><h3>Практика</h3><ul>' +
'<li>Введите вечерний ритуал: по очереди называйте три вещи, за которые вы благодарны партнёру за сегодня.</li>' +
'<li>Пишите короткие записки с благодарностью и оставляйте их на видном месте.</li>' +
'<li>Когда вы ссоритесь, напомните себе о том, за что вы цените партнёра — это снижает градус.</li>' +
'</ul></div>' +
'<h3>Что мешает</h3>' +
'<p>Привычка критиковать, установка «он должен, так что спасибо не за что».</p>' +
'<h3>Результат</h3>' +
'<p>Через 2 недели вы заметите, что негативные моменты перестали доминировать.</p>'
},
{
title: 'Принцип телесного контакта',
subtitle: '«Говорить не только словами, но и телом»',
content:
'<h3>Суть</h3>' +
'<p>Тело — самый честный канал связи. Объятия, поглаживания, прикосновения активируют гормон окситоцин, снижают кортизол и возвращают нас в безопасное состояние. Это не только про секс, а про ежедневные «микро-прикосновения»: рука на плече, касание руки, когда проходите мимо, долгое объятие при встрече.</p>' +
'<h3>Как внедрить</h3><ul>' +
'<li>Договоритесь об обязательных 20-секундных объятиях при встрече и прощании.</li>' +
'<li>За ужином положите руку на руку партнёра.</li>' +
'<li>Если партнёр напряжён, просто положите ладонь ему на спину — без слов.</li>' +
'</ul>' +
'<h3>Что мешает</h3>' +
'<p>Стеснительность, культурные запреты, привычка к безтактильному общению.</p>' +
'<p class="note">Важно: прикосновения должны быть без ожидания секса — только как проявление присутствия. Тело запоминает эту заботу быстрее, чем слова.</p>'
},
{
title: 'Принцип совместного творчества',
subtitle: '«Создавайте общие ритуалы и смыслы»',
content:
'<h3>Суть</h3>' +
'<p>Семейная жизнь держится на «мы» — общей истории, ценностях, традициях. Это могут быть большие проекты (ремонт, путешествия) и маленькие ритуалы (совместный завтрак по субботам, просмотр фильма с попкорном, вечерние прогулки). Ритуалы создают предсказуемость и чувство принадлежности, а совместные проекты — ощущение команды.</p>' +
'<h3>Идеи</h3><ul>' +
'<li>Придумайте свой «семейный ужин без телефонов».</li>' +
'<li>Заведите традицию совместных вечерних прогулок.</li>' +
'<li>Раз в месяц выбирайте новое место для свидания.</li>' +
'<li>Создайте «семейный альбом года» с фото и заметками.</li>' +
'</ul>'
},
{
title: 'Принцип общего будущего',
subtitle: '«Строить вместе, а не рядом»',
content: '<p class="placeholder">[Текст блока будет добавлен автором.]</p>' +
'<p>Отношения живут, когда у пары есть общее направление — не одинаковые цели, а согласованное видение: куда мы идём вдвоём.</p>' +
'<div class="practice"><h3>Практика</h3><p>Напишите каждый по три желания на год и обсудите, что из этого вы хотите сделать вместе.</p></div>'
}
];
/* =========================================================
СОСТОЯНИЕ
========================================================= */
var STORAGE_KEY = 'psy10_state_v1';
var DAY_MS = 86400000;
var state = loadState();
function loadState() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : { activatedAt: null, code: null };
} catch (e) {
return { activatedAt: null, code: null };
}
}
function saveState() {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (e) {}
}
function daysSinceActivation() {
if (!state.activatedAt) return -1;
return Math.floor((Date.now() - state.activatedAt) / DAY_MS);
}
function unlockedCount() {
var d = daysSinceActivation();
if (d < 0) return 0;
return Math.min(BLOCKS.length, d + 1);
}
function nextUnlockIn() {
var u = unlockedCount();
if (u >= BLOCKS.length) return 0;
var nextTime = state.activatedAt + u * DAY_MS;
return Math.max(0, nextTime - Date.now());
}
function formatDuration(ms) {
var totalSec = Math.floor(ms / 1000);
var h = Math.floor(totalSec / 3600);
var m = Math.floor((totalSec % 3600) / 60);
var s = totalSec % 60;
if (h > 0) return h + ' ч ' + m + ' мин';
if (m > 0) return m + ' мин ' + s + ' с';
return s + ' с';
}
/* =========================================================
НАВИГАЦИЯ
========================================================= */
function showScreen(id) {
var screens = document.querySelectorAll('.screen');
for (var i = 0; i < screens.length; i++) screens[i].classList.remove('active');
document.getElementById(id).classList.add('active');
}
/* =========================================================
АКТИВАЦИЯ
========================================================= */
var actInput = document.getElementById('act-input');
var actBtn = document.getElementById('act-btn');
var actError = document.getElementById('act-error');
actBtn.addEventListener('click', function () {
var code = (actInput.value || '').trim().toUpperCase();
if (code.length < 6) {
actError.textContent = 'Введите корректный код (не менее 6 символов).';
return;
}
actError.textContent = '';
state.activatedAt = Date.now();
state.code = code;
saveState();
openLibrary();
});
actInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') actBtn.click();
});
/* =========================================================
БИБЛИОТЕКА
========================================================= */
var libTimer = null;
function openLibrary() {
showScreen('screen-library');
renderLibrary();
if (libTimer) clearInterval(libTimer);
libTimer = setInterval(tick, 1000);
}
function tick() {
var before = unlockedCount();
updateTimers();
if (unlockedCount() !== before) renderLibrary();
}
function renderLibrary() {
var list = document.getElementById('lib-list');
var u = unlockedCount();
var total = BLOCKS.length;
document.getElementById('lib-sub').textContent = 'Открыто ' + u + ' из ' + total;
document.getElementById('progress-bar').style.width = (u / total * 100) + '%';
list.innerHTML = '';
BLOCKS.forEach(function (b, i) {
var unlocked = i < u;
var card = document.createElement('div');
card.className = 'block-card' + (unlocked ? '' : ' locked');
var statusHtml;
if (unlocked) {
statusHtml = '<div class="block-status open" data-idx="' + i + '">Открыт — читать</div>';
} else if (i === u) {
statusHtml = '<div class="block-status" data-idx="' + i + '">Откроется через <span class="timer">…</span></div>';
} else {
statusHtml = '<div class="block-status" data-idx="' + i + '">Заблокирован</div>';
}
card.innerHTML =
'<div class="block-num">' + (unlocked ? (i + 1) : '🔒') + '</div>' +
'<div class="block-body">' +
'<div class="block-title">' + b.title + '</div>' +
statusHtml +
'</div>';
if (unlocked) {
card.addEventListener('click', function () { openReader(i); });
}
list.appendChild(card);
});
updateTimers();
}
function updateTimers() {
var el = document.querySelector('.timer');
if (!el) return;
el.textContent = formatDuration(nextUnlockIn());
}
/* =========================================================
ЧИТАЛКА
========================================================= */
function openReader(i) {
if (i >= unlockedCount()) return;
var b = BLOCKS[i];
document.getElementById('reader-top-title').textContent = 'Принцип ' + (i + 1);
document.getElementById('reader-body').innerHTML =
'<div class="chapter-num">Принцип ' + (i + 1) + ' из ' + BLOCKS.length + '</div>' +
'<h2>' + b.title + '</h2>' +
(b.subtitle ? '<p class="lead">' + b.subtitle + '</p>' : '') +
b.content +
(unlockedCount() >= BLOCKS.length
? '<div class="all-open">Все принципы открыты. Приятного чтения.</div>'
: '');
showScreen('screen-reader');
document.getElementById('screen-reader').scrollTop = 0;
}
document.getElementById('back-btn').addEventListener('click', function () {
openLibrary();
});
/* =========================================================
СБРОС (демо, долгое нажатие на заголовок)
========================================================= */
var pressTimer = null;
var libTitleEl = document.getElementById('lib-title');
function startPress() {
pressTimer = setTimeout(function () {
if (confirm('Сбросить активацию? (демо-функция для тестирования)')) {
try { localStorage.removeItem(STORAGE_KEY); } catch (e) {}
state = { activatedAt: null, code: null };
actInput.value = '';
actError.textContent = '';
showScreen('screen-activation');
}
}, 2000);
}
function cancelPress() { clearTimeout(pressTimer); }
['touchstart', 'mousedown'].forEach(function (ev) { libTitleEl.addEventListener(ev, startPress); });
['touchend', 'touchcancel', 'mouseup', 'mouseleave'].forEach(function (ev) { libTitleEl.addEventListener(ev, cancelPress); });
/* =========================================================
ВОЗВРАТ ИЗ ФОНА
========================================================= */
document.addEventListener('visibilitychange', function () {
if (!document.hidden && state.activatedAt) {
renderLibrary();
}
});
/* =========================================================
СТАРТ
========================================================= */
if (state.activatedAt) {
openLibrary();
} else {
showScreen('screen-activation');
}
})();
</script>
</body>
</html>