

  /* Полноэкранный, не мешает кликам и выделению */
  #ny100-autumn {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    z-index: 9999;            /* при необходимости снизь до 1000-3000 */
    pointer-events: none;
  }

  /* Тёмная тема/насыщенный фон — делаем листья немного светлее */
  @media (prefers-color-scheme: dark) {
    #ny100-autumn {
      filter: brightness(1.1) saturate(1.05);
    }
  }





/*
  NY100 Autumn — v1.0
  Автор: ты + твой внутренний senior :) — свободно используемый код.
  Особенности:
  - Авто-количество листьев от площади экрана (адаптируется)
  - Три формы листьев (клен/дуб/берёза) + палитра осени
  - Поворот, покачивание, параллакс по размеру, лёгкие порывы ветра
  - Экономит батарею: пауза при скрытой вкладке, clamp FPS
*/

(function() {
  const cnv = document.getElementById('ny100-autumn');
  if (!cnv) return;
  const ctx = cnv.getContext('2d');

  // Настройки — можно под себя
  const OPTIONS = {
    densityBase: 16000,     // чем меньше — тем больше листьев
    minLeaves: 24,
    maxLeaves: 90,
    speedY: [30, 90],       // вертикальная скорость px/сек
    swayAmp: [10, 60],      // амплитуда покачивания по X
    swayFreq: [0.6, 1.6],   // частота покачивания
    rotSpeed: [-60, 60],    // °/сек
    scale: [0.5, 1.4],      // множитель размера
    windPulseEvery: [4, 9], // каждые N сек порыв
    windPulseDur: [0.8, 1.6],
    windStrength: [30, 110], // px/сек добавки по X
    dpiCap: 1.8,             // ограничить сверхвысокие DPI
  };

  // Палитра осени
  const COLORS = [
    '#D35400', '#E67E22', '#AF601A', '#9C640C',
    '#F1C40F', '#CD6155', '#B9770E', '#A04000'
  ];

  // Векторные формы листьев (SVG path), рисуем как изображения
  const SHAPES = [
    // Maple (клен)
    'M50 5 C35 20,25 15,20 30 C10 25,5 40,15 50 C5 55,10 70,25 68 C30 80,45 80,50 90 C55 80,70 80,75 68 C90 70,95 55,85 50 C95 40,90 25,80 30 C75 15,65 20,50 5 Z',
    // Oak (дуб)
    'M45 10 C30 12,25 22,28 30 C18 32,12 40,18 48 C10 52,12 62,22 64 C20 72,28 80,38 78 C42 86,54 86,58 78 C68 80,76 72,74 64 C84 62,86 52,78 48 C84 40,78 32,68 30 C71 22,66 12,55 10 C52 4,48 4,45 10 Z',
    // Birch (берёза/осина)
    'M50 8 C38 10,28 20,26 34 C18 36,14 46,18 56 C22 66,34 72,46 70 C58 72,70 66,74 56 C78 46,74 36,66 34 C64 20,54 10,50 8 Z'
  ];

  // Подготовим offscreen canvas для каждого цвета/формы/масштаба? —
  // динамически растеризуем в runtime (достаточно быстро).
  function makeLeafImage(color, pathStr, size=60) {
    const off = document.createElement('canvas');
    off.width = off.height = size;
    const octx = off.getContext('2d');

    // Нарисуем path
    const path = new Path2D(pathStr);
    octx.save();
    // Центруем и масштабируем под size (исходные координаты ~0..100)
    octx.translate(size/2, size/2);
    const s = size / 110; // немного запасов
    octx.scale(s, s);
    octx.translate(-50, -50);

    // Тень/объём
    octx.fillStyle = color;
    octx.shadowColor = 'rgba(0,0,0,0.15)';
    octx.shadowBlur = size*0.06;
    octx.shadowOffsetY = size*0.03;
    octx.fill(path);

    // Жилка-стебель
    octx.lineWidth = Math.max(1, size*0.02);
    octx.strokeStyle = 'rgba(0,0,0,0.15)';
    octx.beginPath();
    octx.moveTo(50, 90);
    octx.quadraticCurveTo(50, 70, 50, 10);
    octx.stroke();

    octx.restore();
    return off;
  }

  // Размеры экрана/канваса
  let vw = 0, vh = 0, dpr = Math.min(window.devicePixelRatio || 1, OPTIONS.dpiCap);
  function resize() {
    vw = window.innerWidth;
    vh = window.innerHeight;
    dpr = Math.min(window.devicePixelRatio || 1, OPTIONS.dpiCap);
    cnv.width  = Math.round(vw * dpr);
    cnv.height = Math.round(vh * dpr);
    cnv.style.width  = vw + 'px';
    cnv.style.height = vh + 'px';
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  resize();
  window.addEventListener('resize', resize, { passive: true });

  // Кол-во листьев от площади
  function calcLeafCount() {
    const byArea = Math.round((vw * vh) / OPTIONS.densityBase);
    return Math.max(OPTIONS.minLeaves, Math.min(OPTIONS.maxLeaves, byArea));
  }

  // Утилиты
  const rand = (a, b) => a + Math.random() * (b - a);
  const choice = arr => arr[(Math.random()*arr.length)|0];
  const clamp = (v, a, b) => Math.max(a, Math.min(b, v));

  // Подготовим набор растров листьев разных размеров (для чёткости)
  const IMAGE_CACHE = [];
  function prepareImages() {
    IMAGE_CACHE.length = 0;
    const baseSizes = [36, 48, 60, 72, 90]; // пиксели (до масштаба)
    for (const col of COLORS) {
      for (const path of SHAPES) {
        for (const bs of baseSizes) {
          IMAGE_CACHE.push(makeLeafImage(col, path, bs));
        }
      }
    }
  }
  prepareImages();

  function randomLeafImage(scale=1) {
    const img = choice(IMAGE_CACHE);
    // Реальный визуальный размер
    const size = img.width / (window.devicePixelRatio || 1) * scale;
    return { img, size };
  }

  // Модель листа
  class Leaf {
    constructor() {
      this.reset(true);
    }
    reset(initial=false) {
      const scale = rand(OPTIONS.scale[0], OPTIONS.scale[1]);
      const { img, size } = randomLeafImage(scale);
      this.img = img;
      this.size = size;

      // Старт немного выше экрана (или случайно внутри, при первом запуске)
      this.x = rand(-0.1*vw, 1.1*vw);
      this.y = initial ? rand(-vh, vh) : rand(-this.size, -0.2*vh);

      this.vy = rand(OPTIONS.speedY[0], OPTIONS.speedY[1]); // px/sec
      this.baseVX = rand(-10, 10); // лёгкий дрейф

      this.swayAmp = rand(OPTIONS.swayAmp[0], OPTIONS.swayAmp[1]);
      this.swayFreq = rand(OPTIONS.swayFreq[0], OPTIONS.swayFreq[1]);
      this.swayPhase = Math.random() * Math.PI * 2;

      this.rot = rand(0, 360);
      this.rotSpeed = rand(OPTIONS.rotSpeed[0], OPTIONS.rotSpeed[1]);

      this.opacity = clamp(rand(0.85, 1), 0.7, 1);

      // Порывы ветра
      this.windVX = 0;
      this.windUntil = 0;
      this.scheduleNextWind();
    }
    scheduleNextWind() {
      this.nextWindAt = now + rand(OPTIONS.windPulseEvery[0], OPTIONS.windPulseEvery[1]) * 1000;
    }
    maybeWind() {
      if (now >= this.nextWindAt) {
        const dur = rand(OPTIONS.windPulseDur[0], OPTIONS.windPulseDur[1]) * 1000;
        this.windVX = (Math.random() < 0.5 ? -1 : 1) * rand(OPTIONS.windStrength[0], OPTIONS.windStrength[1]);
        this.windUntil = now + dur;
        this.scheduleNextWind();
      } else if (now > this.windUntil) {
        // плавный спад ветра
        this.windVX *= 0.96;
        if (Math.abs(this.windVX) < 2) this.windVX = 0;
      }
    }
    step(dt) {
      this.maybeWind();

      // Покачивание
      const sway = Math.sin(this.swayPhase + this.swayFreq * tSec) * this.swayAmp;

      // Движение
      this.y += this.vy * dt;
      this.x += (this.baseVX + this.windVX) * dt + (sway - this.prevSway || 0) * 0.15;
      this.prevSway = sway;

      // Вращение
      this.rot += this.rotSpeed * dt;
      if (this.rot > 360) this.rot -= 360;
      if (this.rot < 0) this.rot += 360;

      // Респаун
      if (this.y - this.size > vh || this.x < -1.5*this.size || this.x > vw + 1.5*this.size) {
        this.reset(false);
      }
    }
    draw(ctx) {
      ctx.save();
      ctx.globalAlpha = this.opacity;
      ctx.translate(this.x, this.y);
      ctx.rotate(this.rot * Math.PI / 180);
      const s = this.size;
      ctx.drawImage(this.img, -s/2, -s/2, s, s);
      ctx.restore();
    }
  }

  // Создаём листья
  let leaves = [];
  function populate() {
    const count = calcLeafCount();
    leaves = new Array(count).fill(0).map(()=>new Leaf());
  }
  populate();

  // Пересоздаём при ресайзе/ориентации
  let resizeTimer;
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(()=> {
      resize();
      populate();
    }, 150);
  });

  // Анимация
  let last = performance.now();
  let now = last;
  let tSec = 0;
  const targetFPS = 60, minFrame = 1000/targetFPS;

  let running = true;
  document.addEventListener('visibilitychange', () => {
    running = !document.hidden;
    if (!running) last = performance.now();
  });

  function frame(ts) {
    if (!running) return requestAnimationFrame(frame);

    const delta = ts - last;
    if (delta < minFrame * 0.75) { // лёгкий FPS clamp
      return requestAnimationFrame(frame);
    }
    last = ts; now = ts; tSec = ts / 1000;

    const dt = Math.min(delta, 60) / 1000; // защитимся от больших прыжков
    ctx.clearRect(0, 0, vw, vh);

    for (let i=0;i