Drawaria – Menú de Colores (Arcoíris)

Agrega un botón flotante 🎮 que abre un menú con una sección "Colores" (incluye modo Arcoíris) para dibujar en drawaria.online. El color se fuerza a nivel de canvas para que funcione aunque la página cambie su UI.

// ==UserScript==
// @name         Drawaria – Menú de Colores (Arcoíris)
// @namespace    https://example.com/userscripts
// @version      1.0.0
// @description  Agrega un botón flotante 🎮 que abre un menú con una sección "Colores" (incluye modo Arcoíris) para dibujar en drawaria.online. El color se fuerza a nivel de canvas para que funcione aunque la página cambie su UI.
// @match        https://drawaria.online/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  /*** ---------------------------------------------
   *  ColorManager — Fuente de la verdad del color
   *  ---------------------------------------------
   */
  const ColorManager = {
    enabled: true,
    mode: localStorage.getItem('rainbow_menu_mode') || 'solid', // 'solid' | 'rainbow'
    color: localStorage.getItem('rainbow_menu_color') || '#ff007a',
    hue: parseInt(localStorage.getItem('rainbow_menu_hue') || '0', 10),
    getCurrentColor() {
      if (!this.enabled) return null;
      if (this.mode === 'rainbow') {
        const h = this.hue % 360;
        this.hue = (this.hue + 4) % 360; // velocidad del arcoíris
        localStorage.setItem('rainbow_menu_hue', String(this.hue));
        return `hsl(${h} 100% 50%)`;
      }
      return this.color;
    },
    setSolid(hex) {
      this.mode = 'solid';
      this.color = hex;
      localStorage.setItem('rainbow_menu_mode', 'solid');
      localStorage.setItem('rainbow_menu_color', hex);
    },
    setRainbow() {
      this.mode = 'rainbow';
      localStorage.setItem('rainbow_menu_mode', 'rainbow');
    }
  };

  /*** ---------------------------------------------
   *  Parcheo de CanvasRenderingContext2D
   *  ---------------------------------------------
   *  En vez de tocar la UI del juego, forzamos el color
   *  a nivel de canvas. Esto hace el script resistente
   *  a cambios en el DOM de drawaria.
   */
  function patchCtx(ctx) {
    if (!ctx || ctx.__rainbowPatched) return;

    const applyColor = () => {
      const c = ColorManager.getCurrentColor();
      if (c) {
        // Forzamos tanto trazo como relleno
        try { ctx.strokeStyle = c; } catch (e) {}
        try { ctx.fillStyle = c; } catch (e) {}
      }
    };

    const wrap = (obj, key, before) => {
      const orig = obj[key];
      if (typeof orig !== 'function') return;
      obj[key] = function (...args) {
        try { before && before(this, args); } catch (e) { /* noop */ }
        return orig.apply(this, args);
      };
    };

    // Cambiamos color en trazos y también mientras se dibujan segmentos
    wrap(ctx, 'stroke', () => applyColor());
    wrap(ctx, 'fill', () => applyColor());
    wrap(ctx, 'lineTo', () => applyColor());
    wrap(ctx, 'bezierCurveTo', () => applyColor());
    wrap(ctx, 'quadraticCurveTo', () => applyColor());
    wrap(ctx, 'arc', () => applyColor());

    ctx.__rainbowPatched = true;
  }

  // Parchear todos los contextos 2D existentes y futuros
  (function patchAllCanvases() {
    const origGetContext = HTMLCanvasElement.prototype.getContext;
    HTMLCanvasElement.prototype.getContext = function (type, ...rest) {
      const ctx = origGetContext.call(this, type, ...rest);
      if (type === '2d' && ctx) patchCtx(ctx);
      return ctx;
    };
    // Canvases ya presentes
    for (const cv of document.querySelectorAll('canvas')) {
      try { const c = cv.getContext('2d'); if (c) patchCtx(c); } catch (e) {}
    }
  })();

  /*** ---------------------------------------------
   *  UI — Botón 🎮 y Panel con "Colores"
   *  ---------------------------------------------
   */
  const style = document.createElement('style');
  style.textContent = `
    .rbw-floating-btn { position: fixed; right: 16px; bottom: 16px; z-index: 999999; width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer; font-size: 24px; box-shadow: 0 8px 24px rgba(0,0,0,.25); background: linear-gradient(135deg, #ff007a, #7a00ff); color: white; display:flex; align-items:center; justify-content:center; }
    .rbw-floating-btn:hover { transform: translateY(-1px); }

    .rbw-panel { position: fixed; right: 16px; bottom: 84px; width: 300px; max-width: calc(100vw - 32px); background: #0d0d10; color: white; border-radius: 16px; box-shadow: 0 16px 48px rgba(0,0,0,.35); overflow: hidden; z-index: 999998; display: none; }
    .rbw-panel.open { display: block; animation: rbw-pop .16s ease-out; }
    @keyframes rbw-pop { from { transform: translateY(6px); opacity: .6; } to { transform: translateY(0); opacity: 1; } }

    .rbw-header { display:flex; align-items:center; justify-content:space-between; padding: 10px 12px; background: linear-gradient(90deg, red, orange, yellow, green, cyan, blue, violet); color: #111; font-weight: 800; }
    .rbw-close { background: transparent; border: none; font-size: 18px; line-height: 1; cursor: pointer; padding: 6px; color: #111; }

    .rbw-body { padding: 10px 12px 14px; }
    .rbw-section { margin-top: 8px; border: 1px solid #2a2a33; border-radius: 12px; overflow: hidden; }

    .rbw-section-head { display:flex; align-items:center; justify-content:space-between; background:#15151b; padding: 8px 10px; cursor:pointer; }
    .rbw-section-title { font-weight: 700; }
    .rbw-toggle { border: none; background: #242432; color: #d9d9e3; border-radius: 10px; padding: 6px 10px; cursor:pointer; }

    .rbw-section-body { padding: 10px; display:none; }
    .rbw-section.open .rbw-section-body { display:block; }

    .rbw-swatches { display:grid; grid-template-columns: repeat(8, 1fr); gap: 6px; }
    .rbw-swatch { width: 28px; height: 28px; border-radius: 6px; border: 2px solid rgba(255,255,255,.25); cursor:pointer; }
    .rbw-swatch[aria-selected="true"] { outline: 2px solid white; outline-offset: 2px; }

    .rbw-chip { display:inline-flex; align-items:center; gap:8px; background:#15151b; border:1px solid #2a2a33; border-radius:999px; padding:6px 10px; font-size:12px; }
    .rbw-chip .dot { width:14px; height:14px; border-radius:50%; border:1px solid rgba(255,255,255,.35); }
  `;
  document.head.appendChild(style);

  // Botón flotante 🎮
  const fab = document.createElement('button');
  fab.className = 'rbw-floating-btn';
  fab.title = 'Abrir menú de dibujo';
  fab.textContent = '🎮';

  // Panel
  const panel = document.createElement('div');
  panel.className = 'rbw-panel';
  panel.innerHTML = `
    <div class="rbw-header">
      <div>Menú de Dibujo</div>
      <button class="rbw-close" title="Cerrar">✕</button>
    </div>
    <div class="rbw-body">
      <div class="rbw-chip" id="rbw-status">
        <span class="dot"></span>
        <span>Color activo</span>
      </div>

      <div class="rbw-section" id="rbw-colores">
        <div class="rbw-section-head">
          <div class="rbw-section-title">Colores</div>
          <button class="rbw-toggle" type="button">Mostrar</button>
        </div>
        <div class="rbw-section-body">
          <div class="rbw-swatches" id="rbw-swatches"></div>
          <div style="margin-top:10px; display:flex; gap:8px; flex-wrap:wrap;">
            <button id="rbw-solid" class="rbw-toggle" type="button">Modo sólido</button>
            <button id="rbw-rainbow" class="rbw-toggle" type="button">Arcoíris</button>
            <input id="rbw-picker" type="color" style="margin-left:auto; background:#15151b; border:1px solid #2a2a33; border-radius:10px; width:44px; height:32px; padding:0;" title="Elegir color" />
          </div>
        </div>
      </div>
    </div>
  `;

  document.body.appendChild(fab);
  document.body.appendChild(panel);

  // Interacciones del panel
  const closeBtn = panel.querySelector('.rbw-close');
  const coloresSection = panel.querySelector('#rbw-colores');
  const coloresToggle = coloresSection.querySelector('.rbw-toggle');
  const swatchesBox = panel.querySelector('#rbw-swatches');
  const picker = panel.querySelector('#rbw-picker');
  const btnSolid = panel.querySelector('#rbw-solid');
  const btnRainbow = panel.querySelector('#rbw-rainbow');
  const statusDot = panel.querySelector('#rbw-status .dot');

  const preset = [
    '#000000', '#444444', '#7f7f7f', '#bfbfbf', '#ffffff',
    '#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#7f00ff', '#ff00ff',
    '#ff4d6d', '#ff8fa3', '#ffd166', '#06d6a0', '#118ab2', '#073b4c',
    '#f72585', '#b5179e', '#7209b7', '#3a0ca3', '#4361ee', '#4cc9f0',
  ];

  function updateStatus() {
    const c = ColorManager.mode === 'rainbow' ? `conic-gradient(from 0deg, red, orange, yellow, green, cyan, blue, violet, red)` : ColorManager.color;
    statusDot.style.background = c;
  }

  function makeSwatches() {
    swatchesBox.innerHTML = '';
    // Swatch especial: Arcoíris
    const rainbow = document.createElement('button');
    rainbow.className = 'rbw-swatch';
    rainbow.title = 'Arcoíris';
    rainbow.style.background = 'conic-gradient(from 0deg, red, orange, yellow, green, cyan, blue, violet, red)';
    rainbow.addEventListener('click', () => {
      ColorManager.setRainbow();
      updateStatus();
      markSelected(null);
    });
    swatchesBox.appendChild(rainbow);

    // Swatches de colores sólidos
    for (const hex of preset) {
      const b = document.createElement('button');
      b.className = 'rbw-swatch';
      b.style.background = hex;
      b.setAttribute('data-hex', hex);
      b.addEventListener('click', () => {
        ColorManager.setSolid(hex);
        picker.value = hex;
        updateStatus();
        markSelected(hex);
      });
      swatchesBox.appendChild(b);
    }
  }

  function markSelected(hex) {
    for (const el of swatchesBox.querySelectorAll('.rbw-swatch')) {
      const isSel = el.getAttribute('data-hex') === hex && ColorManager.mode === 'solid';
      el.setAttribute('aria-selected', isSel ? 'true' : 'false');
    }
  }

  // Eventos UI
  fab.addEventListener('click', () => panel.classList.toggle('open'));
  closeBtn.addEventListener('click', () => panel.classList.remove('open'));

  coloresToggle.addEventListener('click', () => {
    coloresSection.classList.toggle('open');
    coloresToggle.textContent = coloresSection.classList.contains('open') ? 'Ocultar' : 'Mostrar';
  });

  btnSolid.addEventListener('click', () => {
    if (ColorManager.mode !== 'solid') ColorManager.setSolid(ColorManager.color);
    updateStatus();
  });

  btnRainbow.addEventListener('click', () => {
    ColorManager.setRainbow();
    updateStatus();
    markSelected(null);
  });

  picker.addEventListener('input', (e) => {
    ColorManager.setSolid(e.target.value);
    updateStatus();
    markSelected(e.target.value.toLowerCase());
  });

  // Inicialización
  makeSwatches();
  updateStatus();
  markSelected(ColorManager.mode === 'solid' ? ColorManager.color.toLowerCase() : null);

  // Auto-abrir la sección de Colores la primera vez
  if (!localStorage.getItem('rainbow_menu_seen')) {
    panel.classList.add('open');
    coloresSection.classList.add('open');
    coloresToggle.textContent = 'Ocultar';
    localStorage.setItem('rainbow_menu_seen', '1');
  }
})();