Плагин Кнопка копирования для цитат

Плагин автоматически находит все блоки

на странице.

Для каждого блока создается кнопка «Копировать», которая размещается сразу после цитаты.

При нажатии на кнопку текст цитаты копируется в буфер обмена.

Пользователь получает визуальную обратную связь (изменение цвета кнопки, текста и иконки).

Плагин готов к использованию и не требует дополнительных настроек. Он будет работать на всех страницах вашего сайта, где используются цитаты.

Файл copy-quote-button.php

🐘 PHP
<?php
/**
 * Plugin Name: Кнопка копирования для цитат
 * Description: Добавляет эффектную адаптивную кнопку "Копировать" к цитатам, стилизованную под дизайн сайта 1promtai.ru.
 * Version: 2.0
 * Author: Ведерников Сергей ИИ Дэп и ИИ Алиса
 */

// Защита от прямого доступа
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class CopyQuoteButton {

    private static $initialized = false;

    public function __construct() {
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
        add_action( 'wp_footer', array( $this, 'add_copy_button_to_quotes' ) );
    }

    public function enqueue_assets() {
        if ( ! is_admin() ) {
            wp_enqueue_style(
                'copy-quote-button-style',
                plugin_dir_url( __FILE__ ) . 'style.css',
                array(),
                '2.0'
            );

            wp_enqueue_script(
                'copy-quote-button-script',
                plugin_dir_url( __FILE__ ) . 'script.js',
                array(),
                '2.0',
                true
            );
        }
    }

    public function add_copy_button_to_quotes() {
        // Предотвращаем множественное добавление
        if ( self::$initialized ) {
            return;
        }
        self::$initialized = true;
        ?>
        <script>
        document.addEventListener('DOMContentLoaded', function() {
            // Функция для инициализации кнопок
            function initCopyButtons() {
                const quoteBlocks = document.querySelectorAll('blockquote, .wp-block-quote, .quote, .cited');
                
                quoteBlocks.forEach(function(quote) {
                    // Проверяем, не добавлена ли уже кнопка
                    if (quote.dataset.copyButtonAdded === 'true') {
                        return;
                    }
                    
                    // Проверяем, есть ли уже контейнер с кнопкой после цитаты
                    const nextSibling = quote.nextElementSibling;
                    if (nextSibling && nextSibling.classList && nextSibling.classList.contains('copy-quote-button-container')) {
                        return;
                    }

                    // Создаем контейнер для кнопки
                    const buttonContainer = document.createElement('div');
                    buttonContainer.className = 'copy-quote-button-container';
                    
                    // Создаем кнопку с эффектным дизайном
                    const button = document.createElement('button');
                    button.className = 'copy-quote-button';
                    button.setAttribute('aria-label', 'Копировать цитату');
                    button.innerHTML = `
                        <span class="btn-content">
                            <span class="btn-icon">
                                <svg class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                    <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                                    <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                                </svg>
                                <svg class="check-icon hidden" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                    <polyline points="20 6 9 17 4 12"></polyline>
                                </svg>
                            </span>
                            <span class="btn-text">Копировать</span>
                            <span class="btn-shine"></span>
                        </span>
                    `;
                    
                    buttonContainer.appendChild(button);
                    quote.parentNode.insertBefore(buttonContainer, quote.nextSibling);
                    quote.dataset.copyButtonAdded = 'true';
                    
                    // Обработчик клика
                    button.addEventListener('click', function(e) {
                        e.preventDefault();
                        
                        // Получаем текст цитаты
                        const textToCopy = quote.textContent.trim();
                        const currentButton = this;
                        
                        // Эффект нажатия
                        currentButton.classList.add('pressed');
                        setTimeout(() => {
                            currentButton.classList.remove('pressed');
                        }, 300);
                        
                        // Копирование
                        if (navigator.clipboard && navigator.clipboard.writeText) {
                            navigator.clipboard.writeText(textToCopy).then(function() {
                                showSuccess(currentButton);
                            }).catch(function() {
                                showError(currentButton);
                            });
                        } else {
                            // Fallback
                            try {
                                const textArea = document.createElement('textarea');
                                textArea.value = textToCopy;
                                textArea.style.position = 'fixed';
                                textArea.style.left = '-9999px';
                                textArea.style.top = '-9999px';
                                document.body.appendChild(textArea);
                                textArea.select();
                                document.execCommand('copy');
                                document.body.removeChild(textArea);
                                showSuccess(currentButton);
                            } catch (err) {
                                showError(currentButton);
                            }
                        }
                    });
                });
            }
            
            function showSuccess(button) {
                button.classList.add('copied');
                button.querySelector('.copy-icon').classList.add('hidden');
                button.querySelector('.check-icon').classList.remove('hidden');
                button.querySelector('.btn-text').textContent = 'Скопировано! ✓';
                
                // Эффект конфетти
                createConfetti(button);
                
                setTimeout(function() {
                    button.classList.remove('copied');
                    button.querySelector('.copy-icon').classList.remove('hidden');
                    button.querySelector('.check-icon').classList.add('hidden');
                    button.querySelector('.btn-text').textContent = 'Копировать';
                }, 2500);
            }
            
            function showError(button) {
                button.querySelector('.btn-text').textContent = 'Ошибка!';
                setTimeout(function() {
                    button.querySelector('.btn-text').textContent = 'Копировать';
                }, 2000);
            }
            
            function createConfetti(button) {
                const colors = ['#ff6b6b', '#ffd93d', '#6bcb77', '#4d96ff', '#ff6bcb', '#ffb347'];
                const container = button.parentElement;
                
                for (let i = 0; i < 20; i++) {
                    const confetti = document.createElement('div');
                    confetti.className = 'confetti-piece';
                    confetti.style.cssText = `
                        position: absolute;
                        width: ${Math.random() * 8 + 4}px;
                        height: ${Math.random() * 8 + 4}px;
                        background: ${colors[Math.floor(Math.random() * colors.length)]};
                        left: ${Math.random() * 100}%;
                        top: 50%;
                        border-radius: ${Math.random() > 0.5 ? '50%' : '2px'};
                        transform: rotate(${Math.random() * 360}deg);
                        pointer-events: none;
                        z-index: 1000;
                        animation: confettiFall ${Math.random() * 0.8 + 0.7}s ease-out forwards;
                        animation-delay: ${Math.random() * 0.1}s;
                    `;
                    container.appendChild(confetti);
                    
                    setTimeout(() => {
                        confetti.remove();
                    }, 1500);
                }
            }
            
            // Инициализация
            initCopyButtons();
            
            // MutationObserver для динамического контента
            if (window.MutationObserver) {
                let timeout;
                const observer = new MutationObserver(function(mutations) {
                    clearTimeout(timeout);
                    timeout = setTimeout(() => {
                        initCopyButtons();
                    }, 300);
                });
                
                observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });
            }
        });
        </script>
        <?php
    }
}

new CopyQuoteButton();

Файл 2: style.css

🎨 CSS
/* style.css - Эффектная кнопка копирования */

/* Контейнер кнопки */
.copy-quote-button-container {
    position: relative;
    display: flex;
    justify-content: flex-end;
    margin-top: 20px;
    margin-bottom: 25px;
    width: 100%;
}

/* Основная кнопка */
.copy-quote-button {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 12px 28px;
    background: linear-gradient(135deg, #007cba 0%, #005a8c 50%, #003d66 100%);
    color: #ffffff;
    border: none;
    border-radius: 50px;
    font-size: 15px;
    font-weight: 600;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    cursor: pointer;
    transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    box-shadow: 
        0 4px 15px rgba(0, 124, 186, 0.3),
        inset 0 1px 0 rgba(255, 255, 255, 0.2);
    line-height: 1.2;
    letter-spacing: 0.5px;
    overflow: hidden;
    min-height: 50px;
    min-width: 140px;
    transform-origin: center;
}

/* Бликовый эффект */
.copy-quote-button .btn-shine {
    position: absolute;
    top: -50%;
    left: -50%;
    width: 200%;
    height: 200%;
    background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.15) 0%, transparent 60%);
    pointer-events: none;
    transition: all 0.6s ease;
}

.copy-quote-button:hover .btn-shine {
    transform: translate(10%, 10%);
    background: radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.2) 0%, transparent 60%);
}

/* Содержимое кнопки */
.copy-quote-button .btn-content {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    position: relative;
    z-index: 1;
}

.copy-quote-button .btn-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
}

.copy-quote-button .btn-text {
    transition: all 0.3s ease;
    white-space: nowrap;
}

/* Иконки */
.copy-quote-button .copy-icon,
.copy-quote-button .check-icon {
    flex-shrink: 0;
    stroke: currentColor;
    transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.copy-quote-button .hidden {
    display: none !important;
}

/* Эффекты при наведении */
.copy-quote-button:hover {
    transform: translateY(-3px) scale(1.02);
    background: linear-gradient(135deg, #0088d4 0%, #006699 50%, #004a7a 100%);
    box-shadow: 
        0 8px 30px rgba(0, 124, 186, 0.4),
        inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.copy-quote-button:hover .copy-icon {
    transform: rotate(-5deg) scale(1.1);
}

/* Эффект нажатия */
.copy-quote-button.pressed {
    transform: scale(0.95) !important;
    transition: transform 0.1s ease !important;
}

/* Состояние "Скопировано" */
.copy-quote-button.copied {
    background: linear-gradient(135deg, #28a745 0%, #1e7e34 50%, #146c28 100%);
    box-shadow: 
        0 4px 20px rgba(40, 167, 69, 0.4),
        inset 0 1px 0 rgba(255, 255, 255, 0.25);
    animation: successPulse 0.6s ease;
}

.copy-quote-button.copied:hover {
    background: linear-gradient(135deg, #34ce57 0%, #28a745 50%, #1e7e34 100%);
    box-shadow: 
        0 8px 35px rgba(40, 167, 69, 0.5),
        inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

.copy-quote-button.copied .check-icon {
    animation: checkPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Анимации */
@keyframes checkPop {
    0% {
        transform: scale(0) rotate(-30deg);
        opacity: 0;
    }
    60% {
        transform: scale(1.3) rotate(5deg);
        opacity: 1;
    }
    100% {
        transform: scale(1) rotate(0deg);
        opacity: 1;
    }
}

@keyframes successPulse {
    0% {
        transform: scale(1);
    }
    30% {
        transform: scale(1.05);
    }
    60% {
        transform: scale(0.98);
    }
    100% {
        transform: scale(1);
    }
}

@keyframes confettiFall {
    0% {
        opacity: 1;
        transform: translateY(0) rotate(0deg) scale(1);
    }
    100% {
        opacity: 0;
        transform: translateY(80px) rotate(720deg) scale(0);
    }
}

/* Поддержка светлой/темной темы */
@media (prefers-color-scheme: dark) {
    .copy-quote-button {
        background: linear-gradient(135deg, #0a7fc9 0%, #0866a3 50%, #064a7a 100%);
        box-shadow: 
            0 4px 20px rgba(10, 127, 201, 0.4),
            inset 0 1px 0 rgba(255, 255, 255, 0.15);
    }
    
    .copy-quote-button:hover {
        background: linear-gradient(135deg, #1a8fd9 0%, #0a7fc9 50%, #0866a3 100%);
        box-shadow: 
            0 8px 35px rgba(10, 127, 201, 0.5),
            inset 0 1px 0 rgba(255, 255, 255, 0.2);
    }
}

/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
    .copy-quote-button {
        padding: 10px 20px;
        font-size: 14px;
        width: 100%;
        justify-content: center;
        min-height: 44px;
        min-width: unset;
        border-radius: 40px;
    }
    
    .copy-quote-button .btn-content {
        gap: 8px;
    }
    
    .copy-quote-button-container {
        margin-top: 15px;
        margin-bottom: 20px;
        padding: 0 5px;
    }
}

@media (max-width: 480px) {
    .copy-quote-button {
        padding: 12px 16px;
        font-size: 13px;
        min-height: 40px;
        border-radius: 35px;
    }
    
    .copy-quote-button .copy-icon,
    .copy-quote-button .check-icon {
        width: 16px;
        height: 16px;
    }
}

/* Для людей с vestibular disorders */
@media (prefers-reduced-motion: reduce) {
    .copy-quote-button,
    .copy-quote-button * {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
    
    .copy-quote-button .btn-shine {
        display: none;
    }
}

/* Фокус для доступности */
.copy-quote-button:focus-visible {
    outline: 3px solid #007cba;
    outline-offset: 3px;
}

/* Дополнительный эффект - градиентная граница */
.copy-quote-button::before {
    content: '';
    position: absolute;
    inset: -2px;
    background: linear-gradient(135deg, #007cba, #00d4ff, #007cba);
    border-radius: 52px;
    z-index: -1;
    opacity: 0;
    transition: opacity 0.4s ease;
}

.copy-quote-button:hover::before {
    opacity: 1;
    animation: borderRotate 3s linear infinite;
}

@keyframes borderRotate {
    0% {
        background: linear-gradient(135deg, #007cba, #00d4ff, #007cba);
    }
    50% {
        background: linear-gradient(225deg, #007cba, #00d4ff, #007cba);
    }
    100% {
        background: linear-gradient(135deg, #007cba, #00d4ff, #007cba);
    }
}

Файл 3: script.js

⚡ JS
// script.js - Дополнительная логика для кнопки
(function() {
    'use strict';
    
    // Все основные функции уже встроены в PHP файл
    // Этот файл оставлен для будущих расширений
    
    console.log('Кнопка копирования цитат активирована!');
    
})();

Особенности обновленной версии
Улучшенная стилизация:

Что было исправлено и улучшено:
1. Исправление двойной кнопки:
Добавлен флаг data-copy-button-added для каждой цитаты

Проверка существующего контейнера перед добавлением

self::$initialized для предотвращения множественной инициализации

2. Эффектный дизайн:
Градиентный фон с плавным переходом

Бликовый эффект при наведении (сдвигающийся свет)

Анимированная граница с вращающимся градиентом

3D-эффект при наведении (подъем + масштаб)

Эффект нажатия (сжатие кнопки)

Пульсация при успешном копировании

3. Эффект "Конфетти":
При успешном копировании из кнопки вылетают разноцветные частицы

Анимация падения с вращением

Случайные цвета и размеры

4. Улучшенная анимация:
Иконка галочки появляется с эффектом "поп"

Плавные переходы всех элементов

Кастомная кривая Безье для более живых анимаций

5. Дополнительные улучшения:
Поддержка темной темы

Улучшенная адаптивность на мобильных

ARIA-атрибуты для доступности

Отключение анимаций для пользователей с vestibular disorders

Теперь кнопка будет выглядеть премиально, без дублирования, с красивыми эффектами!

Скачать плагин в архиве, мжно установить на сайте через установку плагинов: Кнопка копирования цитат