[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="eventsBtn" class="chronicle-btn" style="border-color: var(--accent);">С событиями</button>
<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://forumstatic.ru/files/001c/5a/1f/57520.jpg');
background-size: cover;
background-position: center;
width: 100%;
max-height: 1024px;
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: flex-end;
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 #756a5e;
border-radius: 20px;
color: #aba8a6;
padding: 8px 20px;
cursor: pointer;
font-weight: bold;
text-transform: uppercase;
transition: transform 0.2s, box-shadow 0.2s;
margin-left: auto;
font-size: 10px;
font-family: 'Montserrat';
}
.chronicle-btn:hover {
transform: translateY(-3px) scale(1.1);
}
#eventsBtn.active {
border-color: var(--accent);
color: #aba8a6;
background: rgba(18, 16, 14, .85);
}
.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: 0px 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 {
text-align: left;
font-weight: bold;
margin-bottom: 3px;
font-size: 13px;
text-transform: uppercase;
}
.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: inline;
}
.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;
margin-top: 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);
}
/* Стили для событий */
.chronicle-event {
display: flex;
flex-wrap: wrap;
margin-top: 3px;
padding: 8px 12px;
border: 0px solid #393028;
border-radius: 8px;
background: rgba(18, 16, 14, 0.4);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.chronicle-event .chronicle-date {
flex: 0 0 120px;
font-weight: bold;
font-size: 12px;
color: var(--accent);
}
.chronicle-event .chronicle-event-desc {
flex: 1;
font-size: 12px;
color: #aba8a6;
line-height: 1.5;
}
@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;
}
.chronicle-event .chronicle-date {
flex: 0 0 80px;
}
}
</style>
<script>
(function() {
function init() {
var container = document.getElementById('chronicleContainer');
if (!container) return setTimeout(init, 50);
// ========== БАЗА ЭПИЗОДОВ И СОБЫТИЙ ==========
var rawData = [
// ========== ЭПИЗОДЫ ==========
{
type: "episode",
date: "27.06.10199",
title: "Рыба ещё эта",
participants: ["Ariste Atreides", "Feyd-Rautha Harkonnen"],
status: "завершённый",
link: "https://dune.rusff.me/viewtopic.php?id=43#p121",
year: "10199", location: "Арракис, Арракин",
description: "Фейд-Раута узнаёт, что после разорванной помолвки Аристе Атрейдес не улетала на Валлах, как ему сказали, а осталась на Арракисе и всё ещё находится там. На-барон решает объясниться с суженой лично."
},
{
type: "episode",
date: "01.03.10191",
title: "Revenge Is a Dish Best Served Poisoned",
participants: ["Feyd-Rautha Harkonnen", "Piter de Vries"],
status: "активный",
link: "https://dune.rusff.me/viewtopic.php?id=51#p339",
year: "10191",
location: "Гиеди Прайм",
description: "Питер де Врис завершает незаконченные дела. Фейд-Раута получает урок семейных ценностей."
},
{
type: "episode",
date: "30.06.10198",
title: "How to Charm a Princess",
participants: ["Feyd-Rautha Harkonnen", "Ariste Atreides", "Piter de Vries"],
status: "активный",
link: "https://dune.rusff.me/viewtopic.php?id=52#p346",
year: "10198",
location: "Арракис",
description: "Званый ужин в честь помолвки Аристе Атрейдес и Фейда-Рауты Харконнена"
},
{
type: "episode",
date: "27.06.10199",
title: "You did what now?",
participants: ["Feyd-Rautha Harkonnen", "Piter de Vries"],
status: "активный",
link: "https://dune.rusff.me/viewtopic.php?id=57#p625",
year: "10199",
location: "Арракис",
description: "Питер и Фейд обсуждают вылазку Фейда в Арракин."
},
{
type: "episode",
date: "18.07.10199",
title: "A Whole New World",
participants: ["Feyd-Rautha Harkonnen", "Ariste Atreides"],
status: "активный",
link: "https://dune.rusff.me/viewtopic.php?id=56#p585",
year: "10199",
location: "Арракис",
description: "Пытаясь выяснить настоящие причины смерти Бронсо Верниуса, Аристе связывается с Фейдом, чтобы запросить помощь"
},
{
type: "episode",
date: "13.08.10199",
title: "Я не договорил",
participants: ["Ariste Atreides", "Marcus Havok"],
status: "активный",
link: "https://dune.rusff.me/viewtopic.php?id=68#p1852",
year: "10199",
location: "Арракис",
description: "Марк и Аристе наконец-то получают возможность объясниться после инцидента в Архивах и истинных причин смерти Зантары"
},
// ========== СОБЫТИЯ ==========
{
type: "event",
date: "ХХ.04.10191",
description: "<b>Битва за Арракин</b>: благодаря вмешательству леди Джессики, личность предателя удаётся раскрыть. Атрейдесы отражают атаку Харконненов и сардаукаров на Арракин.",
year: "10191"
},
{
type: "event",
date: "ХХ.05.10191",
description: "Решением Ландсраада на Арракисе объявляется <b>Война Ассасинов</b> между домами Харконненов и Атрейдесов.",
year: "10191"
},
{
type: "event",
date: "ХХ.11.10194",
description: "<b>Уничтожение Карфага</b> — столицы и основной базы операций Харконненов на Арракисе. Точные причины катастрофы неизвестны, объявляется расследование.",
year: "10194"
},
{
type: "event",
date: "ХХ.11.10194",
description: "Харконнены спешно строят космопорт близ деревни Харко и, решив не останавливаться на этом, объявляют строительство <b>Нео-Карфага</b>.",
year: "10194"
},
{
type: "event",
date: "ХХ.02.10195",
description: "Завершение расследования катастрофы, произошедшей в Карфаге. Официальной версией признаётся трагическая случайность: контакт щита Хольцмана с лазером.",
year: "10195"
},
{
type: "event",
date: "ХХ.08.10195",
description: "Сардаукарский погром, по официальной версии завершившийся полным истреблением фременов. На самом деле фремены мигрируют на юг планеты.",
year: "10195"
},
{
type: "event",
date: "ХХ.06.10198",
description: "Попытка завершить конфликт дипломатическим путём. При посредничестве Бене Гессерит, объявляется помолвка наследников домов Атрейдесов и Харконненов. Подписывается соглашение о временном перемирии.",
year: "10198"
}
];
var allItems = rawData;
var filteredItems = [];
var showEvents = false; // по умолчанию события скрыты
var yearFilter = document.getElementById('yearFilter');
var participantFilter = document.getElementById('participantFilter');
var statusFilter = document.getElementById('statusFilter');
var resetBtn = document.getElementById('resetFilters');
var eventsBtn = document.getElementById('eventsBtn');
var countSpan = document.getElementById('episodeCount');
// Уникальные годы и участники (из эпизодов)
var episodesYearsSet = new Set();
var allYearsSet = new Set();
var participantsSet = new Set();
allItems.forEach(function(item) {
allYearsSet.add(item.year);
if (item.type === 'episode') {
episodesYearsSet.add(item.year);
}
if (item.participants) {
item.participants.forEach(function(p) { participantsSet.add(p); });
}
});
// Функция обновления опций годов в зависимости от режима
function updateYearOptions() {
var currentValue = yearFilter.value;
while (yearFilter.options.length > 1) yearFilter.remove(1);
var yearsArray = showEvents ? Array.from(allYearsSet) : Array.from(episodesYearsSet);
yearsArray.sort(function(a, b) { return b - a; }).forEach(function(year) {
var option = document.createElement('option');
option.value = year;
option.textContent = year;
yearFilter.appendChild(option);
});
// Восстанавливаем выбранное значение, если оно есть
if (currentValue) yearFilter.value = currentValue;
}
// Первичное заполнение годов (только эпизоды)
updateYearOptions();
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);
if (isNaN(day)) day = 0;
var month = parseInt(parts[1], 10);
if (isNaN(month)) month = 0;
var year = parseInt(parts[2], 10);
if (isNaN(year)) year = 0;
return year * 10000 + month * 100 + day;
}
function updateEventsButton() {
if (showEvents) {
eventsBtn.classList.add('active');
eventsBtn.textContent = 'Скрыть';
} else {
eventsBtn.classList.remove('active');
eventsBtn.textContent = 'С событиями';
}
}
function applyFilters() {
var selectedYear = yearFilter.value;
var selectedParticipant = participantFilter.value;
var selectedStatus = statusFilter.value;
filteredItems = allItems.filter(function(item) {
if (selectedYear !== 'all' && item.year !== selectedYear) return false;
if (selectedParticipant !== 'all') {
if (!item.participants || item.participants.indexOf(selectedParticipant) === -1) return false;
}
if (showEvents) {
if (selectedStatus !== 'all') {
if (item.type === 'episode') {
return item.status === selectedStatus;
}
return item.type === 'event';
}
return true;
}
if (selectedStatus !== 'all') {
if (item.type !== 'episode' || item.status !== selectedStatus) return false;
} else {
if (item.type !== 'episode') return false;
}
return true;
});
filteredItems.sort(function(a, b) {
var dateA = parseDate(a.date);
var dateB = parseDate(b.date);
if (dateA !== dateB) return dateA - dateB;
if (a.type === 'event' && b.type !== 'event') return -1;
if (a.type !== 'event' && b.type === 'event') return 1;
return 0;
});
renderTimeline();
}
function renderTimeline() {
if (filteredItems.length === 0) {
container.innerHTML = '<div class="chronicle-loading">Нет записей, соответствующих фильтрам.</div>';
countSpan.textContent = '0';
return;
}
var grouped = {};
filteredItems.forEach(function(item) {
if (!grouped[item.year]) grouped[item.year] = [];
grouped[item.year].push(item);
});
var years = Object.keys(grouped).sort(function(a, b) { return b - a; });
var html = '<div class="chronicle-timeline">';
var totalEpisodes = 0;
years.forEach(function(year) {
html += '<div class="chronicle-year-divider">' + year + ' AG</div>';
grouped[year].forEach(function(item) {
if (item.type === 'event') {
html += '<div class="chronicle-event">';
html += '<div class="chronicle-date">' + item.date + '</div>';
html += '<div class="chronicle-event-desc">' + (item.description || '') + '</div>';
html += '</div>';
} else {
totalEpisodes++;
var statusClass = '';
if (item.status === 'активный') statusClass = 'status-active';
else if (item.status === 'завершённый') statusClass = 'status-closed';
else if (item.status === 'незаконченный') statusClass = 'status-unfinished';
var participantsHtml = '<div class="chronicle-participants">' +
item.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">' + item.date + '</div>';
html += '<div class="chronicle-title"><a href="' + item.link + '" target="_blank">' + item.title + '</a></div>';
html += participantsHtml;
html += '</div>';
html += '<div class="chronicle-right">';
html += '<div class="chronicle-location">' + item.location + '</div>';
html += '<div class="chronicle-desc">' + (item.description || '') + '</div>';
html += '</div>';
html += '</div>';
}
});
});
html += '</div>';
container.innerHTML = html;
countSpan.textContent = totalEpisodes;
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';
showEvents = false;
updateYearOptions();
updateEventsButton();
applyFilters();
});
eventsBtn.addEventListener('click', function() {
showEvents = !showEvents;
updateYearOptions();
updateEventsButton();
applyFilters();
});
updateEventsButton();
applyFilters();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
setTimeout(init, 200);
})();
</script>













