FunPay — Ultra Modal: "Новое расширение!"

Красивейшее всплывающее окно поверх страницы с ссылкой на новое расширение FunPay Tools. Закрывается кликом/ESC, можно "не показывать снова".

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         FunPay — Ultra Modal: "Новое расширение!"
// @namespace    https://funpay.com/
// @version      9.0.0
// @description  Красивейшее всплывающее окно поверх страницы с ссылкой на новое расширение FunPay Tools. Закрывается кликом/ESC, можно "не показывать снова".
// @author       you
// @match        https://funpay.com/*
// @match        https://*.funpay.com/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  // ---------- SETTINGS ----------
  const STORE_URL = "https://chromewebstore.google.com/detail/funpay-tools/pibmnjjfpojnakckilflcboodkndkibb/";
  const STORAGE_KEY = "fpt_ultra_modal_dismissed";
  const REMIND_AFTER_DAYS = 7; // спустя сколько дней снова показать, если было "Не показывать"
  const HOTKEY = { altKey: true, key: "f" }; // Alt+F — снова открыть модалку вручную

  // Показать снова, если прошло N дней
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      const { ts } = JSON.parse(raw);
      const days = (Date.now() - ts) / (1000 * 60 * 60 * 24);
      if (days < REMIND_AFTER_DAYS) return;
    }
  } catch (_) {}

  // Дождаться готовности body
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init, { once: true });
  } else {
    init();
  }

  // ----------- MAIN ------------
  function init() {
    // Shadow DOM, чтобы стили ни с чем не конфликтовали
    const host = document.createElement("div");
    host.style.all = "initial";
    host.style.position = "fixed";
    host.style.inset = "0";
    host.style.zIndex = "999999999"; // поверх всего
    host.style.pointerEvents = "none"; // пока не вставим слои
    document.documentElement.appendChild(host);

    const shadow = host.attachShadow({ mode: "open" });

    // Backdrop + контейнер
    const wrapper = document.createElement("div");
    wrapper.className = "fp-wrapper";
    wrapper.innerHTML = `
      <div class="fp-backdrop"></div>
      <div class="fp-center">
        <div class="fp-card">
          <button class="fp-close" aria-label="Закрыть">✕</button>

          <div class="fp-badge">
            <span class="fp-pulse"></span>
            <span class="fp-badge-text">Новинка</span>
          </div>

          <div class="fp-title">
            Внимание! Вышло <span class="grad">новое расширение</span>
          </div>

          <div class="fp-subtitle">
            Ваш старый юзерскрипт — <em>древняя хуйня</em> и уже устарел.
            Переходите на <strong>FunPay Tools</strong> — быстрее, удобнее, красивее.
          </div>

          <div class="fp-feats">
            <div class="feat">
              <div class="dot"></div>
              Свежие фичи
            </div>
            <div class="feat">
              <div class="dot"></div>
              Ноль танцев с бубном
            </div>
            <div class="feat">
              <div class="dot"></div>
              Поддержка и стабильность
            </div>
          </div>

          <div class="fp-actions">
            <a class="fp-primary" href="${STORE_URL}" target="_blank" rel="noopener">
              Установить FunPay Tools
            </a>
            <button class="fp-ghost" data-close>Позже</button>
          </div>

          <label class="fp-checkbox">
            <input type="checkbox" class="fp-nomore" />
            Не показывать снова
          </label>
        </div>
      </div>
    `;
    shadow.appendChild(style());
    shadow.appendChild(wrapper);

    // Включаем клики
    host.style.pointerEvents = "auto";

    // Закрытия
    const closeModal = () => {
      wrapper.classList.add("fp-hide");
      setTimeout(() => host.remove(), 220);
      // запомнить, если включен чекбокс
      const noMore = shadow.querySelector(".fp-nomore");
      if (noMore && noMore.checked) {
        try {
          localStorage.setItem(
            STORAGE_KEY,
            JSON.stringify({ ts: Date.now() })
          );
        } catch (_) {}
      }
      // убрать обработчик хоткея
      window.removeEventListener("keydown", onKey);
    };

    const onKey = (e) => {
      // ESC закрыть
      if (e.key === "Escape") closeModal();
      // Alt+F — снова открыть (работает до закрытия)
      if (
        e.key.toLowerCase() === HOTKEY.key &&
        !!e.altKey === HOTKEY.altKey
      ) {
        // Ничего: модал уже открыт. Подсветим кнопку.
        const primary = shadow.querySelector(".fp-primary");
        if (primary) {
          primary.classList.add("fp-wiggle");
          setTimeout(() => primary.classList.remove("fp-wiggle"), 600);
        }
      }
    };

    shadow.querySelector(".fp-backdrop")?.addEventListener("click", closeModal);
    shadow.querySelector(".fp-close")?.addEventListener("click", closeModal);
    shadow.querySelector("[data-close]")?.addEventListener("click", closeModal);
    window.addEventListener("keydown", onKey, false);

    // Лёгкий вход-анимация
    requestAnimationFrame(() => wrapper.classList.add("fp-show"));
  }

  // ---------- STYLES ----------
  function style() {
    const el = document.createElement("style");
    el.textContent = `
      @keyframes fpFadeIn {
        from { opacity: 0 } to { opacity: 1 }
      }
      @keyframes fpPop {
        0% { transform: translateY(6px) scale(.96); opacity: 0 }
        100% { transform: translateY(0) scale(1); opacity: 1 }
      }
      @keyframes fpGlow {
        0% { filter: drop-shadow(0 0 0px rgba(255,255,255,.0)); }
        100% { filter: drop-shadow(0 0 18px rgba(255,255,255,.35)); }
      }
      @keyframes fpPulse {
        0% { transform: scale(1); opacity: .9 }
        50% { transform: scale(1.18); opacity: .5 }
        100% { transform: scale(1); opacity: .9 }
      }
      @keyframes fpWiggle {
        0%,100%{ transform: translateX(0) }
        25%{ transform: translateX(-4px) }
        75%{ transform: translateX(4px) }
      }

      :host, .fp-wrapper { all: initial; }

      .fp-wrapper {
        position: fixed; inset: 0;
        display: grid; place-items: center;
        opacity: 0; transition: opacity .2s ease;
      }
      .fp-wrapper.fp-show { opacity: 1; animation: fpFadeIn .2s ease both; }
      .fp-wrapper.fp-hide { opacity: 0; }

      .fp-backdrop {
        position: absolute; inset: 0;
        background: radial-gradient(80vmax 80vmax at center, rgba(24,24,27,.65), rgba(0,0,0,.55) 60%, rgba(0,0,0,.75));
        backdrop-filter: blur(8px) saturate(120%);
      }

      .fp-center {
        position: relative; width: min(92vw, 720px); padding: 24px;
      }

      .fp-card {
        position: relative;
        border-radius: 24px;
        padding: 28px clamp(20px, 4vw, 36px);
        background:
          linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.05)),
          radial-gradient(120% 120% at 0% 0%, rgba(255,255,255,.08), transparent 40%),
          rgba(24,24,27,.72);
        color: #fff;
        box-shadow:
          0 20px 40px rgba(0,0,0,.45),
          inset 0 0 0 1px rgba(255,255,255,.08);
        backdrop-filter: blur(16px) saturate(140%);
        animation: fpPop .25s ease both, fpGlow 1.2s ease .2s both;
        overflow: clip;
      }

      /* Неоновая граница */
      .fp-card::before {
        content: "";
        position: absolute; inset: -1px;
        border-radius: 26px;
        padding: 1px;
        background: linear-gradient(135deg,
          #a78bfa, #60a5fa 20%, #34d399 40%, #f59e0b 60%, #f472b6 80%, #a78bfa);
        -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
        -webkit-mask-composite: xor; mask-composite: exclude;
        pointer-events: none;
        filter: blur(.4px) saturate(140%);
      }

      .fp-close {
        position: absolute; top: 10px; right: 10px;
        width: 36px; height: 36px; border-radius: 10px;
        border: none; cursor: pointer;
        font-size: 18px; line-height: 1;
        background: rgba(255,255,255,.08);
        color: #fff;
        transition: transform .15s ease, background .2s ease, opacity .2s ease;
      }
      .fp-close:hover { transform: scale(1.06); background: rgba(255,255,255,.14); }
      .fp-close:active { transform: scale(.98); opacity: .9; }

      .fp-badge {
        position: relative;
        display: inline-flex; align-items: center; gap: 10px;
        margin-bottom: 12px; padding: 8px 12px;
        border-radius: 999px;
        background: rgba(255,255,255,.08);
        border: 1px solid rgba(255,255,255,.14);
      }
      .fp-pulse {
        width: 10px; height: 10px; border-radius: 999px;
        background: #34d399;
        box-shadow: 0 0 16px #34d399;
        animation: fpPulse 1.6s ease-in-out infinite;
      }
      .fp-badge-text { font-weight: 600; letter-spacing: .3px; }

      .fp-title {
        font-size: clamp(24px, 3.4vw, 36px);
        font-weight: 800;
        line-height: 1.15;
        margin-bottom: 10px;
        letter-spacing: .2px;
      }

      .grad {
        background: linear-gradient(135deg, #a78bfa, #60a5fa, #34d399, #f472b6);
        -webkit-background-clip: text; background-clip: text;
        color: transparent;
      }

      .fp-subtitle {
        opacity: .95;
        font-size: clamp(14px, 2vw, 16px);
        line-height: 1.6;
        margin-bottom: 18px;
      }
      .fp-subtitle em { font-style: normal; opacity: .9; text-decoration: underline wavy rgba(255,255,255,.35); }
      .fp-subtitle strong { font-weight: 800; }

      .fp-feats {
        display: grid;
        grid-template-columns: 1fr;
        gap: 10px;
        margin: 14px 0 22px;
      }
      @media (min-width: 520px) {
        .fp-feats { grid-template-columns: 1fr 1fr 1fr; }
      }
      .feat {
        display: flex; align-items: center; gap: 10px;
        padding: 10px 12px; border-radius: 12px;
        background: rgba(255,255,255,.06);
        border: 1px solid rgba(255,255,255,.1);
        white-space: nowrap;
        text-overflow: ellipsis; overflow: hidden;
      }
      .feat .dot {
        width: 8px; height: 8px; border-radius: 999px;
        background: #60a5fa; box-shadow: 0 0 8px rgba(96,165,250,.9);
      }

      .fp-actions {
        display: flex; gap: 10px; flex-wrap: wrap;
        align-items: center; margin-bottom: 10px;
      }

      .fp-primary, .fp-ghost {
        appearance: none; border: none; cursor: pointer;
        padding: 12px 16px; border-radius: 14px;
        font-weight: 700; text-decoration: none;
        transition: transform .12s ease, box-shadow .2s ease, background .2s ease, opacity .2s ease;
        will-change: transform;
      }
      .fp-primary {
        background: linear-gradient(135deg, #6366f1, #22c55e);
        color: #fff;
        box-shadow: 0 12px 22px rgba(34,197,94,.25), 0 6px 12px rgba(99,102,241,.25);
      }
      .fp-primary:hover { transform: translateY(-1px); }
      .fp-primary:active { transform: translateY(0); opacity: .92; }
      .fp-primary.fp-wiggle { animation: fpWiggle .6s ease both; }

      .fp-ghost {
        background: rgba(255,255,255,.08); color: #fff;
        border: 1px solid rgba(255,255,255,.14);
      }
      .fp-ghost:hover { background: rgba(255,255,255,.12); transform: translateY(-1px); }
      .fp-ghost:active { transform: translateY(0); opacity: .95; }

      .fp-checkbox {
        display: inline-flex; align-items: center; gap: 8px;
        opacity: .85; user-select: none; cursor: pointer;
      }
      .fp-checkbox input {
        width: 16px; height: 16px; accent-color: #22c55e; cursor: pointer;
      }
    `;
    return el;
  }
})();