[html]<style>
.shipro-description p a {
    color: #b93e3e!important;
}
  .shipro-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    margin: 20px auto;
    max-width: 900px;
    background: #171b1c;  /* Цвет темный задника*/
    border-radius: 12px;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
    overflow: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
  }

  .shipro-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
  }

  .shipro-image {
    width: 100%;
    max-width: 300px;
    aspect-ratio: 1 / 1;
    background-size: cover;
    background-position: center;
    border-radius: 12px;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
    margin: 20px 20px;
    transition: transform 0.3s ease;
  }

  .shipro-card:hover .shipro-image {
    transform: scale(1.25);
  }

  .shipro-info {
    text-align: center;
    font-family: "american typewriter", sans-serif;
    padding: 20px;
  }

  .shipro-info h5 {
    font-size: 24px;
    margin: 5px;
    color: #b8b2a8; /* Заголовок*/
  }

  .shipro-info em {
    font-size: 14px;
    color: #6c6c6c; /* Цвет текста под заголовком*/
    margin-bottom: 10px;
    display: block;
  }

  .shipro-description {
    font-size: 14px;
    color: #949494; /* Главный текст */
    text-align: justify;
    line-height: 1.5;
    text-indent: 30px;
    max-height: 2000px;
    overflow: hidden;
    padding: 0 10px;
    transition: max-height 0.3s ease;
  }

  .shipro-card:hover .shipro-description {
    max-height: 900px;
  }

  .shipro-description p {
    margin: 0 !important;
    padding: 0 !important;
    font-size: 14px !important;
  }

</style>

<div class="shipro-card">
 
  <div class="shipro-info">
    <h5>СУДОКУ ОНЛАЙН</h5>
   
    <div class="shipro-description">
      <p>Если вы не знали, чем заняться в ожидании поста от соигрока, то теперь можно поиграть в Судоку. И не просто поиграть, а еще и получить за это баллы на счёт. Каждая разыгранная партия - 50$. Доказательством присылайте скрины в этой теме ниже.
      <p><b>Как играть:</b>
<p> - Игровое поле состоит из 81 клетки (9 строк × 9 столбцов). Каждые 9 клеток образуют небольшой квадрат 3×3.
<p> - Часть клеток уже заполнена цифрами-подсказками.
<p> -Нельзя ставить число, если оно уже есть в ряду, колонке или блоке.
<p> -Нельзя использовать числа вне диапазона 1–9 (например, 0 или 10).
<p> -Нельзя вставлять числа наугад — решение должно быть логически обоснованным на каждом шаге.<br>
<p>"Новая игра" генерирует новое поле. Есть возможность посмотреть подсказки и подсмотреть ответ. Но администрация 1920 верит в наших игроков и знает, что у нас нет места читерству.
<p>Так же есть возможность выбрать сложность игры специальной кнопкой.

    </div>
  </div>
</div>
<!--  Можете поставить следом ещё одну такую же про другого персонажа -->
[/html]

[html]<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Sudoku</title>
  <style>
    :root{--cell-size:47px;--gap:4px;--accent:#2b6cb0;--bg:#888888}
    body{font-family:Inter,Segoe UI,Roboto,Arial,sans-serif;background:var(--bg);padding:9px}
    .sudoku-wrap{display:flex;gap:14px;align-items:flex-start}
    .board{display:grid;grid-template-columns:repeat(9,var(--cell-size));grid-template-rows:repeat(9,var(--cell-size));gap:2px;background:#4a4a4b;padding:12px;border-radius:10px;box-shadow:0 6px 18px rgba(20,20,40,0.06)}
    .cell{width:var(--cell-size);height:var(--cell-size);display:flex;align-items:center;justify-content:center;font-size:18px;background:#b7b8b8}
    .cell input{width:100%;height:100%;border:0;text-align:center;font-weight:600;font-size:14px;background:transparent}
    .cell input:focus{outline:2px solid rgba(43,108,176,.15);border-radius:6px}
    .given input{font-weight:650;color:#0b2540}
    .note{font-size:7px;color:#666}
    .thick-right{border-right:3px solid #999}
    .thick-bottom{border-bottom:3px solid #999}
    .controls{min-width:220px}
    button,select{padding:8px 12px;border-radius:8px;border:1px solid #e2e8f0;background:#fff;cursor:pointer}
    button:hover{box-shadow:0 6px 12px rgba(16,24,40,0.06)}
    .actions{display:flex;flex-direction:column;gap:8px}
    .status{margin-top:8px;color:#0b2540;font-weight:600}
    .small{font-size:13px;color:#334155}
    .hint{background:#fffbeb;border-color:#f6e05e}
    .invalid{background:#fff1f2;border-color:#fecaca}
.button-row {
    display: flex;
    flex-wrap: wrap; /* чтобы на маленьких экранах переносились */
    gap: 8px; /* расстояние между кнопками */
}
    @media (max-width:960px){.sudoku-wrap{flex-direction:column;align-items:center}.controls{width:100%}}
  </style>
</head>
<body>
  <h2>Sudoku</h2>
  <div class="sudoku-wrap">
    <div>
      <div id="board" class="board" aria-label="Судоку поле"></div>
    </div>
<div class="button-row">
    <button id="newBtn">Новая игра</button>
    <button id="solveBtn">Показать решение</button>
    <button id="checkBtn">Проверить</button>
    <button id="hintBtn" class="hint">Подсказка</button>
    <button id="resetBtn">Сбросить ходы</button>
    <button id="fillNotesBtn">Добавить пометки</button>
</div>
    <div class="controls">
      <div class="actions">
        <label class="small">СЛОЖНОСТЬ
          <select id="difficulty">
            <option value="35">Лёгкая</option>
            <option value="45" selected>Средняя</option>
            <option value="55">Сложная</option>
          </select>
        </label>
       
       
      </div>
      <div class="status" id="status">Готово</div>
      <div class="small" style="margin-top:8px"></div>
    </div>
  </div>
<script>
// Sudoku single-file: генерация + UI + проверка
(() => {
  const boardEl = document.getElementById('board');
  const newBtn = document.getElementById('newBtn');
  const solveBtn = document.getElementById('solveBtn');
  const checkBtn = document.getElementById('checkBtn');
  const hintBtn = document.getElementById('hintBtn');
  const resetBtn = document.getElementById('resetBtn');
  const fillNotesBtn = document.getElementById('fillNotesBtn');
  const difficultySelect = document.getElementById('difficulty');
  const status = document.getElementById('status');

  let solution = []; // full solution 9x9
  let puzzle = [];   // puzzle with zeros for empty
  const inputs = []; // DOM inputs
  let givens = new Set();
  let initialSnapshot = null;

  // create board DOM
  function createGrid(){
    boardEl.innerHTML = '';
    inputs.length = 0;
    for(let r=0;r<9;r++){
      for(let c=0;c<9;c++){
        const cell = document.createElement('div');
        cell.className = 'cell';
        if((c+1)%3===0 && c!==8) cell.classList.add('thick-right');
        if((r+1)%3===0 && r!==8) cell.classList.add('thick-bottom');
        const inp = document.createElement('input');
        inp.setAttribute('inputmode','numeric');
        inp.setAttribute('maxlength','1');
        inp.dataset.r = r; inp.dataset.c = c;
        inp.addEventListener('input', onInput);
        inp.addEventListener('keydown', onKeydown);
        cell.appendChild(inp);
        boardEl.appendChild(cell);
        inputs.push(inp);
      }
    }
  }

  function onKeydown(e){
    // allow arrow navigation
    const k = e.key;
    const r = +this.dataset.r; const c = +this.dataset.c;
    if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(k)){
      e.preventDefault();
      let nr=r,nc=c;
      if(k==='ArrowUp') nr = (r+8)%9;
      if(k==='ArrowDown') nr = (r+1)%9;
      if(k==='ArrowLeft') nc = (c+8)%9;
      if(k==='ArrowRight') nc = (c+1)%9;
      inputs[nr*9+nc].focus();
    }
    // allow Backspace to clear
    if(k==='Backspace') this.value='';
  }

  function onInput(e){
    const v = this.value.replace(/[^1-9]/g,'').slice(0,1);
    this.value = v;
    const idx = +this.dataset.r*9 + (+this.dataset.c);
    if(givens.has(idx)) return; // don't change given
    // validate by quick check: conflict -> mark invalid
    validateCell(+this.dataset.r, +this.dataset.c);
  }

  function validateCell(r,c){
    const val = inputs[r*9+c].value;
    clearInvalid();
    if(!val) return;
    // check row/col/box
    for(let i=0;i<9;i++){
      if(i!==c && inputs[r*9+i].value===val) markInvalid(r*9+c);
      if(i!==r && inputs[i*9+c].value===val) markInvalid(r*9+c);
    }
    const br = Math.floor(r/3)*3, bc = Math.floor(c/3)*3;
    for(let dr=0;dr<3;dr++) for(let dc=0;dc<3;dc++){
      const rr = br+dr, cc = bc+dc; const idx = rr*9+cc;
      if((rr!==r || cc!==c) && inputs[idx].value===val) markInvalid(r*9+c);
    }
  }

  function markInvalid(idx){
    inputs[idx].classList.add('invalid');
    status.textContent = 'Есть конфликт! Проверьте отмеченные клетки.';
  }
  function clearInvalid(){
    inputs.forEach(i=>i.classList.remove('invalid'));
    status.textContent = 'Готово';
  }

  // Sudoku generator: backtracking to create full board
  function generateSolution(){
    const grid = Array.from({length:9},()=>Array(9).fill(0));
    const rows = Array.from({length:9},()=>new Set());
    const cols = Array.from({length:9},()=>new Set());
    const boxes = Array.from({length:9},()=>new Set());

    const order = [];
    for(let r=0;r<9;r++) for(let c=0;c<9;c++) order.push([r,c]);

    function shuffle(a){
      for(let i=a.length-1;i>0;i--){
        const j = Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]];
      }
    }

    function tryFill(pos=0){
      if(pos===81) return true;
      // choose next with minimum candidates to speed up
      let best = -1, bestOpts=null, bestIdx=-1;
      for(let i=pos;i<81;i++){
        const [r,c] = order[i];
        if(grid[r][c]!==0) continue;
        const opts = getCandidates(grid,r,c,rows,cols,boxes);
        if(opts.length===0) return false;
        if(best===-1 || opts.length < best){ best = opts.length; bestOpts = opts; bestIdx = i; }
      }
      // swap chosen to current pos
      [order[pos], order[bestIdx]] = [order[bestIdx], order[pos]];
      const [r,c] = order[pos];
      shuffle(bestOpts);
      for(const val of bestOpts){
        grid[r][c]=val; rows[r].add(val); cols[c].add(val); boxes[Math.floor(r/3)*3+Math.floor(c/3)].add(val);
        if(tryFill(pos+1)) return true;
        grid[r][c]=0; rows[r].delete(val); cols[c].delete(val); boxes[Math.floor(r/3)*3+Math.floor(c/3)].delete(val);
      }
      // restore order? not needed in this run
      return false;
    }

    function getCandidates(grid,r,c,rows,cols,boxes){
      const used = new Set([...rows[r],...cols[c],...boxes[Math.floor(r/3)*3+Math.floor(c/3)]]);
      const opts = [];
      for(let v=1;v<=9;v++) if(!used.has(v)) opts.push(v);
      return opts;
    }

    // init order randomly
    shuffle(order);
    tryFill(0);
    return grid;
  }

  // remove cells to make puzzle
  function makePuzzle(full, empties){
    const p = full.map(r=>r.slice());
    const indices = Array.from({length:81},(_,i)=>i);
    for(let i=indices.length-1;i>0;i--){ const j = Math.floor(Math.random()*(i+1)); [indices[i],indices[j]]=[indices[j],indices[i]] }
    let removed = 0; let idx = 0;
    while(removed < empties && idx < 81){
      const pos = indices[idx++]; const r = Math.floor(pos/9), c = pos%9;
      const backup = p[r][c]; p[r][c]=0;
      // ensure unique solution (simple check: count solutions <=1)
      const copy = p.map(row=>row.slice());
      const cnt = countSolutions(copy,2);
      if(cnt!==1){ p[r][c]=backup; }
      else removed++;
    }
    return p;
  }

  // count solutions with backtracking (stop after limit)
  function countSolutions(grid,limit){
    let count=0;
    function findEmpty(){ for(let r=0;r<9;r++) for(let c=0;c<9;c++) if(grid[r][c]===0) return [r,c]; return null; }
    function canPlace(r,c,v){
      for(let i=0;i<9;i++){ if(grid[r][i]===v || grid[i][c]===v) return false; }
      const br=Math.floor(r/3)*3, bc=Math.floor(c/3)*3;
      for(let dr=0;dr<3;dr++) for(let dc=0;dc<3;dc++) if(grid[br+dr][bc+dc]===v) return false;
      return true;
    }
    function back(){
      if(count>=limit) return;
      const pos = findEmpty(); if(!pos){ count++; return; }
      const [r,c]=pos;
      for(let v=1;v<=9;v++) if(canPlace(r,c,v)){
        grid[r][c]=v; back(); grid[r][c]=0; if(count>=limit) return;
      }
    }
    back(); return count;
  }

  // fill UI from puzzle
  function renderPuzzle(p){
    givens.clear();
    puzzle = p.map(r=>r.slice());
    for(let r=0;r<9;r++) for(let c=0;c<9;c++){
      const idx = r*9+c; const val = p[r][c]; const inp = inputs[idx];
      inp.value = val?String(val):'';
      inp.disabled = false; inp.classList.remove('given');
      if(val){ inp.disabled = true; inp.classList.add('given'); givens.add(idx); }
    }
    initialSnapshot = inputs.map(i=>i.value);
    clearInvalid();
  }

  function to2d(arr){ return Array.from({length:9},(_,r)=>arr.slice(r*9,(r+1)*9)); }

  // new game
  function newGame(){
    status.textContent='Генерирую...';
    setTimeout(()=>{
      solution = generateSolution();
      const empties = parseInt(difficultySelect.value,10);
      puzzle = makePuzzle(solution, empties);
      renderPuzzle(puzzle);
      status.textContent='Новая игра готова';
    },10);
  }

  // solve: fill with solution
  function showSolution(){
    if(!solution.length){ status.textContent='Решение не сгенерировано'; return; }
    for(let r=0;r<9;r++) for(let c=0;c<9;c++){
      const idx=r*9+c; inputs[idx].value = String(solution[r][c]); inputs[idx].classList.remove('invalid');
    }
    status.textContent='Показано решение';
  }

  // check current board
  function checkBoard(){
    let ok=true; clearInvalid();
    for(let r=0;r<9;r++) for(let c=0;c<9;c++){
      const idx=r*9+c; const v = inputs[idx].value;
      if(!v){ ok=false; inputs[idx].classList.add('invalid'); }
      else if(String(solution[r] && solution[r][c]) !== v){ ok=false; inputs[idx].classList.add('invalid'); }
    }
    status.textContent = ok ? 'Вы выиграли! Все верно.' : 'Есть ошибки или пустые клетки.';
  }

  // hint: find an empty cell and fill correct value
  function hint(){
    for(let r=0;r<9;r++) for(let c=0;c<9;c++){
      const idx=r*9+c; if(!inputs[idx].value){ inputs[idx].value = solution[r][c]; inputs[idx].classList.add('hint'); setTimeout(()=>inputs[idx].classList.remove('hint'),900); status.textContent='Подсказка поставлена'; return; }
    }
    status.textContent='Нечего подсказать — поле заполнено';
  }

  // reset to initial snapshot
  function resetToStart(){
    if(!initialSnapshot) return;
    inputs.forEach((inp,i)=>{ if(!givens.has(i)) inp.value = initialSnapshot[i]||''; inp.classList.remove('invalid'); });
    status.textContent='Ходы сброшены';
  }

  // fill notes small numbers (simple: show 1-9 possible in title attribute)
  function fillNotes(){
    for(let r=0;r<9;r++) for(let c=0;c<9;c++){
      const idx=r*9+c; if(inputs[idx].value) { inputs[idx].title=''; continue; }
      const possibles = [];
      for(let v=1;v<=9;v++){
        // simulate placing v
        let ok=true;
        for(let i=0;i<9;i++){ if(inputs[r*9+i].value==String(v)) ok=false; if(inputs[i*9+c].value==String(v)) ok=false; }
        const br=Math.floor(r/3)*3, bc=Math.floor(c/3)*3;
        for(let dr=0;dr<3;dr++) for(let dc=0;dc<3;dc++){ if(inputs[(br+dr)*9+(bc+dc)].value==String(v)) ok=false; }
        if(ok) possibles.push(v);
      }
      inputs[idx].title = 'Возможные: ' + (possibles.length?possibles.join(', '):'-');
    }
    status.textContent='Пометки добавлены (наведите на клетку)';
  }

  // utils: read puzzle from UI into 2d
  function readCurrent(){ return to2d(inputs.map(i=>i.value?parseInt(i.value,10):0)); }

  // initial setup
  createGrid();
  newBtn.addEventListener('click', newGame);
  solveBtn.addEventListener('click', showSolution);
  checkBtn.addEventListener('click', checkBoard);
  hintBtn.addEventListener('click', hint);
  resetBtn.addEventListener('click', resetToStart);
  fillNotesBtn.addEventListener('click', fillNotes);
  difficultySelect.addEventListener('change', ()=>{});

  // generate first game
  newGame();
})();
</script>
</body>
</html>[/html]