IMPERIVM

антуражное фэнтези ◆ 18+
готовность к открытию

BDH tester

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » BDH tester » КОДЫ » Тестовое сообщение


Тестовое сообщение

Сообщений 1 страница 9 из 9

1

Благодарим за выбор нашего сервиса!

0

2

This is a quote

Trigger for collapsible section

Content of collapsible section

0

3

[html]
<article class="lore-article chronicle-page" id="chronicle-app">

  <span class="lore-badge">Общая хронология</span>
  <h1>Хроники Материка</h1>
  <p class="lore-tagline">От исхода пришлых до нынешнего безмолвного противостояния</p>

  <p>Единого летоисчисления материк не знал до основания престола — годы до него провинции считали каждая по-своему, и здесь они даны условно, «от основания» (И.Э. — Имперская эра). Эта хроника сводит две вещи, которые обычно рассказывают порознь: то, что происходило со всем материком разом, и то, что происходило в землях, которые сегодня зовутся Латериумом, Альсаром и Тянься.</p>
  <p>Выберите эпоху ниже — каждая карточка открывает свою хронологию. Фильтр под эпохами общий для всех них: он остаётся в силе, даже если вы переключаетесь между эпохами.</p>

  <div class="chronicle-filter">
    <span class="chronicle-filter-label">Показать:</span>
    <div class="chronicle-filter-chips" id="chronicle-filter-chips" role="group" aria-label="Фильтр по стране, Империи в целом или общим событиям"></div>
  </div>

  <!-- инлайновый скрипт ниже вставляет сюда, внутрь #chronicle-app: N
       радио.chronicle-era-input, nav.lore-tabs.chronicle-eras и
       div.lore-panels.chronicle-panels — построено из JSON, а не руками,
       чтобы контент и разметка не расходились -->
</article>

<script>
  // Данные хроники — отдельный JSON-файл (articles/timeline-data.json,
  // залитый на forumstatic.ru тем же способом, что и другие внешние ассеты
  // форума), а не разметка в этом файле — так фильтр, карточки эпох и сам
  // таймлайн строятся из одного источника и правятся в одном месте.
  const CHRONICLE_DATA_URL = 'https://forumstatic.ru/files/001c/c7/56/26482.json';
  let chronicleData = null;

  const scopeLabel = (scopeId) =>
    chronicleData.scopes.find((scope) => scope.id === scopeId)?.label ?? scopeId;

  const renderChip = ({ id, label }) => `
    <input class="chronicle-filter-input" type="checkbox" id="filter-${id}" data-scope="${id}">
    <label class="chronicle-chip" data-scope="${id}" for="filter-${id}">${label}</label>
  `;

  // мета-строка теперь год+заголовок (год несёт акцентный цвет вместе с
  // меткой на спине), а страна/Империя — отдельная безрамочная подпись
  // ниже, а не значок-пилюля — меньше повторов одного акцента на карточку
  const renderEvent = ({ year, scope, title, text }) => `
    <li class="chronicle-item" data-scope="${scope}">
      <div class="chronicle-item-meta">
        <span class="chronicle-year">${year}</span>
        <h4>${title}</h4>
      </div>
      <span class="chronicle-scope-tag">${scopeLabel(scope)}</span>
      <p>${text}</p>
    </li>
  `;

  const renderEraInput = ({ id }, index) => `
    <input class="lore-tab-input chronicle-era-input" type="radio" name="chronicle-era" id="era-${id}" ${index === 0 ? 'checked' : ''}>
  `;

  const renderEraCard = ({ id, name, range, oneLiner }) => `
    <label class="lore-tab chronicle-era-card" for="era-${id}">
      <span class="chronicle-era-name">${name}</span>
      <span class="chronicle-era-range">${range}</span>
      <span class="chronicle-era-oneliner">${oneLiner}</span>
    </label>
  `;

  const renderEraPanel = ({ id, range, intro, events }) => `
    <section class="lore-panel chronicle-detail" data-panel="${id}">
      <p class="chronicle-era-range">${range}</p>
      <p>${intro}</p>
      <ul class="chronicle-timeline">${events.map(renderEvent).join('')}</ul>
      <p class="chronicle-empty" hidden>Ни одно событие этой эпохи не подходит под выбранный фильтр.</p>
    </section>
  `;

  const renderEras = () => {
    const { periods } = chronicleData;
    const app = document.getElementById('chronicle-app');
    app.insertAdjacentHTML('beforeend', `
      ${periods.map(renderEraInput).join('')}
      <nav class="lore-tabs chronicle-eras" aria-label="Эпохи материка">${periods.map(renderEraCard).join('')}</nav>
      <div class="lore-panels chronicle-panels">${periods.map(renderEraPanel).join('')}</div>
    `);
  };

  const renderFilterChips = () => {
    const chips = document.getElementById('chronicle-filter-chips');
    chips.insertAdjacentHTML('beforeend', `
      <input class="chronicle-filter-input" type="checkbox" id="filter-all" checked>
      <label class="chronicle-chip" for="filter-all">Всё</label>
      ${chronicleData.scopes.map(renderChip).join('')}
    `);
  };

  const scopeCheckboxes = () => Array.from(document.querySelectorAll('.chronicle-filter-input[data-scope]'));

  // пустой список = фильтр не сужает ничего, показываем все события
  const activeScopes = () => {
    const checked = scopeCheckboxes().filter((input) => input.checked).map((input) => input.dataset.scope);
    return checked.length ? checked : null;
  };

  const applyFilter = () => {
    const active = activeScopes();
    document.querySelectorAll('.chronicle-detail').forEach((panel) => {
      let visibleCount = 0;
      panel.querySelectorAll('.chronicle-item').forEach((item) => {
        const show = !active || active.includes(item.dataset.scope);
        item.hidden = !show;
        if (show) visibleCount += 1;
      });
      panel.querySelector('.chronicle-empty').hidden = visibleCount > 0;
    });
  };

  const onFilterChange = (event) => {
    const target = event.target;
    if (!target.matches('.chronicle-filter-input')) return;

    const allCheckbox = document.getElementById('filter-all');
    if (target.id === 'filter-all') {
      if (target.checked) scopeCheckboxes().forEach((input) => { input.checked = false; });
    } else {
      if (target.checked) allCheckbox.checked = false;
      if (!scopeCheckboxes().some((input) => input.checked)) allCheckbox.checked = true;
    }
    applyFilter();
  };

  const initChronicle = () => {
    renderEras();
    renderFilterChips();
    document.getElementById('chronicle-filter-chips').addEventListener('change', onFilterChange);
  };

  fetch(CHRONICLE_DATA_URL)
    .then((response) => response.json())
    .then((data) => {
      chronicleData = data;
      initChronicle();
    });
</script>
[/html][hideprofile]

0

4

[html]
<article class="lore-article roster-page" id="roster-app">

  <span class="lore-badge">Двор и знать Империи</span>
  <h1>Кто есть кто в Империи</h1>
  <p class="lore-tagline">Наместники, их дворы и великие дома — по территориям и землям внутри них</p>

  <p class="roster-intro">Список организован по территориям Империи (крупным наделам под властью Наместника или, как в случае Латериума, центральных сановников). Разверните территорию, чтобы увидеть её двор, затем выберите землю внутри неё — там перечислены дома, держащие эту землю, и их люди.</p>

  <div id="roster-territories"><!-- сюда скрипт вставляет .roster-territory по одному на territories[] --></div>

</article>

<script>
  // Данные ростера — отдельный JSON (см. комментарий вверху файла про заливку на форум)
  const CHARACTERS_DATA_URL = 'https://forumstatic.ru/files/001c/c7/56/54183.json';

  // русское склонение количества — форум читает по-русски, дефолтный "N штук" не подходит
  const pluralize = (n, one, few, many) => {
    const mod10 = n % 10;
    const mod100 = n % 100;
    if (mod10 === 1 && mod100 !== 11) return one;
    if ([2, 3, 4].includes(mod10) && ![12, 13, 14].includes(mod100)) return few;
    return many;
  };

  const countTerritory = (territory) => {
    const landFamilies = territory.subterritories.flatMap((sub) => [
      ...(sub.families || []),
      ...(sub.districts || []).flatMap((district) => district.families),
    ]);
    const families = territory.rulingFamily ? [territory.rulingFamily, ...landFamilies] : landFamilies;
    const members = families.flatMap((family) => family.members);
    return {
      lands: territory.subterritories.length,
      families: families.length,
      members: members.length,
    };
  };

  // имя персонажа — просто текст, если у него нет ссылки (профиль на форуме
  // есть не у всех), или ссылка на неё, если есть; и то и другое остаётся
  // жирным через общий .strong снаружи, а не задаётся заново на <a>
  const renderName = ({ name, url }) => (url ? `<a href="${url}">${name}</a>` : name);

  // одна строка — одно имя: жирное имя, тире, роль (+ note отдельным
  // предложением, если есть) — единственный способ показать персонажа на
  // всей странице, будь то двор, местный правитель или член дома
  const renderRow = (person) => `
    <p class="roster-row">
      <strong>${renderName(person)}</strong>${person.role ? ` — ${person.role}${person.note ? `. ${person.note}` : ''}` : ''}
    </p>
  `;

  // одна и та же секция карточки для двух разных вещей — правящего дома (если
  // он у территории есть отдельно от Наместника, как в Альсаре) и двора —
  // так на странице только один визуальный приём для "ещё одной группы людей
  // внутри карточки", а не два похожих
  const renderHeroSection = (label, rows, note) => `
    <div class="roster-hero-section">
      <p class="roster-eyebrow roster-hero-section-label">${label}</p>
      ${note ? `<p class="roster-note">${note}</p>` : ''}
      <div class="roster-hero-rows">${rows.map(renderRow).join('')}</div>
    </div>
  `;

  // одна "голова" карточки — эмблема, эйброу-титул, опционально имя и
  // заметка, опционально список персонажей: и Наместник, и правящий дом
  // рендерятся через одно и то же, чтобы стоять рядом как равные, а не как
  // главная фигура и подпись под ней
  const renderHeroHead = ({ icon, eyebrow, name, nameUrl, note, rows }) => `
    <div class="roster-hero-head">
      <p class="roster-eyebrow roster-hero-title">${eyebrow}</p>
      ${name ? `<p class="roster-hero-lede"><strong>${renderName({ name, url: nameUrl })}</strong></p>` : ''}
      ${note ? `<p class="roster-note">${note}</p>` : ''}
      ${rows ? `<div class="roster-hero-rows roster-hero-rows--stack">${rows.map(renderRow).join('')}</div>` : ''}
    </div>
  `;

  // без Наместника (как в Латериуме) голова остаётся без имени — просто
  // титул и note, объясняющий отсутствие, вместо имени-плейсхолдера
  const renderHero = ({ capitalMage, note, staff, rulingFamily }) => {
    const overseerHead = renderHeroHead({
      icon: 'fa-crown',
      eyebrow: capitalMage ? capitalMage.title : 'Наместник',
      name: capitalMage ? capitalMage.name : null,
      nameUrl: capitalMage ? capitalMage.url : null,
      note,
    });
    const rulingFamilyHead = rulingFamily ? renderHeroHead({
      icon: 'fa-shield-halved',
      eyebrow: rulingFamily.name,
      note: rulingFamily.note,
      rows: rulingFamily.members,
    }) : '';
    return `
      <div class="roster-hero">
        <div class="roster-hero-heads${rulingFamily ? ' roster-hero-heads--split' : ''}">
          ${overseerHead}${rulingFamilyHead}
        </div>
        ${staff.length ? renderHeroSection('Двор', staff) : ''}
      </div>
    `;
  };

  const renderHouse = ({ name, members }) => `
    <div class="roster-house">
      <p class="roster-house-label">${name}</p>
      ${members.map(renderRow).join('')}
    </div>
  `;

  // деление земли на более мелкие территории (уезды, кварталы и т.п.) —
  // не у каждой земли оно есть, families прямо на самой земле остаются
  // способом для домов, не привязанных ни к одному из них
  const renderDistrict = ({ name, note, families }) => `
    <div class="roster-district">
      <p class="roster-district-header">${name}</p>
      ${note ? `<p class="roster-note">${note}</p>` : ''}
      ${families.map(renderHouse).join('')}
    </div>
  `;

  const renderLandCard = ({ name, note, localRuler, districts = [], families = [] }) => `
    <div class="roster-land-card">
      <p class="roster-land-card-header">${name}</p>
      ${note ? `<p class="roster-note">${note}</p>` : ''}
      ${localRuler ? `
        <div class="roster-land-ruler">
          <p class="roster-eyebrow">${localRuler.title}</p>
          <p class="roster-row"><strong>${renderName(localRuler)}</strong></p>
        </div>
      ` : ''}
      ${districts.map(renderDistrict).join('')}
      ${families.map(renderHouse).join('')}
    </div>
  `;

  const renderTerritory = (territory, index) => {
    const counts = countTerritory(territory);
    return `
      <details class="roster-territory" ${index === 0 ? 'open' : ''}>
        <summary class="roster-territory-summary">
          <div class="roster-territory-heading">
            <h2 class="roster-territory-name">${territory.name}</h2>
            <span class="roster-territory-count">${counts.lands} ${pluralize(counts.lands, 'земля', 'земли', 'земель')} · ${counts.families} ${pluralize(counts.families, 'дом', 'дома', 'домов')} · ${counts.members} ${pluralize(counts.members, 'персонаж', 'персонажа', 'персонажей')}</span>
          </div>
          <span class="roster-territory-toggle"></span>
        </summary>
        <div class="roster-territory-body">
          ${renderHero(territory)}
          <div class="roster-lands">${territory.subterritories.map(renderLandCard).join('')}</div>
        </div>
      </details>
    `;
  };

  fetch(CHARACTERS_DATA_URL)
    .then((response) => response.json())
    .then((data) => {
      document.getElementById('roster-territories').innerHTML = data.territories.map(renderTerritory).join('');
    });
</script>
[/html][hideprofile]

0

5

BigDamnHero написал(а):

0

6

BigDamnHero написал(а):

BigDamnHero написал(а):

- шефчшуиечфиушечф
-wefjwefewf

0

7

[dice=1d20:Просто так!]

0

8

[dice=10d20+1:Прост]

0

9

[dice=1d20:Хммммм]

0


Вы здесь » BDH tester » КОДЫ » Тестовое сообщение


Рейтинг форумов | Создать форум бесплатно