«— Можете еще раз повторить? — Эловен говорит свой вопрос в трубку спустя тридцать секунд молчания, отрывая вопросительный взгляд от стены напротив. Там висела потрясающая фотография залива Сан-Франциско и Эловен была бы рада смотреть на нее и дальше, но пока что она пыталась уложить в голове просьбу незнакомой девушки».

мистика — расы — вуду — nc-18
новый орлеан — зима 2025

Unholy Mess

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

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


Вы здесь » Unholy Mess » Veritas occultatur in tenebris » Календарь сюжетных событий


Календарь сюжетных событий

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

1

hide-autor2
[html]<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Календарь</title>
  <style>
    :root{
      --bg: #0b1020;
      --text: #000000;
      --ring: 0 0 0 2px rgba(16,26,51,.45), 0 0 30px rgba(16,26,51,.15); /* #101a33 glow */
      --radius: 16px;
    }

    html,body{
      height:100%;
      margin:0;
      font: 16px/1.5 'El Messiri' !important;
      color: var(--text);
      background: radial-gradient(1200px 800px at 10% 0%, #101a33 0%, var(--bg) 60%);
      display:flex; align-items:center; justify-content:center; padding:24px;
    }

    .wrap{width:min(1100px, 95vw);}
    h1{font-size:clamp(22px, 3vw, 28px); margin:0 0 14px; font-weight:700; letter-spacing:.2px;}
    .sub{color:#000000; margin:0 0 18px;}

    .calendar{
      position: relative;
      background: transparent; /* фон календаря прозрачный */
      border: 1px solid rgba(0,0,0,.06);
      border-radius: var(--radius);
      box-shadow: 0 10px 40px rgba(0,0,0,.08), inset 0 0 0 1px rgba(0,0,0,.04);
      overflow: hidden;
    }

    .months{ display:flex; gap:2px; }
    .month{
      flex:1; min-width: 460px;
      background: transparent; /* прозрачный фон месяца */
      border-radius: calc(var(--radius) - 2px);
      padding: 12px 12px 16px;
      border: 1px solid rgba(0,0,0,.06);
    }
    .month h2{     margin: 4px 6px 10px;
    font-family: 'loreley antiqua' !important;
    font-size: 26px;
    text-shadow: 0 0 2px #e9cfaa; }

    table{ width:100%; border-collapse: collapse; table-layout: fixed;}
    thead th{
      font-size:12px; color: #000000; font-weight:600; text-transform: uppercase; letter-spacing:.08em; padding:8px 4px 10px; border-bottom:1px solid rgba(0,0,0,.06);
    }
    td{
      vertical-align: top; height: 92px; padding:8px;
      border: 1px solid rgba(0,0,0,.06); /* как у .month */
      position: relative; background: rgba(187,185,185,.50); /* a49a98 @ 50% */
    }
    td.is-out{ opacity: .35; }

    .day-number{ font-size: 12px; color:#000000; font-weight:600; }

    .event{
      margin-top:6px; display:inline-flex; align-items:center; gap:6px; padding:6px 8px; border-radius:10px;
      background: rgba(111,108,109,.12);
      border: 1px dashed rgba(111,108,109,.40);
      color:#000000; cursor: pointer;
      user-select:none; outline: none; white-space: nowrap; max-width: 100%; overflow:hidden; text-overflow: ellipsis;
      transition: box-shadow .2s ease, transform .2s ease;
    }
    .event:hover{ box-shadow: var(--ring); }
    .event:focus-visible{ outline: none; box-shadow: var(--ring); }
    .event .dot{ width:8px; height:8px; border-radius:50%; background: #515966; flex:0 0 8px; }

    /* === Панель подробностей по принципу «мироописание от духа» === */
    .info-panel{
      position:absolute; z-index:5;
      /* Центр по ширине без translate — строгие поля дают 80% ширины */
      left:10%; right:10%; width:auto; /* 80% ширины контейнера */
      /* Высота не более 50% контейнера */
      max-height:50%; top:25px;

      background:#9A9697; color:#111;
      box-shadow: 0 0 0 5px #9A9697 inset, 0 0 0 10px rgba(0,0,0,.1) inset;
      border-radius: 14px; padding:25px; text-align:left;
      overflow:hidden;
      /* Анимация появления/сворачивания */
      opacity:0; transform: translateY(-8px) scale(.98); transition: opacity .22s ease, transform .24s ease;
      visibility: hidden; pointer-events:none;
    }
    .info-panel.is-open{ opacity:1; transform: translateY(0) scale(1); visibility: visible; pointer-events:auto; }
    .info-panel > .info-panel__content{ overflow-y:auto; height: calc(100% - 35px); padding-right:5px; }
    .info-panel h5{ font: 700 20px 'El Messiri', sans-serif; margin:20px 0 10px; position:relative; padding-left:45px; }
    .info-panel h5:before{ content:""; height:2px; width:30px; background: rgba(0,0,0,.7); position:absolute; left:0; top:50%; transform: translateY(-50%); }
    .info-panel em{ font: 400 italic 11px 'El Messiri', sans-serif; display:block; border-right:5px solid rgba(0,0,0,.1); padding-right:10px; text-align:right; margin:10px 0; }
    .close-btn{ background: rgba(0,0,0,.04); border:1px solid rgba(0,0,0,.06); height:15px; text-transform:uppercase; font: 600 10px/15px 'El Messiri', sans-serif; text-align:center; margin-bottom:20px; cursor:pointer; letter-spacing:2px; }

    /* Адаптив */
    @media (max-width: 980px){
      .months{ flex-direction: column; }
      .month{ min-width: unset; }
      td{ height: 80px; }
      /* На мобилках делаем шире и чуть выше */
      .info-panel{ left:4%; right:4%; max-height:60%; top:12px; padding:18px; }
    }
  </style>
</head>
<center><body>
  <div class="wrap">
 
    <div class="calendar" id="calendar">
      <div class="months">
        <!-- Сентябрь 2025 -->
        <section class="month" aria-label="Сентябрь 2025">
          <h2>2025, Sept</h2>
          <table role="grid" aria-labelledby="m-sep">
            <thead>
              <tr><th>Пн</th><th>Вт</th><th>Ср</th><th>Чт</th><th>Пт</th><th>Сб</th><th>Вс</th></tr>
            </thead>
            <tbody>
              <tr>
                <td><span class="day-number">1</span>
      <button class="event" data-title="ООО КровьДляВсех" data-when="01 сентября, 12:00" data-where="последний дом на Бурбон-стрит" data-text="Как дети в школу, вампиры решили пойти подкрепиться. Но немного переборщили с масштабами. В подвале последнего дома на Бурбон-стрит начала работу фабрика по извлечению крови из живых (и очень сопротивляющихся) людей." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> ООО КровьДляВсех
                  </button></td>
                <td><span class="day-number">2</span></td>
                <td><span class="day-number">3</span></td>
                <td><span class="day-number">4</span></td>
                <td><span class="day-number">5</span></td>
                <td><span class="day-number">6</span></td>
                <td><span class="day-number">7</span></td>
              </tr>
              <tr>
                <td><span class="day-number">8</span></td>
                <td><span class="day-number">9</span></td>
                <td><span class="day-number">10</span>
      <button class="event" data-title="Censured" data-when="10 сентября, 21:00" data-where="район Ривер Ридж" data-text="Обнаружение старинного артефакта усиливает способности пары суккубов, что проводит к аномальной вспышке сексуального желания у окружающих." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Censured
                  </button></td>
                <td><span class="day-number">11</span></td>
                <td><span class="day-number">12</span></td>
                <td><span class="day-number">13</span></td>
                <td><span class="day-number">14</span></td>
              </tr>
              <tr>
                <td><span class="day-number">15</span></td>
                <td><span class="day-number">16</span>
      <button class="event" data-title="Съезд СМС" data-when="16 сентября, 10:00-18:00" data-where="отель Four Seasons, Канал-Стрит 2" data-text="Обмен опытом, наработками и общение стражей со всего мира происходит в Новом Орлеане. Несомненно у организации хватает недоброжелателей, чтобы испортить им мероприятие. Когда в отеле раздаётся первый взрыв, даже обычные люди начинают догадываться, что дело вовсе не в утечке газа." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Съезд СМС
                  </button></td>
                <td><span class="day-number">17</span></td>
                <td><span class="day-number">18</span></td>
                <td><span class="day-number">19</span></td>
                <td><span class="day-number">20</span>
                  <button class="event" data-title="Турнир по волейболу: вампиры против оборотней" data-when="20 сентября, 22:00–04:00" data-where="Сангрилла Секретный пляж" data-text="Командный турнир 6×6. Стоило Новому Орлеану освободиться от власти ковена Собо, магические представители города отказались от всего былого... кроме любимого события: шестёрка лучших оборотней встречается с шестью наиболее способными вампирами, чтобы в жестоком бою доказать, кто из них лучший - в волейболе. Настоящий песок у бассейна элитного комплекса и искусственное солнце, что не вредит упырям. Получить приглашение на это мероприятие - уже честь. Здесь лучшие угощения, а шоу в перерывах не уступает Half-Time Супер Боула. Здесь налаживают контакты и просто хорошо проводят время. Будет ли и в этот раз так радужно или кому-то турнир всё-таки не угодил?" aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Турнир по волейболу
                  </button>
                </td>
                <td><span class="day-number">21</span></td>
              </tr>
              <tr>
                <td><span class="day-number">22</span></td>
                <td><span class="day-number">23</span></td>
                <td><span class="day-number">24</span></td>
                <td><span class="day-number">25</span>
      <button class="event" data-title="Крик баньши" data-when="25 - 27 сентября" data-where="Новый Орлеан" data-text="Возросшее количество смертей и затяжная пасмурная погода повысили концентрацию баньши в городе. Покоя от их крика нет ни простым смертным, ни магическим существам." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Крик баньши
                  </button></td>
                <td><span class="day-number">26</span>
      <button class="event" data-title="Крик баньши" data-when="25 - 27 сентября" data-where="Новый Орлеан" data-text="Возросшее количество смертей и затяжная пасмурная погода повысили концентрацию баньши в городе. Покоя от их крика нет ни простым смертным, ни магическим существам." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">27</span>
      <button class="event" data-title="Крик баньши" data-when="25 - 27 сентября" data-where="Новый Орлеан" data-text="Возросшее количество смертей и затяжная пасмурная погода повысили концентрацию баньши в городе. Покоя от их крика нет ни простым смертным, ни магическим существам." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">28</span></td>
              </tr>
              <tr>
                <td><span class="day-number">29</span></td>
                <td><span class="day-number">30</span>
      <button class="event" data-title="Восстание мертвецов" data-when="30 сентября, 00:00–06:00" data-where="кладбище Сент-Луи" data-text="После снятия запрета на некроматию ведьмы города зачастили на кладбище Сент-Луи. Заигрывание с мистическими силами идёт не по плану, когда один за другим воскресают духи каждого, кто был захоронен здесь. И, если этого недостаточно, за духами выбираются их разной степени разложившиеся тела. Пока призраки завершают свои земные дела и проведывают родственников, на кладбище разворачивается через чур реалистичный эпизод сериала Walking Dead." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Восстание мертвецов
                  </button></td>
                <td class="is-out"></td>
                <td class="is-out"></td>
                <td class="is-out"></td>
                <td class="is-out"></td>
                <td class="is-out"></td>
              </tr>
            </tbody>
          </table>
        </section>
      </div>

      <!-- мироописание от духа -->
      <div class="info-panel" id="infoPanel" aria-live="polite" aria-atomic="true" role="dialog" aria-modal="false">
        <div class="close-btn" id="panelClose">— закрыть —</div>
        <div class="info-panel__content">
          <h5 id="panelTitle">Заголовок</h5>
          <em id="panelMeta">Когда и где</em>
          <div id="panelBody">Текст</div>
        </div>
      </div>
    </div>
  </div>

  <script>
    (function(){
      // === ЭЛЕМЕНТЫ ===
      const calendar = document.getElementById('calendar');
      const panel = document.getElementById('infoPanel');
      const panelClose = document.getElementById('panelClose');
      const pTitle = document.getElementById('panelTitle');
      const pMeta = document.getElementById('panelMeta');
      const pBody = document.getElementById('panelBody');

      let currentTrigger = null;

      // === ОТКРЫТИЕ ПАНЕЛИ ===
      function openFromTrigger(btn){
        currentTrigger = btn;
        pTitle.textContent = btn.dataset.title || btn.textContent.trim();
        const whenTxt = btn.dataset.when ? btn.dataset.when : '';
        const whereTxt = btn.dataset.where ? (whenTxt ? ' · ' : '') + btn.dataset.where : '';
        pMeta.textContent = (whenTxt + whereTxt).trim();
        pBody.textContent = btn.dataset.text || '';
        panel.classList.add('is-open');
        panel.setAttribute('aria-modal','true');
        panelClose.focus();
      }

      // === ЗАКРЫТИЕ ПАНЕЛИ ===
      function closePopover(){
        panel.classList.remove('is-open');
        panel.setAttribute('aria-modal','false');
        if (currentTrigger) currentTrigger.focus();
      }

      // === СЛУШАТЕЛИ ===
      calendar.addEventListener('click', (e)=>{
        const btn = e.target.closest('.event');
        if (!btn) return;
        openFromTrigger(btn);
      });

      panelClose.addEventListener('click', closePopover);
      document.addEventListener('keydown', (e)=>{ if (e.key === 'Escape') closePopover(); });

      // Быстрое копирование по двойному клику по заголовку
      pTitle.addEventListener('dblclick', async ()=>{
        const text = `${pTitle.textContent}\n${pMeta.textContent}\n\n${pBody.textContent}`.trim();
        try { await navigator.clipboard.writeText(text); } catch(e){}
      });

      // === Самопроверка/"тесты" в консоли ===
      (function selfTest(){
        const results = [];
        const reqIds = ['calendar','infoPanel','panelClose','panelTitle','panelMeta','panelBody'];
        const missing = reqIds.filter(id => !document.getElementById(id));
        results.push(['IDs exist', missing.length===0, missing.length ? ('missing: '+missing.join(', ')) : 'ok']);
        const eventsCount = document.querySelectorAll('.event').length;
        results.push(['Events present', eventsCount>0, 'count='+eventsCount]);
        console.groupCollapsed('%cCalendar self-test','background:#0f1630;color:#cfe0ff;padding:2px 6px;border-radius:6px');
        results.forEach(([name, ok, msg])=> console[ok?'log':'warn'](`${name}: ${ok?'OK':'FAIL'} (${msg})`));
        console.groupEnd();
        if (location.hash === '#test' && eventsCount){
          const first = document.querySelector('.event');
          openFromTrigger(first);
          setTimeout(closePopover, 1200);
        }
      })();
    })();
  </script>
</body>
</html>
[/html]
[html]<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Календарь</title>
  <style>
    :root{
      --bg: #0b1020;
      --text: #000000;
      --ring: 0 0 0 2px rgba(16,26,51,.45), 0 0 30px rgba(16,26,51,.15); /* #101a33 glow */
      --radius: 16px;
    }

    html,body{
      height:100%;
      margin:0;
      font: 16px/1.5 'El Messiri' !important;
      color: var(--text);
      background: radial-gradient(1200px 800px at 10% 0%, #101a33 0%, var(--bg) 60%);
      display:flex; align-items:center; justify-content:center; padding:24px;
    }

    .wrap{width:min(1100px, 95vw);}
    h1{font-size:clamp(22px, 3vw, 28px); margin:0 0 14px; font-weight:700; letter-spacing:.2px;}
    .sub{color:#000000; margin:0 0 18px;}

    .calendar{
      position: relative;
      background: transparent; /* фон календаря прозрачный */
      border: 1px solid rgba(0,0,0,.06);
      border-radius: var(--radius);
      box-shadow: 0 10px 40px rgba(0,0,0,.08), inset 0 0 0 1px rgba(0,0,0,.04);
      overflow: hidden;
    }

    .months{ display:flex; gap:2px; }
    .month{
      flex:1; min-width: 460px;
      background: transparent; /* прозрачный фон месяца */
      border-radius: calc(var(--radius) - 2px);
      padding: 12px 12px 16px;
      border: 1px solid rgba(0,0,0,.06);
    }
    .month h2{     margin: 4px 6px 10px;
    font-family: 'loreley antiqua' !important;
    font-size: 26px;
    text-shadow: 0 0 2px #e9cfaa; }

    table{ width:100%; border-collapse: collapse; table-layout: fixed;}
    thead th{
      font-size:12px; color: #000000; font-weight:600; text-transform: uppercase; letter-spacing:.08em; padding:8px 4px 10px; border-bottom:1px solid rgba(0,0,0,.06);
    }
    td{
      vertical-align: top; height: 92px; padding:8px;
      border: 1px solid rgba(0,0,0,.06); /* как у .month */
      position: relative; background: rgba(187,185,185,.50); /* a49a98 @ 50% */
    }
    td.is-out{ opacity: .35; }

    .day-number{ font-size: 12px; color:#000000; font-weight:600; }

    .event{
      margin-top:6px; display:inline-flex; align-items:center; gap:6px; padding:6px 8px; border-radius:10px;
      background: rgba(111,108,109,.12);
      border: 1px dashed rgba(111,108,109,.40);
      color:#000000; cursor: pointer;
      user-select:none; outline: none; white-space: nowrap; max-width: 100%; overflow:hidden; text-overflow: ellipsis;
      transition: box-shadow .2s ease, transform .2s ease;
    }
    .event:hover{ box-shadow: var(--ring); }
    .event:focus-visible{ outline: none; box-shadow: var(--ring); }
    .event .dot{ width:8px; height:8px; border-radius:50%; background: #515966; flex:0 0 8px; }

    /* === Панель подробностей по принципу «мироописание от духа» === */
    .info-panel{
      position:absolute; z-index:5;
      /* Центр по ширине без translate — строгие поля дают 80% ширины */
      left:10%; right:10%; width:auto; /* 80% ширины контейнера */
      /* Высота не более 50% контейнера */
      max-height:50%; top:25px;

      background:#9A9697; color:#111;
      box-shadow: 0 0 0 5px #9A9697 inset, 0 0 0 10px rgba(0,0,0,.1) inset;
      border-radius: 14px; padding:25px; text-align:left;
      overflow:hidden;
      /* Анимация появления/сворачивания */
      opacity:0; transform: translateY(-8px) scale(.98); transition: opacity .22s ease, transform .24s ease;
      visibility: hidden; pointer-events:none;
    }
    .info-panel.is-open{ opacity:1; transform: translateY(0) scale(1); visibility: visible; pointer-events:auto; }
    .info-panel > .info-panel__content{ overflow-y:auto; height: calc(100% - 35px); padding-right:5px; }
    .info-panel h5{ font: 700 20px 'El Messiri', sans-serif; margin:20px 0 10px; position:relative; padding-left:45px; }
    .info-panel h5:before{ content:""; height:2px; width:30px; background: rgba(0,0,0,.7); position:absolute; left:0; top:50%; transform: translateY(-50%); }
    .info-panel em{ font: 400 italic 11px 'El Messiri', sans-serif; display:block; border-right:5px solid rgba(0,0,0,.1); padding-right:10px; text-align:right; margin:10px 0; }
    .close-btn{ background: rgba(0,0,0,.04); border:1px solid rgba(0,0,0,.06); height:15px; text-transform:uppercase; font: 600 10px/15px 'El Messiri', sans-serif; text-align:center; margin-bottom:20px; cursor:pointer; letter-spacing:2px; }

    /* Адаптив */
    @media (max-width: 980px){
      .months{ flex-direction: column; }
      .month{ min-width: unset; }
      td{ height: 80px; }
      /* На мобилках делаем шире и чуть выше */
      .info-panel{ left:4%; right:4%; max-height:60%; top:12px; padding:18px; }
    }
  </style>
</head>
<center><body>
  <div class="wrap">
 
    <div class="calendar" id="calendar">
      <div class="months">
              <!-- Октябрь 2025 -->
        <section class="month" aria-label="Октябрь 2025">
          <h2>2025, Oct</h2>
          <table role="grid">
            <thead>
              <tr><th>Пн</th><th>Вт</th><th>Ср</th><th>Чт</th><th>Пт</th><th>Сб</th><th>Вс</th></tr>
            </thead>
            <tbody>
              <tr>
                <td class="is-out"></td>
                <td class="is-out"></td>
                <td><span class="day-number">1</span></td>
                <td><span class="day-number">2</span></td>
                <td><span class="day-number">3</span></td>
                <td><span class="day-number">4</span>
      <button class="event" data-title="Задержка рейсов" data-when="04 октября, 09:00–23:00" data-where="аэропорт им. Луи Армстронга" data-text="Колдун, опаздывающий на свой самолёт, - горе в семье. А ещё - в целом городе. Сверхплотный туман заволакивает городской аэропорт, создавая транспортный коллапс. НЕестественное явление не позволяет покинуть здание, а стоит туману пробраться внутрь, как становится ясно, что монстры скрываются не только во тьме, но и в мельчайших частицах воды, которые скопились в воздухе." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Задержка рейсов
                  </button></td>
                <td><span class="day-number">5</span></td>
              </tr>
              <tr>
                <td><span class="day-number">6</span></td>
                <td><span class="day-number">7</span>
      <button class="event" data-title="La Maison de papier" data-when="07 октября, 13:00–19:00" data-where="магический рынок" data-text="Ещё один вторник на магическом рынке (скрытые от туристов ряды с волшебными товарами, а также отдельные магазины) становится совсем не скучным. Банда грабителей в результате неудачного стечения обстоятельств берёт в заложники персонал и посетителей самого дорого магазина. Успеют ли СМС вмещаться до того, как прольётся первая кровь? (Нет)." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> La Maison de papier
                  </button></td>
                <td><span class="day-number">8</span></td>
                <td><span class="day-number">9</span></td>
                <td><span class="day-number">10</span></td>
                <td><span class="day-number">11</span></td>
                <td><span class="day-number">12</span></td>
              </tr>
              <tr>
                <td><span class="day-number">13</span></td>
                <td><span class="day-number">14</span></td>
                <td><span class="day-number">15</span></td>
                <td><span class="day-number">16</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Неделя террора
                  </button>
                </td>
                <td><span class="day-number">17</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">18</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">19</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
              </tr>
              <tr>
                <td><span class="day-number">20</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">21</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">22</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">23</span>
                  <button class="event" data-title="Неделя террора" data-when="16 - 23 октября" data-where="болота Манчак" data-text="День, когда на болотах было обнаружено первое растерзанное тело. Ругару терроризует болота в течении недели. Кемпинг и туристические экскурсии следовало бы отложить, но остались смельчаки, что не склонны менять планы." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span>
                  </button></td>
                <td><span class="day-number">24</span></td>
                <td><span class="day-number">25</span></td>
                <td><span class="day-number">26</span></td>
              </tr>
              <tr>
                <td><span class="day-number">27</span></td>
                <td><span class="day-number">28</span></td>
                <td><span class="day-number">29</span></td>
                <td><span class="day-number">30</span></td>
                <td><span class="day-number">31</span>
                  <button class="event" data-title="Krewe of BOO!" data-when="31 октября, ночь" data-where="улицы Нового Орлеана" data-text="Хэллоуин в Новом Орлеане всегда был особенным, но 2025-й обещает переписать правила игры. Krewe of BOO! выходит на улицы города: мистические платформы, колдовской джаз и маски, за которыми может скрываться кто угодно: сосед с Мариньи, древний дух с Миссисипи или существо, которое ещё вчера боялось показать клыки." aria-haspopup="dialog">
                    <span class="dot" aria-hidden="true"></span> Krewe of BOO!
                  </button>
                </td>
                <td class="is-out"></td>
                <td class="is-out"></td>
              </tr>
            </tbody>
          </table>
        </section>
      </div>

      <!-- мироописание от духа -->
      <div class="info-panel" id="infoPanel" aria-live="polite" aria-atomic="true" role="dialog" aria-modal="false">
        <div class="close-btn" id="panelClose">— закрыть —</div>
        <div class="info-panel__content">
          <h5 id="panelTitle">Заголовок</h5>
          <em id="panelMeta">Когда и где</em>
          <div id="panelBody">Текст</div>
        </div>
      </div>
    </div>
  </div>

  <script>
    (function(){
      // === ЭЛЕМЕНТЫ ===
      const calendar = document.getElementById('calendar');
      const panel = document.getElementById('infoPanel');
      const panelClose = document.getElementById('panelClose');
      const pTitle = document.getElementById('panelTitle');
      const pMeta = document.getElementById('panelMeta');
      const pBody = document.getElementById('panelBody');

      let currentTrigger = null;

      // === ОТКРЫТИЕ ПАНЕЛИ ===
      function openFromTrigger(btn){
        currentTrigger = btn;
        pTitle.textContent = btn.dataset.title || btn.textContent.trim();
        const whenTxt = btn.dataset.when ? btn.dataset.when : '';
        const whereTxt = btn.dataset.where ? (whenTxt ? ' · ' : '') + btn.dataset.where : '';
        pMeta.textContent = (whenTxt + whereTxt).trim();
        pBody.textContent = btn.dataset.text || '';
        panel.classList.add('is-open');
        panel.setAttribute('aria-modal','true');
        panelClose.focus();
      }

      // === ЗАКРЫТИЕ ПАНЕЛИ ===
      function closePopover(){
        panel.classList.remove('is-open');
        panel.setAttribute('aria-modal','false');
        if (currentTrigger) currentTrigger.focus();
      }

      // === СЛУШАТЕЛИ ===
      calendar.addEventListener('click', (e)=>{
        const btn = e.target.closest('.event');
        if (!btn) return;
        openFromTrigger(btn);
      });

      panelClose.addEventListener('click', closePopover);
      document.addEventListener('keydown', (e)=>{ if (e.key === 'Escape') closePopover(); });

      // Быстрое копирование по двойному клику по заголовку
      pTitle.addEventListener('dblclick', async ()=>{
        const text = `${pTitle.textContent}\n${pMeta.textContent}\n\n${pBody.textContent}`.trim();
        try { await navigator.clipboard.writeText(text); } catch(e){}
      });

      // === Самопроверка/"тесты" в консоли ===
      (function selfTest(){
        const results = [];
        const reqIds = ['calendar','infoPanel','panelClose','panelTitle','panelMeta','panelBody'];
        const missing = reqIds.filter(id => !document.getElementById(id));
        results.push(['IDs exist', missing.length===0, missing.length ? ('missing: '+missing.join(', ')) : 'ok']);
        const eventsCount = document.querySelectorAll('.event').length;
        results.push(['Events present', eventsCount>0, 'count='+eventsCount]);
        console.groupCollapsed('%cCalendar self-test','background:#0f1630;color:#cfe0ff;padding:2px 6px;border-radius:6px');
        results.forEach(([name, ok, msg])=> console[ok?'log':'warn'](`${name}: ${ok?'OK':'FAIL'} (${msg})`));
        console.groupEnd();
        if (location.hash === '#test' && eventsCount){
          const first = document.querySelector('.event');
          openFromTrigger(first);
          setTimeout(closePopover, 1200);
        }
      })();
    })();
  </script>
</body>
</html>
[/html]
Все описанные выше события можно использовать для личных отыгрышей.

+2

2

hide-autor2
[html]<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Календарь</title>
  <style>
    :root{
      --bg: #0b1020;
      --text: #000000;
      --ring: 0 0 0 2px rgba(16,26,51,.45), 0 0 30px rgba(16,26,51,.15); /* #101a33 glow */
      --radius: 16px;
    }

    html,body{
      height:100%;
      margin:0;
      font: 12px/1.5 'El Messiri' !important;
      color: var(--text);
      background: radial-gradient(1200px 800px at 10% 0%, #101a33 0%, var(--bg) 60%);
      display:flex; align-items:center; justify-content:center; padding:24px;
    }

    .wrap{width:min(1100px, 95vw);}
    h1{font-size:clamp(22px, 3vw, 28px); margin:0 0 14px; font-weight:700; letter-spacing:.2px;}
    .sub{color:#000000; margin:0 0 18px;}

    .calendar{
      position: relative;
      background: transparent; /* фон календаря прозрачный */
      border: 1px solid rgba(0,0,0,.06);
      border-radius: var(--radius);
      box-shadow: 0 10px 40px rgba(0,0,0,.08), inset 0 0 0 1px rgba(0,0,0,.04);
      overflow: hidden;
    }

    .months{ display:flex; gap:2px; }
    .month{
      flex:1; min-width: 460px;
      background: transparent; /* прозрачный фон месяца */
      border-radius: calc(var(--radius) - 2px);
      padding: 12px 12px 16px;
      border: 1px solid rgba(0,0,0,.06);
    }
    .month h2{     margin: 4px 6px 10px;
    font-family: 'loreley antiqua' !important;
    font-size: 26px;
    text-shadow: 0 0 2px #e9cfaa; }

    table{ width:100%; border-collapse: collapse; table-layout: fixed;}
    thead th{
      font-size:12px; color: #000000; font-weight:600; text-transform: uppercase; letter-spacing:.08em; padding:8px 4px 10px; border-bottom:1px solid rgba(0,0,0,.06);
    }
    td{
      vertical-align: top; height: 92px; padding:8px;
      border: 1px solid rgba(0,0,0,.06); /* как у .month */
      position: relative; background: rgba(187,185,185,.50); /* a49a98 @ 50% */
    }
    td.is-out{ opacity: .35; }

    .day-number{ font-size: 12px; color:#000000; font-weight:600; }

    .event{
      margin-top:6px; display:inline-flex; align-items:center; gap:6px; padding:6px 8px; border-radius:10px;
      background: rgba(111,108,109,.12);
      border: 1px dashed rgba(111,108,109,.40);
      color:#000000; cursor: pointer;
      user-select:none; outline: none; white-space: nowrap; max-width: 100%; overflow:hidden; text-overflow: ellipsis;
      transition: box-shadow .2s ease, transform .2s ease;
    }
    .event:hover{ box-shadow: var(--ring); }
    .event:focus-visible{ outline: none; box-shadow: var(--ring); }
    .event .dot{ width:8px; height:8px; border-radius:50%; background: #515966; flex:0 0 8px; }

    /* === Панель подробностей по принципу «мироописание от духа» === */
    .info-panel{
      position:absolute; z-index:5;
      /* Центр по ширине без translate — строгие поля дают 80% ширины */
      left:10%; right:10%; width:auto; /* 80% ширины контейнера */
      /* Высота не более 50% контейнера */
      max-height:70%; top:25px;

      background:#9A9697; color:#111;
      box-shadow: 0 0 0 5px #9A9697 inset, 0 0 0 10px rgba(0,0,0,.1) inset;
      border-radius: 14px; padding:25px; text-align:left;
      overflow:hidden;
      /* Анимация появления/сворачивания */
      opacity:0; transform: translateY(-8px) scale(.98); transition: opacity .22s ease, transform .24s ease;
      visibility: hidden; pointer-events:none;
    }
    .info-panel.is-open{ opacity:1; transform: translateY(0) scale(1); visibility: visible; pointer-events:auto; }
    .info-panel > .info-panel__content{ overflow-y:auto; height: calc(100% - 35px); padding-right:5px; }
    .info-panel h5{ font: 700 20px 'El Messiri', sans-serif; margin:20px 0 10px; position:relative; padding-left:45px; }
    .info-panel h5:before{ content:""; height:2px; width:30px; background: rgba(0,0,0,.7); position:absolute; left:0; top:50%; transform: translateY(-50%); }
    .info-panel em{ font: 400 italic 11px 'El Messiri', sans-serif; display:block; border-right:5px solid rgba(0,0,0,.1); padding-right:10px; text-align:right; margin:10px 0; }
    .close-btn{ background: rgba(0,0,0,.04); border:1px solid rgba(0,0,0,.06); height:15px; text-transform:uppercase; font: 600 10px/15px 'El Messiri', sans-serif; text-align:center; margin-bottom:20px; cursor:pointer; letter-spacing:2px; }

    /* Адаптив */
    @media (max-width: 980px){
      .months{ flex-direction: column; }
      .month{ min-width: unset; }
      td{ height: 80px; }
      /* На мобилках делаем шире и чуть выше */
      .info-panel{ left:4%; right:4%; max-height:70%; top:12px; padding:18px; }
    }
  </style>
</head>
<center><body>
  <div class="wrap">
 
    <div class="calendar" id="calendar">
      <div class="months">
        <!-- Декабрь 2025 -->
        <section class="month" aria-label="Декабрь 2025">
  <h2>2025, Dec</h2>
  <table role="grid">
    <thead>
      <tr><th>Пн</th><th>Вт</th><th>Ср</th><th>Чт</th><th>Пт</th><th>Сб</th><th>Вс</th></tr>
    </thead>
    <tbody>
      <tr>
        <td><span class="day-number">1</span></td>
        <td><span class="day-number">2</span></td>
        <td><span class="day-number">3</span>
          <button class="event" data-title="Первые порталы" data-when="03 декабря" data-where="Болота Манчак" data-text="Первые очаги порталов из демонического измерения были открыты на Манчакских болотах. Сначала всё выглядело как очередная болотная аномалия. Туристы жаловались на мёртвую воду, будто покрытую тонкой масляной плёнкой. Охотники находили растерзанных аллигаторов. Птицы покинули район за несколько дней до того, как люди поняли, что происходит, и ночи над Манчаком стали неестественно тихими. То тут, то там из трясины поднимались пузыри тёплого воздуха с резким запахом серы. Вода местами закипала без причины, а в тумане стали появляться разломы, что вспыхивали багровым светом, пульсировали и исчезали, чтобы через несколько часов открыться вновь, но уже в другом месте.
Когда первый портал стабилизировался, из него начали выходить разведчики, они были дезориентированные, раздражённые, голодные. Тёмные создания, слишком долго ждавшие возможности ступить на землю. Некоторые напоминали животных, если бы природа однажды решила отказаться от логики: многоногие силуэты с глазами там, где не должно быть глаз; бесформенные массы, собирающиеся в подобие тел; твари, двигавшиеся рывками, словно не до конца понимали законы физического мира. Другие были почти человеческие. Никто из них не пытался уйти далеко. Что-то удерживало их внутри болот. Каждый демон привязан к своему пролому и может удаляться лишь на несколько сотен метров, прежде чем его начинает буквально тянуть обратно." aria-haspopup="dialog">
            <span class="dot"></span> Первые порталы
          </button>
        </td>
        <td><span class="day-number">4</span></td>
        <td><span class="day-number">5</span></td>
        <td><span class="day-number">6</span></td>
        <td><span class="day-number">7</span></td>
      </tr>

      <tr>
        <td><span class="day-number">8</span></td>
        <td><span class="day-number">9</span></td>
        <td><span class="day-number">10</span></td>
        <td><span class="day-number">11</span></td>
        <td><span class="day-number">12</span></td>
        <td><span class="day-number">13</span></td>
        <td><span class="day-number">14</span></td>
      </tr>

      <tr>
        <td><span class="day-number">15</span></td>
        <td><span class="day-number">16</span></td>
        <td><span class="day-number">17</span>
          <button class="event" data-title="Одержимые джазом" data-when="17 декабря" data-where="Улица Бурбон" data-text="Во время ночного джем-сейшна неизвестный трубач начинает играть мелодию, которую никто не узнаёт, но все почему-то знают слова. Музыканты один за другим впадают в транс, а слушатели начинают переживать чужие воспоминания. К утру половина квартала убеждена, что прожила чужую жизнь." aria-haspopup="dialog">
            <span class="dot"></span> Одержимые джазом
          </button>
        </td>
        <td><span class="day-number">18</span></td>
        <td><span class="day-number">19</span></td>
        <td><span class="day-number">20</span></td>
        <td><span class="day-number">21</span></td>
      </tr>

      <tr>
        <td><span class="day-number">22</span>
          <button class="event" data-title="Апокалипсис сегодня" data-when="22 декабря" data-where="Garden District" data-text="Сначала задрожали бокалы в дорогих особняках. Затем по старинной штукатурке поползли трещины, лопнули витражи, осыпались карнизы. За первые десять минут район пережил серию локальных землетрясений, слишком слабых, чтобы разрушить весь город, но достаточно точных, чтобы ударить по сердцу старого, богатого, защищённого Нового Орлеана. Подземные коммуникации не выдержали, электроснабжение исчезло квартал за кварталом. Погасли фонари, замолчали генераторы, остановились лифты. Связь оборвалась, телефоны ловили лишь белый шум. Газовые трубы дали течь, и через считанные минуты по улицам поползли первые пожары. Однако, когда земля раскололась, стало ясно: это не землетрясение.
Несколько порталов открылись прямо внутри района. В подвалах особняков, под часовнями, на перекрёстках старых улиц и даже в садовых прудах. Изломы в реальности пульсирували алым светом, и из каждого такого портала в мир просачивалось что-то новое. В отличие от болот, здесь демоны оказались сильнее. Одни подменяли реальность: целые улицы превращались в бесконечные лабиринты, где люди часами ходили кругами, возвращаясь к одной и той же двери. Другие питались паникой, превращая чужие страхи в осязаемые иллюзии. Некоторые  поджигали дома одним прикосновением. Самые опасные подчиняли слабовольных: жители, не успевшие эвакуироваться, начинали открывать двери, ломать защитные печати и впускать внутрь то, что не должно было оказаться на земле.
К моменту прибытия Стражей район уже напоминал зону военных действий. Повреждённые особняки стояли с вывернутыми фасадами, улицы были пусты, ни машин, ни прохожих, ни даже животных. Только вой сирен, дым, запах серы и фигуры, двигающиеся в клубах пыли слишком быстро, чтобы быть людьми.
СМС первыми оцепили район, объявив происходящее экологической катастрофой. Для смертных — утечка химикатов, карантин, опасная зона." aria-haspopup="dialog">
            <span class="dot"></span> Апокалипсис сегодня
          </button>
        </td>
        <td><span class="day-number">23</span></td>
        <td><span class="day-number">24</span>
<button class="event" data-title="Curse Exchange" data-when="24 - 27 декабря" data-where="Новый Орлеан" data-text="Студенты-ведьмы запускают игру «Тайный Санта», но с проклятиями. Идея была шуточной. Пока один участник не получает настоящее древнее проклятие, которое начинает распространяться как вирус. * Проклятие «Возвращённого подарка»: работает по принципу: всё, что ты получаешь, возвращается втройне. Подарили цветы → весь дом зарастает лозами. Подарили свечу → начинается пожар. Подарили шоколад → человек впадает в сахарную кому." aria-haspopup="dialog">
            <span class="dot"></span> Curse Exchange
          </button></td>
        <td><span class="day-number">25</span>
<button class="event" data-title="Curse Exchange" data-when="24 - 27 декабря" data-where="Новый Орлеан" data-text="Студенты-ведьмы запускают игру «Тайный Санта», но с проклятиями. Идея была шуточной. Пока один участник не получает настоящее древнее проклятие, которое начинает распространяться как вирус. * Проклятие «Возвращённого подарка»: работает по принципу: всё, что ты получаешь, возвращается втройне. Подарили цветы → весь дом зарастает лозами. Подарили свечу → начинается пожар. Подарили шоколад → человек впадает в сахарную кому." aria-haspopup="dialog">
            <span class="dot"></span>
          </button></td>
        <td><span class="day-number">26</span>
<button class="event" data-title="Curse Exchange" data-when="24 - 27 декабря" data-where="Новый Орлеан" data-text="Студенты-ведьмы запускают игру «Тайный Санта», но с проклятиями. Идея была шуточной. Пока один участник не получает настоящее древнее проклятие, которое начинает распространяться как вирус. * Проклятие «Возвращённого подарка»: работает по принципу: всё, что ты получаешь, возвращается втройне. Подарили цветы → весь дом зарастает лозами. Подарили свечу → начинается пожар. Подарили шоколад → человек впадает в сахарную кому." aria-haspopup="dialog">
            <span class="dot"></span>
          </button></td>
        <td><span class="day-number">27</span>
<button class="event" data-title="Curse Exchange" data-when="24 - 27 декабря" data-where="Новый Орлеан" data-text="Студенты-ведьмы запускают игру «Тайный Санта», но с проклятиями. Идея была шуточной. Пока один участник не получает настоящее древнее проклятие, которое начинает распространяться как вирус. * Проклятие «Возвращённого подарка»: работает по принципу: всё, что ты получаешь, возвращается втройне. Подарили цветы → весь дом зарастает лозами. Подарили свечу → начинается пожар. Подарили шоколад → человек впадает в сахарную кому." aria-haspopup="dialog">
            <span class="dot"></span>
          </button></td>
        <td><span class="day-number">28</span></td>
      </tr>

      <tr>
        <td><span class="day-number">29</span></td>
        <td><span class="day-number">30</span></td>
        <td><span class="day-number">31</span>
          <button class="event" data-title="Длинная ночь" data-when="31 декабря - 3 января" data-where="Новый Орлеан" data-text="Вампирские кланы и одиночки объединяются, чтобы провести древний ритуал искажения цикла дня: вызвать затяжные грозы, закрыть солнце искусственным штормом,
«затемнить» часть города. В результате над Новым Орлеаном несколько дней подряд стоит неестественный сумрак.." aria-haspopup="dialog">
            <span class="dot"></span> Длинная ночь
          </button>
        </td>
        <td class="is-out"></td>
        <td class="is-out"></td>
        <td class="is-out"></td>
        <td class="is-out"></td>
      </tr>
    </tbody>
  </table>
</section>
      </div>

      <!-- мироописание от духа -->
      <div class="info-panel" id="infoPanel" aria-live="polite" aria-atomic="true" role="dialog" aria-modal="false">
        <div class="close-btn" id="panelClose">— закрыть —</div>
        <div class="info-panel__content">
          <h5 id="panelTitle">Заголовок</h5>
          <em id="panelMeta">Когда и где</em>
          <div id="panelBody">Текст</div>
        </div>
      </div>
    </div>
  </div>

  <script>
    (function(){
      // === ЭЛЕМЕНТЫ ===
      const calendar = document.getElementById('calendar');
      const panel = document.getElementById('infoPanel');
      const panelClose = document.getElementById('panelClose');
      const pTitle = document.getElementById('panelTitle');
      const pMeta = document.getElementById('panelMeta');
      const pBody = document.getElementById('panelBody');

      let currentTrigger = null;

      // === ОТКРЫТИЕ ПАНЕЛИ ===
      function openFromTrigger(btn){
        currentTrigger = btn;
        pTitle.textContent = btn.dataset.title || btn.textContent.trim();
        const whenTxt = btn.dataset.when ? btn.dataset.when : '';
        const whereTxt = btn.dataset.where ? (whenTxt ? ' · ' : '') + btn.dataset.where : '';
        pMeta.textContent = (whenTxt + whereTxt).trim();
        pBody.textContent = btn.dataset.text || '';
        panel.classList.add('is-open');
        panel.setAttribute('aria-modal','true');
        panelClose.focus();
      }

      // === ЗАКРЫТИЕ ПАНЕЛИ ===
      function closePopover(){
        panel.classList.remove('is-open');
        panel.setAttribute('aria-modal','false');
        if (currentTrigger) currentTrigger.focus();
      }

      // === СЛУШАТЕЛИ ===
      calendar.addEventListener('click', (e)=>{
        const btn = e.target.closest('.event');
        if (!btn) return;
        openFromTrigger(btn);
      });

      panelClose.addEventListener('click', closePopover);
      document.addEventListener('keydown', (e)=>{ if (e.key === 'Escape') closePopover(); });

      // Быстрое копирование по двойному клику по заголовку
      pTitle.addEventListener('dblclick', async ()=>{
        const text = `${pTitle.textContent}\n${pMeta.textContent}\n\n${pBody.textContent}`.trim();
        try { await navigator.clipboard.writeText(text); } catch(e){}
      });

      // === Самопроверка/"тесты" в консоли ===
      (function selfTest(){
        const results = [];
        const reqIds = ['calendar','infoPanel','panelClose','panelTitle','panelMeta','panelBody'];
        const missing = reqIds.filter(id => !document.getElementById(id));
        results.push(['IDs exist', missing.length===0, missing.length ? ('missing: '+missing.join(', ')) : 'ok']);
        const eventsCount = document.querySelectorAll('.event').length;
        results.push(['Events present', eventsCount>0, 'count='+eventsCount]);
        console.groupCollapsed('%cCalendar self-test','background:#0f1630;color:#cfe0ff;padding:2px 6px;border-radius:6px');
        results.forEach(([name, ok, msg])=> console[ok?'log':'warn'](`${name}: ${ok?'OK':'FAIL'} (${msg})`));
        console.groupEnd();
        if (location.hash === '#test' && eventsCount){
          const first = document.querySelector('.event');
          openFromTrigger(first);
          setTimeout(closePopover, 1200);
        }
      })();
    })();
  </script>
</body>
</html>
[/html]
[html]<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Календарь</title>
  <style>
    :root{
      --bg: #0b1020;
      --text: #000000;
      --ring: 0 0 0 2px rgba(16,26,51,.45), 0 0 30px rgba(16,26,51,.15); /* #101a33 glow */
      --radius: 16px;
    }

    html,body{
      height:100%;
      margin:0;
      font: 16px/1.5 'El Messiri' !important;
      color: var(--text);
      background: radial-gradient(1200px 800px at 10% 0%, #101a33 0%, var(--bg) 60%);
      display:flex; align-items:center; justify-content:center; padding:24px;
    }

    .wrap{width:min(1100px, 95vw);}
    h1{font-size:clamp(22px, 3vw, 28px); margin:0 0 14px; font-weight:700; letter-spacing:.2px;}
    .sub{color:#000000; margin:0 0 18px;}

    .calendar{
      position: relative;
      background: transparent; /* фон календаря прозрачный */
      border: 1px solid rgba(0,0,0,.06);
      border-radius: var(--radius);
      box-shadow: 0 10px 40px rgba(0,0,0,.08), inset 0 0 0 1px rgba(0,0,0,.04);
      overflow: hidden;
    }

    .months{ display:flex; gap:2px; }
    .month{
      flex:1; min-width: 460px;
      background: transparent; /* прозрачный фон месяца */
      border-radius: calc(var(--radius) - 2px);
      padding: 12px 12px 16px;
      border: 1px solid rgba(0,0,0,.06);
    }
    .month h2{     margin: 4px 6px 10px;
    font-family: 'loreley antiqua' !important;
    font-size: 26px;
    text-shadow: 0 0 2px #e9cfaa; }

    table{ width:100%; border-collapse: collapse; table-layout: fixed;}
    thead th{
      font-size:12px; color: #000000; font-weight:600; text-transform: uppercase; letter-spacing:.08em; padding:8px 4px 10px; border-bottom:1px solid rgba(0,0,0,.06);
    }
    td{
      vertical-align: top; height: 92px; padding:8px;
      border: 1px solid rgba(0,0,0,.06); /* как у .month */
      position: relative; background: rgba(187,185,185,.50); /* a49a98 @ 50% */
    }
    td.is-out{ opacity: .35; }

    .day-number{ font-size: 12px; color:#000000; font-weight:600; }

    .event{
      margin-top:6px; display:inline-flex; align-items:center; gap:6px; padding:6px 8px; border-radius:10px;
      background: rgba(111,108,109,.12);
      border: 1px dashed rgba(111,108,109,.40);
      color:#000000; cursor: pointer;
      user-select:none; outline: none; white-space: nowrap; max-width: 100%; overflow:hidden; text-overflow: ellipsis;
      transition: box-shadow .2s ease, transform .2s ease;
    }
    .event:hover{ box-shadow: var(--ring); }
    .event:focus-visible{ outline: none; box-shadow: var(--ring); }
    .event .dot{ width:8px; height:8px; border-radius:50%; background: #515966; flex:0 0 8px; }

    /* === Панель подробностей по принципу «мироописание от духа» === */
    .info-panel{
      position:absolute; z-index:5;
      /* Центр по ширине без translate — строгие поля дают 80% ширины */
      left:10%; right:10%; width:auto; /* 80% ширины контейнера */
      /* Высота не более 50% контейнера */
      max-height:50%; top:25px;

      background:#9A9697; color:#111;
      box-shadow: 0 0 0 5px #9A9697 inset, 0 0 0 10px rgba(0,0,0,.1) inset;
      border-radius: 14px; padding:25px; text-align:left;
      overflow:hidden;
      /* Анимация появления/сворачивания */
      opacity:0; transform: translateY(-8px) scale(.98); transition: opacity .22s ease, transform .24s ease;
      visibility: hidden; pointer-events:none;
    }
    .info-panel.is-open{ opacity:1; transform: translateY(0) scale(1); visibility: visible; pointer-events:auto; }
    .info-panel > .info-panel__content{ overflow-y:auto; height: calc(100% - 35px); padding-right:5px; }
    .info-panel h5{ font: 700 20px 'El Messiri', sans-serif; margin:20px 0 10px; position:relative; padding-left:45px; }
    .info-panel h5:before{ content:""; height:2px; width:30px; background: rgba(0,0,0,.7); position:absolute; left:0; top:50%; transform: translateY(-50%); }
    .info-panel em{ font: 400 italic 11px 'El Messiri', sans-serif; display:block; border-right:5px solid rgba(0,0,0,.1); padding-right:10px; text-align:right; margin:10px 0; }
    .close-btn{ background: rgba(0,0,0,.04); border:1px solid rgba(0,0,0,.06); height:15px; text-transform:uppercase; font: 600 10px/15px 'El Messiri', sans-serif; text-align:center; margin-bottom:20px; cursor:pointer; letter-spacing:2px; }

    /* Адаптив */
    @media (max-width: 980px){
      .months{ flex-direction: column; }
      .month{ min-width: unset; }
      td{ height: 80px; }
      /* На мобилках делаем шире и чуть выше */
      .info-panel{ left:4%; right:4%; max-height:60%; top:12px; padding:18px; }
    }
  </style>
</head>
<center><body>
  <div class="wrap">
 
    <div class="calendar" id="calendar">
      <div class="months">
              <!-- Январь 2026 -->
        <section class="month" aria-label="Январь 2026">
  <h2>2026, Jan</h2>
  <table role="grid">
    <thead>
      <tr><th>Пн</th><th>Вт</th><th>Ср</th><th>Чт</th><th>Пт</th><th>Сб</th><th>Вс</th></tr>
    </thead>
    <tbody>
      <tr>
        <td class="is-out"></td>
        <td class="is-out"></td>
        <td class="is-out"></td>
        <td><span class="day-number">1</span>
       <button class="event" data-title="Длинная ночь" data-when="31 декабря - 3 января" data-where="Новый Орлеан" data-text="Вампирские кланы и одиночки объединяются, чтобы провести древний ритуал искажения цикла дня: вызвать затяжные грозы, закрыть солнце искусственным штормом,
«затемнить» часть города. В результате над Новым Орлеаном несколько дней подряд стоит неестественный сумрак.." aria-haspopup="dialog">
            <span class="dot"></span>
          </button></td>
        <td><span class="day-number">2</span>
<button class="event" data-title="Длинная ночь" data-when="31 декабря - 3 января" data-where="Новый Орлеан" data-text="Вампирские кланы и одиночки объединяются, чтобы провести древний ритуал искажения цикла дня: вызвать затяжные грозы, закрыть солнце искусственным штормом,
«затемнить» часть города. В результате над Новым Орлеаном несколько дней подряд стоит неестественный сумрак.." aria-haspopup="dialog">
            <span class="dot"></span>
          </button></td>
        <td><span class="day-number">3</span>
<button class="event" data-title="Длинная ночь" data-when="31 декабря - 3 января" data-where="Новый Орлеан" data-text="Вампирские кланы и одиночки объединяются, чтобы провести древний ритуал искажения цикла дня: вызвать затяжные грозы, закрыть солнце искусственным штормом,
«затемнить» часть города. В результате над Новым Орлеаном несколько дней подряд стоит неестественный сумрак.." aria-haspopup="dialog">
            <span class="dot"></span>
          </button></td>
        <td><span class="day-number">4</span></td>
      </tr>

      <tr>
        <td><span class="day-number">5</span></td>
        <td><span class="day-number">6</span></td>
        <td><span class="day-number">7</span>
          <button class="event" data-title="Трамвай дальше не едет" data-when="07 января" data-where="Заброшенное трамвайное депо" data-text="Камбионы «Первого поколения» захватывают одно из  исторических мест - старинное трамвайное депо, где проводят ритуал с собственной кровь с целью открыть портал, расширить его и удержать проход открытым. В качестве подношения предкам на мероприятие приглашено (похищено) более десятка человек." aria-haspopup="dialog">
            <span class="dot"></span> Трамвай дальше не едет
          </button>
        </td>
        <td><span class="day-number">8</span></td>
        <td><span class="day-number">9</span></td>
        <td><span class="day-number">10</span></td>
        <td><span class="day-number">11</span></td>
      </tr>

      <tr>
        <td><span class="day-number">12</span></td>
        <td><span class="day-number">13</span></td>
        <td><span class="day-number">14</span></td>
        <td><span class="day-number">15</span>
          <button class="event" data-title="Hex & the City" data-when="15 января" data-where="Французский квартал" data-text="В элитном ведьмовском книжном клубе одна из участниц случайно активирует старое проклятие ревности. В течение вечера все присутствующие начинают буквально озвучивать мысли друг друга. Спустя час в районе фиксируется рекордное количество разводов, дуэлей и вызовов на магические поединки." aria-haspopup="dialog">
            <span class="dot"></span> Hex & the City
          </button>
        </td>
        <td><span class="day-number">16</span></td>
        <td><span class="day-number">17</span></td>
        <td><span class="day-number">18</span></td>
      </tr>

      <tr>
        <td><span class="day-number">19</span></td>
        <td><span class="day-number">20</span></td>
        <td><span class="day-number">21</span></td>
        <td><span class="day-number">22</span></td>
        <td><span class="day-number">23</span></td>
        <td><span class="day-number">24</span></td>
        <td><span class="day-number">25</span></td>
      </tr>

      <tr>
        <td><span class="day-number">26</span>
          <button class="event" data-title="Bad Voodoo" data-when="26 января" data-where="Tremé" data-text="Кто-то начинает продавать дешёвые поддельные вуду-куклы туристам. Через пару часов выясняется, что они всё-таки работают, просто неправильно. Люди начинают чесаться в неожиданных местах, терять голос и внезапно влюбляться в незнакомцев." aria-haspopup="dialog">
            <span class="dot"></span> Bad Voodoo
          </button>
        </td>
        <td><span class="day-number">27</span></td>
        <td><span class="day-number">28</span></td>
        <td><span class="day-number">29</span></td>
        <td><span class="day-number">30</span></td>
        <td><span class="day-number">31</span></td>
        <td class="is-out"></td>
      </tr>
    </tbody>
  </table>
</section>
      </div>

      <!-- мироописание от духа -->
      <div class="info-panel" id="infoPanel" aria-live="polite" aria-atomic="true" role="dialog" aria-modal="false">
        <div class="close-btn" id="panelClose">— закрыть —</div>
        <div class="info-panel__content">
          <h5 id="panelTitle">Заголовок</h5>
          <em id="panelMeta">Когда и где</em>
          <div id="panelBody">Текст</div>
        </div>
      </div>
    </div>
  </div>

  <script>
    (function(){
      // === ЭЛЕМЕНТЫ ===
      const calendar = document.getElementById('calendar');
      const panel = document.getElementById('infoPanel');
      const panelClose = document.getElementById('panelClose');
      const pTitle = document.getElementById('panelTitle');
      const pMeta = document.getElementById('panelMeta');
      const pBody = document.getElementById('panelBody');

      let currentTrigger = null;

      // === ОТКРЫТИЕ ПАНЕЛИ ===
      function openFromTrigger(btn){
        currentTrigger = btn;
        pTitle.textContent = btn.dataset.title || btn.textContent.trim();
        const whenTxt = btn.dataset.when ? btn.dataset.when : '';
        const whereTxt = btn.dataset.where ? (whenTxt ? ' · ' : '') + btn.dataset.where : '';
        pMeta.textContent = (whenTxt + whereTxt).trim();
        pBody.textContent = btn.dataset.text || '';
        panel.classList.add('is-open');
        panel.setAttribute('aria-modal','true');
        panelClose.focus();
      }

      // === ЗАКРЫТИЕ ПАНЕЛИ ===
      function closePopover(){
        panel.classList.remove('is-open');
        panel.setAttribute('aria-modal','false');
        if (currentTrigger) currentTrigger.focus();
      }

      // === СЛУШАТЕЛИ ===
      calendar.addEventListener('click', (e)=>{
        const btn = e.target.closest('.event');
        if (!btn) return;
        openFromTrigger(btn);
      });

      panelClose.addEventListener('click', closePopover);
      document.addEventListener('keydown', (e)=>{ if (e.key === 'Escape') closePopover(); });

      // Быстрое копирование по двойному клику по заголовку
      pTitle.addEventListener('dblclick', async ()=>{
        const text = `${pTitle.textContent}\n${pMeta.textContent}\n\n${pBody.textContent}`.trim();
        try { await navigator.clipboard.writeText(text); } catch(e){}
      });

      // === Самопроверка/"тесты" в консоли ===
      (function selfTest(){
        const results = [];
        const reqIds = ['calendar','infoPanel','panelClose','panelTitle','panelMeta','panelBody'];
        const missing = reqIds.filter(id => !document.getElementById(id));
        results.push(['IDs exist', missing.length===0, missing.length ? ('missing: '+missing.join(', ')) : 'ok']);
        const eventsCount = document.querySelectorAll('.event').length;
        results.push(['Events present', eventsCount>0, 'count='+eventsCount]);
        console.groupCollapsed('%cCalendar self-test','background:#0f1630;color:#cfe0ff;padding:2px 6px;border-radius:6px');
        results.forEach(([name, ok, msg])=> console[ok?'log':'warn'](`${name}: ${ok?'OK':'FAIL'} (${msg})`));
        console.groupEnd();
        if (location.hash === '#test' && eventsCount){
          const first = document.querySelector('.event');
          openFromTrigger(first);
          setTimeout(closePopover, 1200);
        }
      })();
    })();
  </script>
</body>
</html>
[/html]
Все описанные выше события можно использовать для личных отыгрышей.

+2


Вы здесь » Unholy Mess » Veritas occultatur in tenebris » Календарь сюжетных событий


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