[hideprofile]
<!--HTML-->
<div class="chronicle-app">
<div class="chronicle-header">
<div class="chronicle-main-title">ХРОНОЛОГИЯ ЭПИЗОДОВ</div>
<div class="chronicle-subtitle">В этой теме можно найти все эпизоды и посмотреть, что вообще играется на форуме.<br>Хронология ведётся администрацией и регулярно обновляется.</div>
</div>
<div class="chronicle-controls">
<div class="chronicle-filter">
<label for="yearFilter">Год:</label>
<select id="yearFilter" class="chronicle-select">
<option value="all">Не выбрано</option>
</select>
</div>
<div class="chronicle-filter">
<label for="participantFilter">Участник:</label>
<select id="participantFilter" class="chronicle-select">
<option value="all">Не выбрано</option>
</select>
</div>
<div class="chronicle-filter">
<label for="statusFilter">Статус:</label>
<select id="statusFilter" class="chronicle-select">
<option value="all">Не выбрано</option>
<option value="активный">Активный</option>
<option value="завершённый">Завершённый</option>
<option value="незаконченный">Незаконченный</option>
</select>
</div>
<button id="resetFilters" class="chronicle-btn">Сбросить</button>
<div class="chronicle-stats">
<span class="stats-badge">эпизодов: <b id="episodeCount">0</b></span>
</div>
</div>
<div id="chronicleContainer"></div>
</div>
<style>
.chronicle-app {
background-image: url('https://i.ibb.co/WRzrLDB/image.jpg');
background-size: cover;
background-position: center;
width: 100%;
max-height: 830px;
margin: -15px auto;
padding: 100px 50px 35px 50px;
box-sizing: border-box;
border-radius: 8px;
display: flex;
flex-direction: column;
color: var(--text1);
}
.chronicle-header {
flex-shrink: 0;
margin-bottom: 20px;
text-align: center;
}
.chronicle-main-title {
font-size: 22px;
font-weight: bold;
color: var(--text4);
text-transform: uppercase;
letter-spacing: 3px;
text-shadow: 0px 0px 6px rgba(0, 0, 0, 1);
margin-bottom: 8px;
}
.chronicle-subtitle {
font-size: 12px;
color: var(--text3, #bbb);
max-width: 80%;
margin: 0 auto;
line-height: 1.5;
font-style: italic;
text-shadow: 0px 0px 6px rgba(0, 0, 0, 1);
}
.chronicle-controls {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
align-items: center;
background: transparent;
padding: 0;
}
.chronicle-filter {
display: flex;
flex-direction: column;
gap: 5px;
}
.chronicle-controls label {
font-size: 11px;
font-weight: bold;
color: var(--text4);
text-transform: uppercase;
}
.chronicle-select {
padding: 6px 10px !important;
color: #aba8a6 !important;
background: #211c18 !important;
border: none !important;
border-radius: 10px;
font: 400 11px var(--font), arial !important;
box-sizing: border-box;
max-width: 100%;
}
.chronicle-select option {
background: #1a1612;
color: #b9ae95;
}
.chronicle-btn {
background: rgba(18, 16, 14, .85);
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
border: 1px solid #aba8a6;
border-radius: 20px;
color: #aba8a6;
padding: 8px 20px;
cursor: pointer;
font-weight: bold;
text-transform: uppercase;
font-size: 11px;
transition: transform 0.2s, box-shadow 0.2s;
margin-left: auto;
}
.chronicle-btn:hover {
transform: translateY(-2px);
}
.chronicle-stats {
font-size: 12px;
color: #b9ae95;
margin-left: 10px;
display: flex;
align-items: center;
height: 100%;
}
.stats-badge {
background: rgba(18, 16, 14, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid #756a5e;
border-radius: 20px;
padding: 7px 15px;
font-size: 11px;
color: #756a5e;
font-weight: 500;
letter-spacing: 0.5px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
#chronicleContainer {
flex: 1;
overflow-y: auto;
padding-right: 5px;
min-height: 0;
}
.chronicle-timeline {
max-width: 100%;
font-family: inherit;
font-size: 10px;
}
.chronicle-year-divider {
display: flex;
align-items: center;
justify-content: center;
margin: 12px 0 8px;
color: var(--text4, #d4c9a8);
font-weight: bold;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 2px;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.chronicle-year-divider::before,
.chronicle-year-divider::after {
content: "";
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, var(--text2), transparent);
margin: 0 15px;
}
.chronicle-year-divider::before {
background: linear-gradient(to left, transparent, var(--text2), transparent);
}
.chronicle-item {
display: flex;
flex-wrap: wrap;
margin-top: 3px;
padding: 8px 8px;
border-bottom: 1px solid rgba(189, 185, 184, 0.1);
transition: background 0.2s;
background: rgba(18, 16, 14, 0.6);
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
border: 1px solid #393028;
border-radius: 10px;
}
.chronicle-item.status-active {
background: var(--beg100);
border-left: 4px solid var(--accent);
padding-left: 8px;
}
.chronicle-item.status-active .chronicle-title a {
color: var(--links) !important;
}
.chronicle-item.status-active .chronicle-title a:hover {
color: var(--links2) !important;
}
.chronicle-item.status-closed {
opacity: 1;
border-left: 4px solid var(--accent2);
padding-left: 8px;
background: var(--beg100);
}
.chronicle-item.status-unfinished {
border: 1px solid #393028;
padding-left: 8px;
opacity: 0.8;
background: var(--beg100);
}
.chronicle-left {
flex: 0 0 35%;
padding-right: 15px;
box-sizing: border-box;
}
.chronicle-right {
flex: 0 0 65%;
box-sizing: border-box;
}
.chronicle-date {
font-weight: bold;
color: var(--text1);
margin-bottom: 3px;
font-size: 10px;
}
.chronicle-title {
font-weight: bold;
margin-bottom: 3px;
font-size: 13px;
}
.chronicle-title a {
color: var(--links);
text-decoration: none;
}
.chronicle-title a:hover {
color: var(--accent) !important;
}
.chronicle-participants {
font-size: 9px;
color: var(--text1);
margin-top: 3px;
display: flex;
flex-direction: column;
gap: 3px;
}
.chronicle-location {
font-weight: bold;
color: var(--text1);
margin-bottom: 3px;
font-size: 10px;
}
.chronicle-desc {
font-size: 11px;
color: var(--text1);
line-height: 1.4;
}
.participant-tag {
display: inline-block;
background: var(--beg300);
padding: 2px 6px;
border-radius: 12px;
margin-right: 4px;
font-size: 10px;
cursor: pointer;
transition: background 0.2s;
align-self: flex-start;
}
.participant-tag:hover {
background: var(--beg100);
}
.chronicle-loading {
text-align: center;
padding: 40px;
color: var(--text3);
}
@media (max-width: 720px) {
.chronicle-app {
padding: 50px 20px;
}
.chronicle-left,
.chronicle-right {
flex: 0 0 100%;
padding-right: 0;
margin-bottom: 5px;
}
.chronicle-controls {
flex-direction: column;
align-items: stretch;
}
.chronicle-btn {
margin-left: 0;
}
.chronicle-year-divider {
font-size: 12px;
}
.chronicle-year-divider::before,
.chronicle-year-divider::after {
margin: 0 8px;
}
.chronicle-main-title {
font-size: 18px;
}
.chronicle-subtitle {
font-size: 11px;
max-width: 100%;
}
.chronicle-select {
min-width: 100%;
}
.chronicle-stats {
margin-left: 0;
justify-content: center;
}
}
</style>
<script>
(function() {
function init() {
var container = document.getElementById('chronicleContainer');
if (!container) return setTimeout(init, 50);
// ========== БАЗА ЭПИЗОДОВ ==========
// Добавляйте новые эпизоды по образцу ниже.
// Поля:
// date – дата эпизода (ДД.ММ.ГГГГ)
// title – название эпизода
// participants – массив участников (имена в кавычках через запятую)
// status – один из: "активный", "завершённый", "незаконченный"
// link – ссылка на тему эпизода
// year – год для группировки (строка)
// location – место действия
// description – краткое описание
var rawData = [
{
date: "11.10.10199",
title: "хоспаде куда я жмав",
participants: ["Leto Atreides", "Lady Jessica"],
status: "активный",
link: "https://dune.rusff.me/",
year: "10199",
location: "Арракис",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." },
{
date: "20.03.10199",
title: "я вредоносный",
participants: ["Feyd-Rautha Harkonnen", "Piter de Vries"],
status: "активный",
link: "https://dune.rusff.me/",
year: "10199",
location: "Гиеди Прайм",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." },
{
date: "30.06.10198",
title: "plotting... scheming even",
participants: ["Margot Fenring", "Lady Jessica"],
status: "активный",
link: "https://dune.rusff.me/",
year: "10198",
location: "Арракис",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." },
{
date: "24.09.10198",
title: "абвгдеденьги",
participants: ["Vladimir Harkonnen", "Hasimir Fenring"],
status: "завершённый",
link: "https://dune.rusff.me/",
year: "10198",
location: "Гиеди Прайм",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." },
{
date: "01.01.10198",
title: "please find alternative ways to disappoint your father",
participants: ["Leto Atreides", "Ariste Atreides"],
status: "незаконченный",
link: "https://dune.rusff.me/",
year: "10198",
location: "Арракис",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." },
{
date: "23.06.10198",
title: "вам наверно интересно как мы тут оказались",
participants: ["Bronso Vernius", "Stilgar"],
status: "завершённый",
link: "https://dune.rusff.me/",
year: "10198",
location: "Кайтан",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." },
{
date: "23.02.10199",
title: "отдел контента",
participants: ["Vladimir Harkonnen", "Shaddam Corrino"],
status: "завершённый",
link: "https://dune.rusff.me/",
year: "10199",
location: "Кайтан",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." }
// Добавляйте новые эпизоды сюда (не забудьте запятую после предыдущей строки)
];
var allEpisodes = rawData;
var filteredEpisodes = [];
var currentSort = { field: 'date', direction: 'asc' };
var yearFilter = document.getElementById('yearFilter');
var participantFilter = document.getElementById('participantFilter');
var statusFilter = document.getElementById('statusFilter');
var resetBtn = document.getElementById('resetFilters');
var countSpan = document.getElementById('episodeCount');
// Собираем уникальные годы и участников
var yearsSet = new Set();
var participantsSet = new Set();
allEpisodes.forEach(function(ep) {
yearsSet.add(ep.year);
ep.participants.forEach(function(p) { participantsSet.add(p); });
});
// Заполняем выпадающий список годов (по убыванию)
while (yearFilter.options.length > 1) yearFilter.remove(1);
Array.from(yearsSet).sort(function(a, b) { return b - a; }).forEach(function(year) {
var option = document.createElement('option');
option.value = year;
option.textContent = year;
yearFilter.appendChild(option);
});
// Заполняем выпадающий список участников
while (participantFilter.options.length > 1) participantFilter.remove(1);
Array.from(participantsSet).sort().forEach(function(participant) {
var option = document.createElement('option');
option.value = participant;
option.textContent = participant;
participantFilter.appendChild(option);
});
// Вспомогательная функция: преобразовать дату ДД.ММ.ГГГГ в число для сравнения
function parseDate(dateStr) {
var parts = dateStr.split('.');
if (parts.length !== 3) return 0;
var day = parseInt(parts[0], 10);
var month = parseInt(parts[1], 10);
var year = parseInt(parts[2], 10);
return year * 10000 + month * 100 + day;
}
function applyFilters() {
var selectedYear = yearFilter.value;
var selectedParticipant = participantFilter.value;
var selectedStatus = statusFilter.value;
filteredEpisodes = allEpisodes.filter(function(ep) {
if (selectedYear !== 'all' && ep.year !== selectedYear) return false;
if (selectedParticipant !== 'all' && ep.participants.indexOf(selectedParticipant) === -1) return false;
if (selectedStatus !== 'all' && ep.status !== selectedStatus) return false;
return true;
});
// Сортировка
filteredEpisodes.sort(function(a, b) {
if (currentSort.field === 'date') {
var dateA = parseDate(a.date);
var dateB = parseDate(b.date);
return currentSort.direction === 'asc' ? dateA - dateB : dateB - dateA;
} else {
var valA = String(a[currentSort.field]).toLowerCase();
var valB = String(b[currentSort.field]).toLowerCase();
if (valA < valB) return currentSort.direction === 'asc' ? -1 : 1;
if (valA > valB) return currentSort.direction === 'asc' ? 1 : -1;
return 0;
}
});
renderTimeline();
}
function renderTimeline() {
if (filteredEpisodes.length === 0) {
container.innerHTML = '<div class="chronicle-loading">Нет эпизодов, соответствующих фильтрам.</div>';
countSpan.textContent = '0';
return;
}
var grouped = {};
filteredEpisodes.forEach(function(ep) {
if (!grouped[ep.year]) grouped[ep.year] = [];
grouped[ep.year].push(ep);
});
// Сортировка годов по убыванию
var years = Object.keys(grouped).sort(function(a, b) { return b - a; });
var html = '<div class="chronicle-timeline">';
years.forEach(function(year) {
html += '<div class="chronicle-year-divider">' + year + ' AG</div>';
grouped[year].forEach(function(ep) {
var statusClass = '';
if (ep.status === 'активный') statusClass = 'status-active';
else if (ep.status === 'завершённый') statusClass = 'status-closed';
else if (ep.status === 'незаконченный') statusClass = 'status-unfinished';
var participantsHtml = '<div class="chronicle-participants">' +
ep.participants.map(function(p) {
return '<span class="participant-tag" data-participant="' + p + '">' + p + '</span>';
}).join('') +
'</div>';
html += '<div class="chronicle-item ' + statusClass + '">';
html += '<div class="chronicle-left">';
html += '<div class="chronicle-date">' + ep.date + '</div>';
html += '<div class="chronicle-title"><a href="' + ep.link + '" target="_blank">' + ep.title + '</a></div>';
html += participantsHtml;
html += '</div>';
html += '<div class="chronicle-right">';
html += '<div class="chronicle-location">' + ep.location + '</div>';
html += '<div class="chronicle-desc">' + (ep.description || '') + '</div>';
html += '</div>';
html += '</div>';
});
});
html += '</div>';
container.innerHTML = html;
countSpan.textContent = filteredEpisodes.length;
var tags = container.querySelectorAll('.participant-tag');
for (var j = 0; j < tags.length; j++) {
tags[j].addEventListener('click', function() {
var participant = this.getAttribute('data-participant');
participantFilter.value = participant;
applyFilters();
});
}
}
yearFilter.addEventListener('change', applyFilters);
participantFilter.addEventListener('change', applyFilters);
statusFilter.addEventListener('change', applyFilters);
resetBtn.addEventListener('click', function() {
yearFilter.value = 'all';
participantFilter.value = 'all';
statusFilter.value = 'all';
applyFilters();
});
applyFilters();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
setTimeout(init, 200);
})();
</script>



