[html]
<!-- Стили лежат в style/style.css, секция WORDLE. -->

<div id="wg">
  <div id="wg-loader">
    <div id="wg-loader-spinner"></div>
    <span id="wg-loader-text">Загрузка...</span>
  </div>
</div>

<script>
(function () {
  // ─── Настройки ─────────────────────────────────────────────────────────────
  // Список слов по порядку: слово (5 заглавных букв, без Ё) и необязательная подсказка hint (нет поля = нет кнопки).
  // Каждый день в 00:00 по Москве берётся следующее; после последнего список идёт по кругу.
  const WORDS = [
    {word: 'МАГИЯ'},
    {word: 'ПЛАМЯ'},
    {word: 'РУБЕЖ'},
    {word: 'ОГОНЬ'},
    {word: 'УГОЛЬ'},
    {word: 'ТИТУЛ'},
    {word: 'КРОВЬ'},
    {word: 'ОБРЯД'},
    {word: 'КУЛЬТ'},
    {word: 'РУИНЫ'},
    {word: 'БАШНЯ'},
    {word: 'ОАЗИС'},
    {word: 'ЗАМОК'},
    {word: 'ФЬОРД'},
    {word: 'АРХИВ'},
    {word: 'ПЕПЕЛ'},
    {word: 'ОРДЕН'},
    {word: 'ЕРЕСЬ'},
    {word: 'ДОЗОР'},
    {word: 'АРЕНА'},
    {word: 'ПЕСОК'},
    {word: 'КНЯЗЬ'},
    {word: 'КАЗНА'},
    {word: 'СЕДЛО'},
    {word: 'ДЕКАН'},
    {word: 'ЛЕГАТ'},
    {word: 'ДОГМА'},
    {word: 'ОЗЕРО'},
    {word: 'ВОЙНА'},
    {word: 'ПЛАТО'},
  ];
  const START_DATE  = '2026-10-23';  // ← московский день, в который показывается WORDS[0]
  const STRICT_DICT = true;      // ← true = только слова из словаря; false = любые 5 букв
  const VERSION     = 1;        // ← увеличьте, чтобы сбросить прогресс всех игроков
  const API_KEY  = `wordle_v${VERSION}`;          // ключ хранилища (не содержит слова)
  const DICT_URL = 'https://forumstatic.ru/files/001c/d1/ce/31144.js';                // ← URL загруженного dict.js
  // ───────────────────────────────────────────────────────────────────────────

  // Москва — UTC+3 без перехода на летнее время, поэтому смещаем время на 3 часа
  // и считаем целые сутки; так смена слова не зависит от часового пояса игрока.
  const DAY_MS = 864e5, MSK_OFFSET = 3 * 36e5;
  const mskDay = t => Math.floor((t + MSK_OFFSET) / DAY_MS);
  const dayIndex = mskDay(Date.now()) - mskDay(Date.parse(START_DATE));
  const {word: SECRET, hint: HINT} = WORDS[((dayIndex % WORDS.length) + WORDS.length) % WORDS.length];

  // Прогресс привязан к слову (s.secret === SECRET), поэтому новый день начинается с чистого поля.
  // Страница, открытая через полночь, перезагружается, чтобы подхватить новое слово.
  setTimeout(() => location.reload(), (mskDay(Date.now()) + 1) * DAY_MS - MSK_OFFSET - Date.now() + 1000);

  const STORE = `wg_${SECRET}_v${VERSION}`;
  const ROWS = 6, COLS = 5;
  const KB = [
    ['Й','Ц','У','К','Е','Н','Г','Ш','Щ','З','Х','Ъ'],
    ['Ф','Ы','В','А','П','Р','О','Л','Д','Ж','Э'],
    ['Ввод','Я','Ч','С','М','И','Т','Ь','Б','Ю','⌫']
  ];

  let grid = Array.from({length: ROWS}, () => Array(COLS).fill(''));
  let hues = Array.from({length: ROWS}, () => Array(COLS).fill(''));
  let row = 0, col = 0, over = false, busy = false;
  let kmap = {};
  let dict = null;

  function applyState(s) {
    ({grid, hues, row, col, over, kmap} = s);
  }

  function load() {
    setLoaderText('Данные...');
    const handler = e => {
      if (e.data?.type !== 'wordle-storage-response' || e.data?.key !== API_KEY) return;
      window.removeEventListener('message', handler);
      clearTimeout(timeout);
      let applied = false;
      try {
        const s = JSON.parse(e.data.value);
        if (s?.secret === SECRET) {
          applyState(s);
          applied = true;
        }
      } catch {}
      if (!applied) {
        try {
          const s = JSON.parse(localStorage.getItem(STORE));
          if (s?.secret === SECRET) applyState(s);
        } catch {}
      }
      hideLoader();
      renderAll();
      if (over) {
        const won = hues[row - 1]?.every(c => c === 'green');
        msg(won ? 'Победа! 🎉' : SECRET, true);
        if (won) document.getElementById('wg-share').style.display = 'block';
        document.getElementById('wg-hint')?.style.setProperty('display', 'none');
      }
    };
    window.addEventListener('message', handler);
    const timeout = setTimeout(() => {
      window.removeEventListener('message', handler);
      try {
        const s = JSON.parse(localStorage.getItem(STORE));
        if (s?.secret === SECRET) applyState(s);
      } catch {}
      hideLoader();
      renderAll();
    }, 3000);
    window.parent.postMessage({type: 'wordle-storage-get', key: API_KEY}, '*');
  }

  function save() {
    const state = {secret: SECRET, grid, hues, row, col, over, kmap};
    localStorage.setItem(STORE, JSON.stringify(state));
    window.parent.postMessage({type: 'wordle-storage-set', key: API_KEY, value: JSON.stringify(state)}, '*');
  }

  const tile = (r, c) => document.getElementById(`wt${r}${c}`);
  const kbtn = k => document.getElementById(`wk${k}`);

  function setTile(r, c, pop) {
    const t = tile(r, c);
    const letter = grid[r][c], color = hues[r][c];
    t.textContent = letter;
    t.className = 'wg-tile' + (color ? ` wg-${color}` : letter ? ' wg-has-letter' : '');
    if (pop && letter && !color) {
      t.style.animation = 'none';
      t.offsetHeight;
      t.style.animation = 'wg-pop 0.12s ease';
    }
  }

  function syncKeys() {
    for (const [k, c] of Object.entries(kmap)) {
      const b = kbtn(k);
      if (b) b.className = `wg-key${['Ввод','⌫'].includes(k) ? ' wg-wide' : ''} wg-${c}`;
    }
  }

  function renderAll() {
    for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) setTile(r, c, false);
    syncKeys();
  }

  function check(guess) {
    const res = Array(COLS).fill('gray');
    const sec = [...SECRET], used = Array(COLS).fill(false);
    guess.forEach((l, i) => { if (l === sec[i]) { res[i] = 'green'; used[i] = true; } });
    guess.forEach((l, i) => {
      if (res[i] === 'green') return;
      const j = sec.findIndex((s, idx) => !used[idx] && s === l);
      if (j !== -1) { res[i] = 'yellow'; used[j] = true; }
    });
    return res;
  }

  function reveal(r, result, done) {
    busy = true;
    const DUR = 320, GAP = 110;
    result.forEach((color, c) => {
      setTimeout(() => {
        const t = tile(r, c);
        t.style.transition = `transform ${DUR / 2}ms ease`;
        t.style.transform = 'scaleY(0)';
        setTimeout(() => {
          hues[r][c] = color;
          setTile(r, c, false);
          t.style.transform = 'scaleY(1)';
        }, DUR / 2);
      }, c * GAP);
    });
    setTimeout(() => { busy = false; done(); }, (COLS - 1) * GAP + DUR + 60);
  }

  function shake() {
    const rowEl = document.querySelectorAll('.wg-row')[row];
    rowEl.style.animation = 'none';
    rowEl.offsetHeight;
    rowEl.style.animation = 'wg-shake 0.4s ease';
    setTimeout(() => { rowEl.style.animation = ''; }, 400);
  }

  function submit() {
    if (col < COLS) { shake(); msg('Мало букв'); return; }
    const guess = grid[row];
    if (STRICT_DICT && dict && !dict.has(guess.join(''))) { shake(); msg('Нет в словаре'); return; }
    const result = check(guess);
    const pri = {green: 3, yellow: 2, gray: 1};
    guess.forEach((l, i) => {
      if (!kmap[l] || pri[result[i]] > pri[kmap[l]]) kmap[l] = result[i];
    });
    const won = result.every(c => c === 'green');
    const r = row;
    row++; col = 0;
    reveal(r, result, () => {
      syncKeys();
      if (won) {
        over = true; save();
        window.parent.postMessage({type: 'wordle-solved'}, '*');
        msg('Победа! 🎉', true);
        document.getElementById('wg-share').style.display = 'block';
        document.getElementById('wg-hint')?.style.setProperty('display', 'none');
      } else if (row >= ROWS) {
        over = true; save();
        msg(SECRET, true);
        document.getElementById('wg-hint')?.style.setProperty('display', 'none');
      } else {
        save();
      }
    });
  }

  function handle(key) {
    if (over || busy) return;
    if (key === 'Ввод' || key === 'Enter') {
      submit();
    } else if (key === '⌫' || key === 'Backspace') {
      if (col > 0) { col--; grid[row][col] = ''; setTile(row, col, false); }
    } else {
      const up = key.toUpperCase();
      if (/^[А-ЯЁ]$/.test(up) && col < COLS) {
        grid[row][col] = up === 'Ё' ? 'Е' : up;
        setTile(row, col, true);
        col++;
      }
    }
  }

  let msgT;
  function msg(text, perm) {
    const el = document.getElementById('wg-msg');
    el.textContent = text;
    el.style.opacity = '1';
    clearTimeout(msgT);
    if (!perm) msgT = setTimeout(() => { el.style.opacity = '0'; }, 1800);
  }

  window.wgShare = function () {
    const ob = '\x5B', cb = '\x5D';
    const text = `${ob}hide=10005000${cb}\n${ob}b${cb}Ответ:${ob}/b${cb} ${SECRET}\n${ob}b${cb}Попыток:${ob}/b${cb} ${row}\n${ob}/hide${cb}`;
    const fallback = () => {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.setAttribute('readonly', '');
      ta.style.cssText = 'position:fixed;top:-9999px;left:-9999px;opacity:0;pointer-events:none';
      document.body.appendChild(ta);
      ta.select();
      const onCopy = e => { e.stopImmediatePropagation(); };
      document.addEventListener('copy', onCopy, true);
      try { document.execCommand('copy'); msg('Скопировано!'); } catch { msg('Ошибка копирования'); }
      document.removeEventListener('copy', onCopy, true);
      ta.remove();
    };
    if (navigator.clipboard?.writeText) {
      navigator.clipboard.writeText(text).then(() => msg('Скопировано!')).catch(fallback);
    } else {
      fallback();
    }
  };

  function build() {
    const root = document.getElementById('wg');

    const board = document.createElement('div');
    board.id = 'wg-board';
    for (let r = 0; r < ROWS; r++) {
      const rEl = document.createElement('div');
      rEl.className = 'wg-row';
      for (let c = 0; c < COLS; c++) {
        const t = document.createElement('div');
        t.className = 'wg-tile';
        t.id = `wt${r}${c}`;
        rEl.appendChild(t);
      }
      board.appendChild(rEl);
    }
    root.appendChild(board);

    const msgEl = document.createElement('div');
    msgEl.id = 'wg-msg';
    root.appendChild(msgEl);

    const kb = document.createElement('div');
    kb.id = 'wg-keyboard';
    KB.forEach(krow => {
      const rEl = document.createElement('div');
      rEl.className = 'wg-kb-row';
      krow.forEach(k => {
        const b = document.createElement('button');
        b.className = 'wg-key' + (['Ввод','⌫'].includes(k) ? ' wg-wide' : '');
        b.id = `wk${k}`;
        b.textContent = k;
        b.addEventListener('click', () => handle(k));
        rEl.appendChild(b);
      });
      kb.appendChild(rEl);
    });
    root.appendChild(kb);

    const share = document.createElement('button');
    share.id = 'wg-share';
    share.className = 'button';
    share.textContent = 'Поделиться результатом';
    share.addEventListener('click', () => window.wgShare());
    root.appendChild(share);

    if (HINT) {
      const hintBtn = document.createElement('button');
      hintBtn.id = 'wg-hint';
      hintBtn.className = 'button';
      hintBtn.textContent = 'Подсказка';
      hintBtn.style.display = 'block';
      hintBtn.addEventListener('click', () => msg(HINT, false));
      root.appendChild(hintBtn);
    }

    document.addEventListener('keydown', e => {
      if (e.ctrlKey || e.altKey || e.metaKey) return;
      if (e.key === 'Enter') { e.preventDefault(); handle('Enter'); }
      else if (e.key === 'Backspace') handle('Backspace');
      else handle(e.key);
    });
  }

  function init() {
    build();
    load();
  }

  function setLoaderText(t) {
    const el = document.getElementById('wg-loader-text');
    if (el) el.textContent = t;
  }

  function hideLoader() {
    const el = document.getElementById('wg-loader');
    if (el) el.remove();
  }

  function loadDict() {
    setLoaderText('Словарь...');
    const s = document.createElement('script');
    s.src = DICT_URL;
    s.onload = () => {
      const d = window.WG_DICT || '';
      dict = new Set();
      for (let i = 0; i < d.length; i += 5) dict.add(d.slice(i, i + 5));
      init();
    };
    s.onerror = () => init();
    document.head.appendChild(s);
  }

  document.readyState === 'loading'
    ? document.addEventListener('DOMContentLoaded', loadDict)
    : loadDict();
})();
</script>
[/html]
[hideprofile]