Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>moonlight.exe</title>
<style>
  * { box-sizing: border-box; }
  html, body {
    margin: 0; padding: 0;
    min-height: 100vh;
    background: radial-gradient(circle at 50% 40%, #101024 0%, #050510 70%, #000 100%);
    display: flex; align-items: center; justify-content: center;
    font-family: "Courier New", monospace;
    overflow: hidden;
  }
  .window {
    position: relative;
    width: 960px;
    max-width: 95vw;
    background: #f0ead6;
    border: 2px solid #0a0a1a;
    box-shadow:
      8px 8px 0 rgba(240, 234, 214, 0.12),
      0 30px 80px rgba(240, 234, 214, 0.08),
      0 20px 60px rgba(0,0,0,0.7);
    transition: opacity 0.5s ease, transform 0.5s ease, width 0.4s ease;
    transform-origin: center;
  }
  .window.closed { opacity: 0; transform: scale(0.85); pointer-events: none; }
  .window.maximized { width: min(100vw, calc((100vh - 40px) * 16 / 9)); max-width: none; }

  .titlebar {
    display: flex; align-items: center; justify-content: space-between;
    background: #f0ead6; height: 30px; padding: 0 8px;
    border-bottom: 2px solid #0a0a1a; user-select: none;
  }
  .titlebar .icon { width: 16px; height: 16px; background: #0a0a1a; }
  .titlebar .title {
    font-size: 12px; color: #0a0a1a; font-weight: bold;
    letter-spacing: 2px; text-transform: lowercase;
  }
  .titlebar .buttons { display: flex; gap: 6px; }
  .btn {
    width: 18px; height: 18px;
    border: 2px solid #0a0a1a; background: transparent;
    position: relative; cursor: pointer; padding: 0; outline: none;
    transition: background 0.12s;
  }
  .btn:hover { background: rgba(10,10,26,0.12); }
  .btn:active { background: rgba(10,10,26,0.28); }
  .btn.min::after {
    content: ""; position: absolute;
    left: 3px; right: 3px; bottom: 4px;
    height: 2px; background: #0a0a1a;
  }
  .btn.max::after {
    content: ""; position: absolute;
    left: 4px; right: 4px; top: 4px; bottom: 4px;
    border: 2px solid #0a0a1a;
  }
  .btn.close::before,
  .btn.close::after {
    content: ""; position: absolute;
    left: 50%; top: 50%;
    width: 2px; height: 13px; background: #0a0a1a;
    transform-origin: center;
  }
  .btn.close::before { transform: translate(-50%, -50%) rotate(45deg); }
  .btn.close::after  { transform: translate(-50%, -50%) rotate(-45deg); }

  .scene {
    position: relative; width: 100%;
    aspect-ratio: 16 / 9;
    background: #0a0a1a;
    overflow: hidden;
    max-height: 1500px;
    transition: max-height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .window.minimized .scene { max-height: 0; }

  canvas {
    display: block; width: 100%; height: 100%;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
  }

  .restore {
    position: fixed; bottom: 40px; left: 50%;
    transform: translateX(-50%) translateY(20px);
    background: #f0ead6; color: #0a0a1a;
    border: 2px solid #0a0a1a;
    padding: 10px 22px;
    font-family: inherit; font-weight: bold;
    cursor: pointer; letter-spacing: 2px; font-size: 12px;
    opacity: 0; pointer-events: none;
    transition: opacity 0.3s, transform 0.3s;
    box-shadow: 4px 4px 0 rgba(240, 234, 214, 0.2);
  }
  .restore.visible { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
  .restore:hover { background: #fff; }
</style>
</head>
<body>

<div class="window" id="win">
  <div class="titlebar">
    <div class="icon"></div>
    <div class="title">moonlight</div>
    <div class="buttons">
      <button class="btn min" id="btnMin" title="Свернуть"></button>
      <button class="btn max" id="btnMax" title="Развернуть"></button>
      <button class="btn close" id="btnClose" title="Закрыть"></button>
    </div>
  </div>
  <div class="scene">
    <canvas id="c"></canvas>
  </div>
</div>

<button class="restore" id="btnRestore">ОТКРЫТЬ</button>

<script>
/* =====================================================
   CANVAS
===================================================== */
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = 480, H = 270;
canvas.width = W;
canvas.height = H;
ctx.imageSmoothingEnabled = false;

const CREAM = '#f0ead6';
const NIGHT = 'rgb(10,10,26)';

/* =====================================================
   RANDOM
===================================================== */
let seed = 1337;
function rnd() {
  seed = (seed * 9301 + 49297) % 233280;
  return seed / 233280;
}

/* =====================================================
   BACKGROUND (static)
===================================================== */
const bgCanvas = document.createElement('canvas');
bgCanvas.width = W; bgCanvas.height = H;
const bg = bgCanvas.getContext('2d');
bg.imageSmoothingEnabled = false;

// Функция для «пиксельного» круга
function fillCircle(c, cx, cy, r, color) {
  c.fillStyle = color;
  for (let y = -r - 1; y <= r + 1; y++) {
    for (let x = -r - 1; x <= r + 1; x++) {
      const d = x*x + y*y;
      if (d <= r*r + r*0.55) c.fillRect(cx + x, cy + y, 1, 1);
    }
  }
}

// Полумесяц (разница двух кругов)
function drawCrescent(c, cx, cy, R, cutX, cutY, rCut) {
  c.fillStyle = CREAM;
  for (let y = -R - 2; y <= R + 2; y++) {
    for (let x = -R - 2; x <= R + 2; x++) {
      const inOuter = (x*x + y*y) <= R*R + R*0.55;
      const dx = x - cutX, dy = y - cutY;
      const inCut = (dx*dx + dy*dy) <= rCut*rCut + rCut*0.55;
      if (inOuter && !inCut) c.fillRect(cx + x, cy + y, 1, 1);
    }
  }
}

const MOON_X = 240, MOON_Y = 88, MOON_R = 34;

function buildBackground() {
  // Градиент неба
  for (let y = 0; y < H; y++) {
    const t = Math.min(1, y / (H - 55));
    const r = Math.round(8  + (28 - 8)  * t);
    const g = Math.round(8  + (28 - 8)  * t);
    const b = Math.round(26 + (54 - 26) * t);
    bg.fillStyle = `rgb(${r},${g},${b})`;
    bg.fillRect(0, y, W, 1);
  }

  // Мягкое свечение вокруг луны
  const glow = bg.createRadialGradient(MOON_X, MOON_Y, MOON_R * 0.8, MOON_X, MOON_Y, MOON_R * 4);
  glow.addColorStop(0, 'rgba(240,234,214,0.13)');
  glow.addColorStop(0.5, 'rgba(240,234,214,0.05)');
  glow.addColorStop(1, 'rgba(240,234,214,0)');
  bg.fillStyle = glow;
  bg.fillRect(MOON_X - MOON_R*4, MOON_Y - MOON_R*4, MOON_R*8, MOON_R*8);

  // Луна
  drawCrescent(bg, MOON_X, MOON_Y, MOON_R, 20, -3, MOON_R - 2);

  // Кратеры на освещённой части
  bg.fillStyle = 'rgba(10,10,26,0.30)';
  bg.fillRect(MOON_X - 22, MOON_Y - 12, 4, 4);
  bg.fillRect(MOON_X - 14, MOON_Y + 6, 3, 3);
  bg.fillRect(MOON_X - 24, MOON_Y + 4, 3, 3);
  bg.fillRect(MOON_X - 10, MOON_Y - 20, 3, 3);
  bg.fillRect(MOON_X - 20, MOON_Y - 2, 2, 2);
  bg.fillRect(MOON_X - 6,  MOON_Y + 14, 2, 2);

  // Дальние холмы (силуэт)
  bg.fillStyle = 'rgba(240,234,214,0.07)';
  for (let x = 0; x < W; x++) {
    const y = H - 48 - Math.sin(x * 0.014) * 9 - Math.sin(x * 0.037 + 1) * 5;
    bg.fillRect(x, Math.floor(y), 1, H - Math.floor(y));
  }
  bg.fillStyle = 'rgba(240,234,214,0.10)';
  for (let x = 0; x < W; x++) {
    const y = H - 38 - Math.sin(x * 0.02 + 2) * 6;
    bg.fillRect(x, Math.floor(y), 1, H - Math.floor(y));
  }

  // Ближний холм — «земля»
  bg.fillStyle = CREAM;
  for (let x = 0; x < W; x++) {
    const y = H - 28 - Math.sin(x * 0.021 + 0.5) * 5 - Math.sin(x * 0.05) * 3;
    bg.fillRect(x, Math.floor(y), 1, H - Math.floor(y));
  }
  // Тёмный контур
  bg.fillStyle = 'rgba(10,10,26,0.5)';
  for (let x = 0; x < W; x++) {
    const y = H - 28 - Math.sin(x * 0.021 + 0.5) * 5 - Math.sin(x * 0.05) * 3;
    bg.fillRect(x, Math.floor(y), 1, 1);
  }

  // Травинки
  seed = 777;
  bg.fillStyle = CREAM;
  for (let x = 6; x < W; x += 6 + Math.floor(rnd() * 14)) {
    if (Math.abs(x - W/2) < 32) continue; // не под котом
    const y = Math.floor(H - 28 - Math.sin(x * 0.021 + 0.5) * 5 - Math.sin(x * 0.05) * 3);
    const h = 1 + Math.floor(rnd() * 2);
    bg.fillRect(x, y - h, 1, h);
    if (rnd() < 0.55) bg.fillRect(x - 1, y - h + 1, 1, 1);
    if (rnd() < 0.55) bg.fillRect(x + 1, y - h + 1, 1, 1);
    if (rnd() < 0.25) bg.fillRect(x, y - h - 1, 1, 1);
  }
}

/* =====================================================
   STARS
===================================================== */
const stars = [];
function buildStars() {
  seed = 987654;
  for (let i = 0; i < 460; i++) {
    const x = Math.floor(rnd() * W);
    const y = Math.floor(rnd() * (H - 70));
    const type = rnd();
    const phase = rnd() * Math.PI * 2;
    const speed = 0.4 + rnd() * 3.2;
    const bright = 0.45 + rnd() * 0.55;
    stars.push({ x, y, type, phase, speed, bright });
  }
}

function drawStars(t) {
  for (let i = 0; i < stars.length; i++) {
    const s = stars[i];
    const twinkle = Math.sin(s.phase + t * s.speed) * 0.5 + 0.5;
    const alpha = s.bright * (0.25 + twinkle * 0.75);

    ctx.globalAlpha = alpha;
    ctx.fillStyle = CREAM;

    if (s.type < 0.07) {
      // Плюсик + мерцающий центр
      ctx.fillRect(s.x, s.y - 1, 1, 3);
      ctx.fillRect(s.x - 1, s.y, 3, 1);
      if (twinkle > 0.6) {
        ctx.globalAlpha = alpha * 0.6;
        ctx.fillRect(s.x - 2, s.y, 1, 1);
        ctx.fillRect(s.x + 2, s.y, 1, 1);
        ctx.fillRect(s.x, s.y - 2, 1, 1);
        ctx.fillRect(s.x, s.y + 2, 1, 1);
      }
    } else if (s.type < 0.18) {
      // Крупный квадратик
      ctx.fillRect(s.x, s.y, 2, 2);
      if (twinkle > 0.75) {
        ctx.globalAlpha = alpha * 0.5;
        ctx.fillRect(s.x - 1, s.y, 1, 1);
        ctx.fillRect(s.x + 2, s.y, 1, 1);
        ctx.fillRect(s.x, s.y - 1, 1, 1);
        ctx.fillRect(s.x, s.y + 2, 1, 1);
      }
    } else {
      // Точка
      ctx.fillRect(s.x, s.y, 1, 1);
    }
  }
  ctx.globalAlpha = 1;
}

/* =====================================================
   SHOOTING STARS
===================================================== */
const shooters = [];
let shootTimer = 1.5;

function updateShooters(dt) {
  shootTimer -= dt;
  if (shootTimer <= 0) {
    shootTimer = 1.8 + Math.random() * 3.5;
    const fromLeft = Math.random() < 0.5;
    shooters.push({
      x: fromLeft ? -10 : W + 10,
      y: 20 + Math.random() * (H * 0.35),
      vx: (fromLeft ? 1 : -1) * (140 + Math.random() * 90),
      vy: 35 + Math.random() * 45,
      life: 1.6 + Math.random() * 1.2,
      trail: []
    });
  }
  for (let i = shooters.length - 1; i >= 0; i--) {
    const s = shooters[i];
    s.x += s.vx * dt;
    s.y += s.vy * dt;
    s.trail.push({ x: s.x, y: s.y });
    if (s.trail.length > 18) s.trail.shift();
    s.life -= dt;
    if (s.life <= 0 || s.x < -60 || s.x > W + 60 || s.y > H) {
      shooters.splice(i, 1);
    }
  }
}

function drawShooters() {
  ctx.fillStyle = CREAM;
  for (const s of shooters) {
    const n = s.trail.length;
    for (let i = 0; i < n; i++) {
      const p = s.trail[i];
      const t = i / n;
      ctx.globalAlpha = Math.pow(t, 1.6);
      const sz = i === n - 1 ? 2 : 1;
      ctx.fillRect(Math.floor(p.x), Math.floor(p.y), sz, sz);
    }
  }
  ctx.globalAlpha = 1;
}

/* =====================================================
   MOON DRIPS
===================================================== */
const drips = [];
function initDrips() {
  const basePositions = [
    { x: MOON_X - 18, y: MOON_Y + MOON_R - 6 },
    { x: MOON_X - 12, y: MOON_Y + MOON_R - 3 },
    { x: MOON_X - 6,  y: MOON_Y + MOON_R - 1 },
  ];
  basePositions.forEach((p, i) => {
    drips.push({
      x: p.x, startY: p.y, y: p.y,
      vy: 0,
      maxDrop: 5 + Math.random() * 10,
      delay: i * 600 + Math.random() * 900,
    });
  });
}

function updateDrips(dt) {
  for (const d of drips) {
    if (d.delay > 0) { d.delay -= dt * 1000; continue; }
    d.vy += dt * 22;
    d.y += d.vy * dt * 40;
    if (d.y - d.startY > d.maxDrop) {
      d.y = d.startY; d.vy = 0;
      d.delay = 900 + Math.random() * 1800;
      d.maxDrop = 4 + Math.random() * 10;
    }
  }
}

function drawDrips() {
  ctx.fillStyle = CREAM;
  for (const d of drips) {
    if (d.delay > 0) continue;
    ctx.fillRect(Math.floor(d.x), Math.floor(d.y), 1, 2);
    if (d.vy > 30) ctx.fillRect(Math.floor(d.x), Math.floor(d.y) + 2, 1, 1);
  }
}

/* =====================================================
   CAT
===================================================== */
function drawCat(t) {
  const cx = Math.floor(W / 2);
  const groundY = Math.floor(H - 28 - Math.sin(cx * 0.021 + 0.5) * 5 - Math.sin(cx * 0.05) * 3);
  const by = groundY;

  ctx.fillStyle = CREAM;

  /* ---- ХВОСТ (за телом) ---- */
  // Хвост — цепочка толстых пикселей по кривой с бегущей волной
  const segs = 16;
  for (let i = 0; i <= segs; i++) {
    const k = i / segs;
    // Форма: уходит вправо-вверх и изгибается назад к коту
    const baseX = cx + 8 + k * 16;
    const baseY = by - 4 - k * 16 + Math.pow(k, 2) * 6;
    // Волна — сила растёт к концу хвоста
    const wave = Math.sin(t * 6 - k * 5.5) * (2 + k * 5);
    const x = baseX + wave * 0.55;
    const y = baseY + wave;
    const size = i < 3 ? 4 : i < 9 ? 3 : 2;
    ctx.fillRect(Math.floor(x - size / 2), Math.floor(y - size / 2), size, size);
  }

  /* ---- ТЕЛО ---- */
  const bodyTop = by - 20;
  const bodyH = 20;
  for (let y = 0; y < bodyH; y++) {
    const k = y / (bodyH - 1);
    const halfW = 5 + Math.floor(k * 4);
    ctx.fillRect(cx - halfW, bodyTop + y, halfW * 2 + 1, 1);
  }

  /* ---- ГОЛОВА ---- */
  const headCY = bodyTop - 7;
  const headR = 8;
  fillCircle(ctx, cx, headCY, headR, CREAM);

  /* ---- УШКИ ---- */
  for (let i = 0; i < 6; i++) {
    const w = 6 - Math.floor(i * 0.8);
    ctx.fillRect(cx - 8 + Math.floor(i * 0.3), headCY - headR - 1 - i, w, 1);
    ctx.fillRect(cx + 8 - Math.floor(i * 0.3) - w + 1, headCY - headR - 1 - i, w, 1);
  }

  /* ---- МОРДА (вырезы) ---- */
  ctx.fillStyle = NIGHT;
  // Медленное моргание раз в ~5 секунд
  const blinkCycle = (t * 0.22) % 5;
  const blinking = blinkCycle > 4.85;
  if (!blinking) {
    // Глазки-точки
    ctx.fillRect(cx - 4, headCY - 1, 2, 3);
    ctx.fillRect(cx + 3, headCY - 1, 2, 3);
    // Блик
    ctx.fillStyle = CREAM;
    ctx.fillRect(cx - 4, headCY - 1, 1, 1);
    ctx.fillRect(cx + 3, headCY - 1, 1, 1);
    ctx.fillStyle = NIGHT;
  } else {
    // Закрытые глазки
    ctx.fillRect(cx - 4, headCY + 1, 2, 1);
    ctx.fillRect(cx + 3, headCY + 1, 2, 1);
  }
  // Носик
  ctx.fillRect(cx - 1, headCY + 3, 2, 2);
  // Ротик
  ctx.fillRect(cx - 2, headCY + 5, 1, 1);
  ctx.fillRect(cx, headCY + 5, 1, 1);
  ctx.fillRect(cx - 1, headCY + 5, 1, 1);
}

/* =====================================================
   MAIN LOOP
===================================================== */
let lastT = 0;
let elapsed = 0;
let running = true;

function loop(now) {
  if (!running) return;
  const dt = Math.min(0.05, (now - lastT) / 1000) || 0;
  lastT = now;
  elapsed += dt;

  // Фон
  ctx.drawImage(bgCanvas, 0, 0);

  // Звёзды
  drawStars(elapsed);

  // Капли с луны
  updateDrips(dt);
  drawDrips();

  // Падающие звёзды
  updateShooters(dt);
  drawShooters();

  // Кот
  drawCat(elapsed);

  requestAnimationFrame(loop);
}

/* =====================================================
   INIT
===================================================== */
buildBackground();
buildStars();
initDrips();
requestAnimationFrame(t => { lastT = t; loop(t); });

/* =====================================================
   WINDOW CONTROLS
===================================================== */
const win        = document.getElementById('win');
const btnMin     = document.getElementById('btnMin');
const btnMax     = document.getElementById('btnMax');
const btnClose   = document.getElementById('btnClose');
const btnRestore = document.getElementById('btnRestore');

btnMin.addEventListener('click', () => win.classList.toggle('minimized'));
btnMax.addEventListener('click', () => win.classList.toggle('maximized'));
btnClose.addEventListener('click', () => {
  win.classList.add('closed');
  btnRestore.classList.add('visible');
});
btnRestore.addEventListener('click', () => {
  win.classList.remove('closed');
  btnRestore.classList.remove('visible');
});

// Esc — закрыть окно
document.addEventListener('keydown', e => {
  if (e.key === 'Escape') {
    win.classList.add('closed');
    btnRestore.classList.add('visible');
  }
});
</script>
</body>
</html>