// ==UserScript==
// @name Gartic.io Pro Drawing Assist (Sparkle UI + Grid + Guides + DnD)
// @namespace http://tampermonkey.net/
// @version 5.0
// @description Reference overlay + grid + shape guides + color helper for Gartic.io. URL/file/drag-n-drop, drag/scale/rotate/opacity/lock/reset/clear, glittery UI, and hotkeys.
// @match *://gartic.io/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// ---------- Styles ----------
const css = `
:root {
--da-bg: rgba(18,18,30,.96);
--da-panel-w: 280px;
--da-accent1:#ff6eb4;
--da-accent2:#ff9bd3;
--da-text:#fff;
}
#da-panel, #da-color {
position: fixed; top: 20px; left: 20px; z-index: 999999; width: var(--da-panel-w);
background: var(--da-bg); color: var(--da-text); border-radius: 14px; padding: 12px;
box-shadow: 0 10px 30px rgba(255, 150, 200, 0.35); font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
user-select: none; overflow: hidden;
}
#da-color { top: 20px; left: calc(20px + var(--da-panel-w) + 12px); }
/* Sparkles only inside panels */
#da-panel::before, #da-color::before{
content: ""; position: absolute; inset: -50% -50%;
background:
radial-gradient(2px 2px at 20% 30%, #ffffffcc, transparent 40%),
radial-gradient(2px 2px at 80% 70%, #ff69b4cc, transparent 40%),
radial-gradient(2px 2px at 50% 50%, #ffd700cc, transparent 40%);
animation: da-spark 4s linear infinite; pointer-events: none;
}
@keyframes da-spark { from{transform:translate(0,0)} to{transform:translate(-35%,-35%)} }
h3{margin:0 0 8px 0; text-align:center; font-size:16px; color:#ffd1e9}
.da-row{margin-top:8px}
input[type="text"], input[type="file"], input[type="number"]{
width:100%; background:#1f1f2b; color:#fff; border:1px solid #3a3a4d; border-radius:8px; padding:6px 8px; font-size:13px; box-sizing:border-box; outline:none;
}
input[type="text"]:focus{ border-color:#ff7abf; box-shadow:0 0 0 3px rgba(255,122,191,.2) }
.da-label{font-size:12px;opacity:.9;display:flex;justify-content:space-between}
input[type="range"]{ -webkit-appearance:none; appearance:none; width:100%; height:6px; border-radius:999px; background:linear-gradient(90deg,#ff7abf,#ffd1e9); outline:none; margin-top:4px}
input[type="range"]::-webkit-slider-thumb{ -webkit-appearance:none; width:16px;height:16px;border-radius:50%;background:#fff;border:2px solid #ff7abf; box-shadow:0 0 10px rgba(255,122,191,.7); cursor:pointer}
.da-flex{display:flex; gap:8px}
.da-col{flex:1}
.da-btn{
width:100%; padding:9px 10px; border:none; border-radius:10px; font-weight:700; font-size:14px; color:#fff; cursor:pointer; margin-top:8px;
background:linear-gradient(120deg,var(--da-accent1),var(--da-accent2),var(--da-accent1)); background-size:220% 220%;
box-shadow:0 8px 20px rgba(255,110,180,.35); transition:transform .15s ease, box-shadow .2s ease, opacity .2s ease; position:relative; overflow:hidden;
}
.da-btn:hover{ transform:translateY(-1px); box-shadow:0 12px 26px rgba(255,110,180,.5) }
.da-btn::after{ content:""; position:absolute; top:0; left:-150%; width:120%; height:100%;
background:linear-gradient(120deg,transparent 0%,rgba(255,255,255,.35) 50%,transparent 100%); transform:skewX(-20deg); animation:da-glitter 3s linear infinite}
@keyframes da-glitter { 0%{left:-150%} 60%{left:110%} 100%{left:110%} }
.gray{ background-image:linear-gradient(120deg,#607d8b,#8aa2ad,#607d8b) }
.warn{ background-image:linear-gradient(120deg,#ff9800,#ffc36a,#ff9800) }
.danger{ background-image:linear-gradient(120deg,#e53935,#ff6b66,#e53935) }
#da-show{
position:fixed; top:20px; left:20px; z-index:1000000; display:none; padding:8px 12px; border-radius:10px; border:none; font-weight:800; color:#fff; cursor:pointer;
background:linear-gradient(120deg,#2196f3,#64b5f6,#2196f3); background-size:220% 220%; box-shadow:0 8px 20px rgba(33,150,243,.35)
}
#da-overlay{
position:absolute; top:100px; left:100px; width:380px; height:auto; opacity:.7; transform-origin:center center; pointer-events:auto; z-index:999998;
user-select:none; -webkit-user-drag:none; cursor:grab;
}
/* Grid canvas overlay (over content, not blocking clicks) */
#da-grid{
position:fixed; inset:0; pointer-events:none; z-index:999997; opacity:.4; display:none;
}
/* Guides */
.da-guide{ position:absolute; border:2px dashed rgba(255,206,230,.9); z-index:999999; cursor:move; box-shadow:0 0 10px rgba(255,110,180,.35) inset }
.da-circle{ border-radius:50% }
.da-handle{ position:absolute; width:10px;height:10px;background:#ffd1e9;border:2px solid #ff7abf;border-radius:50%; z-index:9999999; cursor:nwse-resize }
.da-handle.br{ right:-7px; bottom:-7px }
.da-handle.tl{ left:-7px; top:-7px }
/* Color panel */
#da-color .swatch{ display:flex; gap:8px; flex-wrap:wrap; margin-top:8px }
#da-color .swatch button{ width:22px;height:22px;border-radius:6px;border:none;cursor:pointer; box-shadow:0 2px 8px rgba(0,0,0,.25) }
#da-color .mini{ display:flex; gap:8px; margin-top:8px }
#da-color input[type="color"]{ width:100%; height:40px; border:none; border-radius:8px; padding:0 }
#da-copy{ margin-top:8px }
.da-help{ font-size:11px; opacity:.85; margin-top:8px; line-height:1.35 }
`;
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
// ---------- Main Panel ----------
const panel = document.createElement('div');
panel.id = 'da-panel';
panel.innerHTML = `
<h3>✨ Drawing Assist</h3>
<div class="da-row"><input type="text" id="da-url" placeholder="Paste image URL & press Enter"></div>
<div class="da-row"><input type="file" id="da-file" accept="image/*"></div>
<div class="da-row"><div class="da-label"><span>Opacity</span><span id="da-op-val">0.70</span></div>
<input type="range" id="da-op" min="0" max="1" step="0.01" value="0.70"></div>
<div class="da-row"><div class="da-label"><span>Scale</span><span id="da-sc-val">1.00×</span></div>
<input type="range" id="da-sc" min="0.10" max="3.00" step="0.05" value="1.00"></div>
<div class="da-row"><div class="da-label"><span>Rotate</span><span id="da-rt-val">0°</span></div>
<input type="range" id="da-rt" min="0" max="360" step="1" value="0"></div>
<div class="da-row"><label style="font-size:12px; display:flex; align-items:center; gap:8px;">
<input type="checkbox" id="da-lock"> Lock image position</label></div>
<div class="da-flex">
<button class="da-btn gray" id="da-grid-toggle">Grid: Off</button>
<button class="da-btn gray" id="da-grid-settings">⚙</button>
</div>
<div class="da-flex">
<button class="da-btn" id="da-circle-toggle">Circle Guide</button>
<button class="da-btn" id="da-rect-toggle">Rect Guide</button>
</div>
<button class="da-btn danger" id="da-clear">Clear Image</button>
<button class="da-btn warn" id="da-reset">Reset Transform/Pos</button>
<button class="da-btn gray" id="da-hide">Hide UI</button>
<div class="da-help">
<b>Hotkeys:</b> H (UI), G (grid), C (circle), V (rect), Arrows (move), +/- (scale), , . (rotate), [ ] (opacity), R (reset), Del (clear). Shift = bigger steps. Drag & drop an image onto the page to load it.
</div>
`;
document.body.appendChild(panel);
// ---------- Color Helper Panel ----------
const colorPanel = document.createElement('div');
colorPanel.id = 'da-color';
colorPanel.innerHTML = `
<h3>🎨 Color Helper</h3>
<div class="mini">
<input type="color" id="da-color-input" value="#ff6eb4">
<input type="text" id="da-color-hex" value="#ff6eb4">
</div>
<div class="swatch" id="da-swatch"></div>
<button class="da-btn" id="da-copy">Copy HEX</button>
<div class="da-help">Tip: pick here, then click game color closest to this value.</div>
`;
document.body.appendChild(colorPanel);
// ---------- Floating Show Button ----------
const showBtn = document.createElement('button');
showBtn.id = 'da-show';
showBtn.textContent = 'Show UI';
document.body.appendChild(showBtn);
// ---------- Overlay Image ----------
const overlay = document.createElement('img');
overlay.id = 'da-overlay';
overlay.alt = '';
document.body.appendChild(overlay);
// ---------- Grid Canvas ----------
const grid = document.createElement('canvas');
grid.id = 'da-grid';
document.body.appendChild(grid);
// ---------- Guides (rectangle & circle) ----------
function createGuide(className, defaultW=150, defaultH=150){
const g = document.createElement('div');
g.className = `da-guide ${className}`;
g.style.left = '120px'; g.style.top = '120px';
g.style.width = `${defaultW}px`; g.style.height = `${defaultH}px`;
const tl = document.createElement('div'); tl.className='da-handle tl';
const br = document.createElement('div'); br.className='da-handle br';
g.appendChild(tl); g.appendChild(br);
document.body.appendChild(g);
// drag
let dragging=false, offX=0, offY=0;
g.addEventListener('mousedown', (e)=>{
if (e.target.classList.contains('da-handle')) return;
dragging=true;
const r=g.getBoundingClientRect();
offX=e.clientX - r.left; offY=e.clientY - r.top;
e.preventDefault();
});
document.addEventListener('mousemove', (e)=>{
if(!dragging) return;
g.style.left = `${e.clientX - offX + window.scrollX}px`;
g.style.top = `${e.clientY - offY + window.scrollY}px`;
});
document.addEventListener('mouseup', ()=> dragging=false);
// resize (tl & br)
let resizing=null, startX=0, startY=0, startW=0, startH=0, startL=0, startT=0;
function startResize(which,e){
resizing=which;
const r=g.getBoundingClientRect();
startX=e.clientX; startY=e.clientY;
startW=r.width; startH=r.height; startL=r.left + window.scrollX; startT=r.top + window.scrollY;
e.preventDefault();
e.stopPropagation();
}
tl.addEventListener('mousedown', e=> startResize('tl',e));
br.addEventListener('mousedown', e=> startResize('br',e));
document.addEventListener('mousemove', (e)=>{
if(!resizing) return;
if(resizing==='br'){
const w=Math.max(20, startW + (e.clientX - startX));
const h=Math.max(20, startH + (e.clientY - startY));
g.style.width = `${w}px`; g.style.height = `${h}px`;
}else{
const dx = e.clientX - startX, dy = e.clientY - startY;
const w=Math.max(20, startW - dx);
const h=Math.max(20, startH - dy);
g.style.width = `${w}px`; g.style.height = `${h}px`;
g.style.left = `${startL + dx}px`; g.style.top = `${startT + dy}px`;
}
});
document.addEventListener('mouseup', ()=> resizing=null);
g.style.display = 'none';
return g;
}
const circleGuide = createGuide('da-circle', 160,160);
const rectGuide = createGuide('da-rect', 200,120);
// ---------- State ----------
let scale = 1, rotation = 0, locked = false;
const qs = s => document.querySelector(s);
const urlInput = qs('#da-url'), fileInput = qs('#da-file');
const opRange = qs('#da-op'), scRange = qs('#da-sc'), rtRange = qs('#da-rt');
const opVal = qs('#da-op-val'), scVal = qs('#da-sc-val'), rtVal = qs('#da-rt-val');
const clearBtn = qs('#da-clear'), resetBtn = qs('#da-reset'), hideBtn = qs('#da-hide');
const lockChk = qs('#da-lock');
const gridToggle = qs('#da-grid-toggle'), gridSettings = qs('#da-grid-settings');
function applyTransform(){ overlay.style.transform = `scale(${scale}) rotate(${rotation}deg)`; }
function clamp(n,min,max){ return Math.max(min, Math.min(max, n)); }
function typing(el){ if(!el) return false; const t=(el.tagName||'').toLowerCase(); const type=(el.type||'').toLowerCase(); return t==='input'||t==='textarea'||el.isContentEditable||type==='text'||type==='search'; }
// ---------- Overlay loading ----------
urlInput.addEventListener('keydown', e=>{ if(e.key==='Enter'){ overlay.src = urlInput.value.trim(); }});
fileInput.addEventListener('change', e=>{
const f=e.target.files?.[0]; if(!f) return;
const rd=new FileReader(); rd.onload = ev => overlay.src = ev.target.result; rd.readAsDataURL(f);
});
// Drag & Drop anywhere
window.addEventListener('dragover', e=>{ e.preventDefault(); });
window.addEventListener('drop', e=>{
e.preventDefault();
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const f = e.dataTransfer.files[0];
if (f.type.startsWith('image/')) {
const rd=new FileReader(); rd.onload = ev=> overlay.src=ev.target.result; rd.readAsDataURL(f);
}
} else {
const text = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain');
if (text && /^https?:\/\//i.test(text)) overlay.src = text.trim();
}
});
// ---------- Controls ----------
opRange.addEventListener('input', ()=>{ overlay.style.opacity = opRange.value; opVal.textContent = Number(opRange.value).toFixed(2); });
scRange.addEventListener('input', ()=>{ scale = Number(scRange.value); scVal.textContent=`${scale.toFixed(2)}×`; applyTransform(); });
rtRange.addEventListener('input', ()=>{ rotation = Number(rtRange.value); rtVal.textContent=`${rotation}°`; applyTransform(); });
lockChk.addEventListener('change', ()=>{ locked = lockChk.checked; overlay.style.cursor = locked ? 'default' : 'grab'; });
clearBtn.addEventListener('click', ()=> overlay.src='');
resetBtn.addEventListener('click', ()=>{
overlay.style.top='100px'; overlay.style.left='100px';
scale=1; rotation=0; scRange.value='1.00'; rtRange.value='0'; scVal.textContent='1.00×'; rtVal.textContent='0°';
opRange.value='0.70'; overlay.style.opacity='0.70'; opVal.textContent='0.70'; applyTransform();
});
hideBtn.addEventListener('click', ()=>{ panel.style.display='none'; colorPanel.style.display='none'; showBtn.style.display='block'; });
showBtn.addEventListener('click', ()=>{ panel.style.display='block'; colorPanel.style.display='block'; showBtn.style.display='none'; });
// ---------- Drag overlay (no jump) ----------
let dragging=false, offX=0, offY=0;
overlay.addEventListener('mousedown', (e)=>{
if(locked) return;
dragging=true; overlay.style.cursor='grabbing';
const r=overlay.getBoundingClientRect();
offX=e.clientX - r.left; offY=e.clientY - r.top; e.preventDefault();
});
document.addEventListener('mousemove', (e)=>{
if(!dragging) return;
overlay.style.left = `${e.clientX - offX + window.scrollX}px`;
overlay.style.top = `${e.clientY - offY + window.scrollY}px`;
});
document.addEventListener('mouseup', ()=>{ dragging=false; if(!locked) overlay.style.cursor='grab'; });
// ---------- Quick resize (Shift + Wheel) ----------
document.addEventListener('wheel', (e)=>{
if(!overlay.src) return;
if(e.shiftKey){
e.preventDefault();
const step = e.deltaY<0 ? 0.05 : -0.05;
scale = clamp(scale + step, 0.10, 3.00);
scRange.value = scale.toFixed(2); scVal.textContent = `${scale.toFixed(2)}×`; applyTransform();
}
}, {passive:false});
// ---------- Grid ----------
let gridOn=false, gridSpacing=40, gridOpacity=0.4;
function drawGrid(){
grid.width = window.innerWidth; grid.height = window.innerHeight;
const ctx = grid.getContext('2d');
ctx.clearRect(0,0,grid.width,grid.height);
ctx.globalAlpha = gridOpacity;
ctx.strokeStyle = '#ffc2e6';
ctx.lineWidth = 1;
for(let x=0; x<grid.width; x+=gridSpacing){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,grid.height); ctx.stroke(); }
for(let y=0; y<grid.height; y+=gridSpacing){ ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(grid.width,y); ctx.stroke(); }
}
function toggleGrid(force){
gridOn = (typeof force==='boolean') ? force : !gridOn;
grid.style.display = gridOn ? 'block' : 'none';
gridToggle.textContent = `Grid: ${gridOn?'On':'Off'}`;
if (gridOn){ drawGrid(); }
}
window.addEventListener('resize', ()=>{ if(gridOn) drawGrid(); });
// Grid settings prompt (simple)
gridSettings.addEventListener('click', ()=>{
const sp = prompt('Grid spacing (px):', String(gridSpacing));
if(sp!==null){
const n = Math.max(5, Math.min(400, parseInt(sp,10)||gridSpacing));
gridSpacing = n;
}
const op = prompt('Grid opacity (0-1):', String(gridOpacity));
if(op!==null){
const f = Math.max(0, Math.min(1, parseFloat(op)||gridOpacity));
gridOpacity = f;
}
if(gridOn) drawGrid();
});
gridToggle.addEventListener('click', ()=> toggleGrid());
// ---------- Guides toggles ----------
function toggleEl(el){ el.style.display = (el.style.display==='none' || !el.style.display) ? 'block' : 'none'; }
qs('#da-circle-toggle').addEventListener('click', ()=> toggleEl(circleGuide));
qs('#da-rect-toggle').addEventListener('click', ()=> toggleEl(rectGuide));
// ---------- Color helper ----------
const colorInput = qs('#da-color-input');
const colorHex = qs('#da-color-hex');
const copyBtn = qs('#da-copy');
const swatch = qs('#da-swatch');
const presets = ['#000000','#ffffff','#ff6eb4','#ff9bd3','#ff9800','#ffd700','#00bcd4','#4caf50','#9c27b0','#e91e63'];
presets.forEach(hex=>{
const b=document.createElement('button'); b.style.background=hex; b.title=hex;
b.addEventListener('click', ()=>{ colorInput.value=hex; colorHex.value=hex; });
swatch.appendChild(b);
});
colorInput.addEventListener('input', ()=> colorHex.value = colorInput.value);
colorHex.addEventListener('input', ()=> {
let v=colorHex.value.trim();
if(!v.startsWith('#')) v='#'+v;
if(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(v)) colorInput.value=v;
});
copyBtn.addEventListener('click', ()=>{
const v = colorHex.value.trim();
navigator.clipboard?.writeText(v).then(()=>{ copyBtn.textContent='Copied!'; setTimeout(()=>copyBtn.textContent='Copy HEX',800); });
});
// ---------- Keyboard Hotkeys ----------
document.addEventListener('keydown', (e)=>{
if(typing(e.target)) return;
const move = e.shiftKey? 20:5, stepScale = e.shiftKey? .10:.05, stepRot = e.shiftKey? 10:2, stepOp = e.shiftKey? .10:.05;
switch(e.key){
case 'h': case 'H':
if (panel.style.display==='none'){ panel.style.display='block'; colorPanel.style.display='block'; showBtn.style.display='none'; }
else { panel.style.display='none'; colorPanel.style.display='none'; showBtn.style.display='block'; }
break;
case 'g': case 'G': toggleGrid(); break;
case 'c': case 'C': toggleEl(circleGuide); break;
case 'v': case 'V': toggleEl(rectGuide); break;
case 'ArrowLeft': e.preventDefault(); overlay.style.left = `${(parseFloat(overlay.style.left)||100)-move}px`; break;
case 'ArrowRight': e.preventDefault(); overlay.style.left = `${(parseFloat(overlay.style.left)||100)+move}px`; break;
case 'ArrowUp': e.preventDefault(); overlay.style.top = `${(parseFloat(overlay.style.top )||100)-move}px`; break;
case 'ArrowDown': e.preventDefault(); overlay.style.top = `${(parseFloat(overlay.style.top )||100)+move}px`; break;
case '+': case '=':
if(!overlay.src) break;
scale = clamp(scale + stepScale, .10, 3.00); scRange.value=scale.toFixed(2); scVal.textContent=`${scale.toFixed(2)}×`; applyTransform(); break;
case '-':
if(!overlay.src) break;
scale = clamp(scale - stepScale, .10, 3.00); scRange.value=scale.toFixed(2); scVal.textContent=`${scale.toFixed(2)}×`; applyTransform(); break;
case ',': // rotate left
if(!overlay.src) break;
rotation = (rotation - stepRot + 360)%360; rtRange.value=rotation; rtVal.textContent=`${rotation}°`; applyTransform(); break;
case '.': // rotate right
if(!overlay.src) break;
rotation = (rotation + stepRot)%360; rtRange.value=rotation; rtVal.textContent=`${rotation}°`; applyTransform(); break;
case '[': // opacity down
if(!overlay.src) break;
opRange.value = clamp(parseFloat(opRange.value)-stepOp,0,1).toFixed(2); overlay.style.opacity=opRange.value; opVal.textContent=opRange.value; break;
case ']': // opacity up
if(!overlay.src) break;
opRange.value = clamp(parseFloat(opRange.value)+stepOp,0,1).toFixed(2); overlay.style.opacity=opRange.value; opVal.textContent=opRange.value; break;
case 'r': case 'R':
overlay.style.top='100px'; overlay.style.left='100px'; scale=1; rotation=0;
scRange.value='1.00'; rtRange.value='0'; scVal.textContent='1.00×'; rtVal.textContent='0°';
opRange.value='0.70'; overlay.style.opacity='0.70'; opVal.textContent='0.70'; applyTransform(); break;
case 'Delete': overlay.src=''; break;
}
});
})();