Gartic Auto Draw Bot

Gartic.io için otomatik resim çizim botu. Görsel yükle, modu seç, otomatik çiz.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Gartic Auto Draw Bot
// @namespace    https://greasyfork.org/users/zerask
// @version      1.0.0
// @description  Gartic.io için otomatik resim çizim botu. Görsel yükle, modu seç, otomatik çiz.
// @author       zerask
// @match        https://gartic.io/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gartic.io
// ==/UserScript==
(function () {
"use strict";

const CW = 767, CH = 448;
const CFG = { delay: 120 };

// state
let timeoutIds = new Set();
let drawing = false;
let globalSegs = [];
let globalColorMode = false;
let lastIndex = 0;

// vector state
let vectorAnimationId = null;
let vectorDrawCancel = false;

// --- yardımcı fonksiyonlar ---

function hexToRgb(hex) {
  const r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return r ? { r: parseInt(r[1],16), g: parseInt(r[2],16), b: parseInt(r[3],16) } : null;
}

function sampleColorClassic(x1, y1, x2, rgba) {
  const mx = Math.max(0, Math.min(CW-1, Math.round((x1+x2)/2)));
  const my = Math.max(0, Math.min(CH-1, Math.round(y1)));
  const i = (my * CW + mx) * 4;
  return { r: rgba[i], g: rgba[i+1], b: rgba[i+2] };
}

function processImageClassic(imgEl, threshold, contrast) {
  const cv = document.createElement("canvas");
  cv.width = CW; cv.height = CH;
  const ctx = cv.getContext("2d");
  const scale = Math.min(CW / imgEl.naturalWidth, CH / imgEl.naturalHeight);
  const w = imgEl.naturalWidth * scale;
  const h = imgEl.naturalHeight * scale;
  const ox = (CW-w)/2, oy = (CH-h)/2;
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, CW, CH);
  ctx.drawImage(imgEl, ox, oy, w, h);
  const raw = ctx.getImageData(0, 0, CW, CH);
  const data = raw.data;
  const gray = new Uint8Array(CW * CH);
  for (let i = 0; i < CW*CH; i++) {
    const idx = i*4, a = data[idx+3];
    const r = data[idx], g = data[idx+1], b = data[idx+2];
    if (a < 128 || (r > 240 && g > 240 && b > 240)) { gray[i] = 255; }
    else {
      let lum = 0.299*r + 0.587*g + 0.114*b;
      lum = Math.min(255, Math.max(0, ((lum-128)*contrast)+128));
      gray[i] = lum;
    }
  }
  const mask = new Uint8Array(CW*CH);
  for (let i = 0; i < CW*CH; i++) mask[i] = gray[i] < threshold ? 0 : 255;
  return { mask, rgba: data };
}

function collectImageSegmentsClassic(processed, step, colorMode, baseColor) {
  const { mask, rgba } = processed;
  const segs = [];
  function tag(s) {
    if (!colorMode || !baseColor) return s;
    const c = sampleColorClassic(s.x1, s.y1, s.x2, rgba);
    s.color = `rgb(${Math.round(c.r*0.7+baseColor.r*0.3)},${Math.round(c.g*0.7+baseColor.g*0.3)},${Math.round(c.b*0.7+baseColor.b*0.3)})`;
    return s;
  }
  for (let y = 0; y < CH; y += step) {
    let run = false, sx = 0;
    for (let x = 0; x < CW; x++) {
      const dark = mask[y*CW+x] < 128;
      if (dark && !run)  { run = true; sx = x; }
      else if (!dark && run) { run = false; segs.push(tag({x1:sx,y1:y,x2:x,y2:y})); }
    }
    if (run) segs.push(tag({x1:sx,y1:y,x2:CW-1,y2:y}));
  }
  return segs;
}

// --- gartic bağlantısı ---

function getDrawer() {
  const el = document.querySelector("div#answer");
  if (!el) return null;
  for (const k in el) {
    if (!k.startsWith("__react")) continue;
    const g = el[k]?.return?.stateNode?._game;
    if (!g) continue;
    return { d: g._desenho, rect: g._desenho._elemBase.getBoundingClientRect() };
  }
  return null;
}

function selectTool(toolId) {
  const btn = document.querySelector(`li#${toolId}`);
  if (btn && !btn.classList.contains('active')) btn.click();
}

// temizle diyalogu
function clickYesOnDialog() {
  let attempts = 0;
  const interval = setInterval(() => {
    attempts++;
    const buttons = Array.from(document.querySelectorAll('button, div[role="button"], .button, [class*="btn"]'));
    const yesBtn = buttons.find(b => {
      const t = b.textContent.trim().toUpperCase();
      return t === 'EVET' || t === 'YES' || t === 'TAMAM' || t === 'OK';
    });
    if (yesBtn) {
      yesBtn.click();
      clearInterval(interval);
      hardStop();
      setStatus("🗑️ Temizlendi", false);
    } else if (attempts >= 25) {
      clearInterval(interval);
    }
  }, 120);
}

function clearCanvas() {
  const btn = document.querySelector("li#clean");
  if (btn) { btn.click(); clickYesOnDialog(); }
  else setStatus("❌ Temizle butonu yok", false);
}

// --- durdur / devam et ---

function hardStop() {
  window.__garticBotStop = true;
  timeoutIds.forEach(id => clearTimeout(id));
  timeoutIds.clear();
  drawing = false;
  const resumeBtn = document.getElementById("gu-resume");
  if (resumeBtn) resumeBtn.style.display = (globalSegs.length > lastIndex) ? "block" : "none";
  setStatus(`⛔ Durduruldu (${lastIndex}/${globalSegs.length})`, false);
}

function stopDrawing() { hardStop(); stopVectorDrawing(); }

function resumeDrawing() {
  if (!globalSegs.length || lastIndex >= globalSegs.length) {
    setStatus("⚠️ Devam edilecek çizim yok", false); return;
  }
  if (globalColorMode) startDrawColored(globalSegs, lastIndex);
  else startDraw(globalSegs, lastIndex);
}

function sendLine(d, rect, x1, y1, x2, y2, delay, tool) {
  if (window.__garticBotStop) return;
  const mk = (x,y) => ({ clientX: rect.left+x, clientY: rect.top+y, preventDefault(){} });
  const id = setTimeout(() => {
    if (window.__garticBotStop) return;
    if (tool) selectTool(tool);
    d._eventMouseDown(mk(x1,y1));
    d._eventMouseMove(mk(x2,y2));
    d._eventMouseUp(mk(x2,y2));
  }, delay);
  timeoutIds.add(id);
  const cleanId = setTimeout(() => timeoutIds.delete(id), delay+2000);
  timeoutIds.add(cleanId);
}

// --- görüntü işleme araçları ---

function getImageData(imgEl) {
  const cv = document.createElement("canvas");
  cv.width = CW; cv.height = CH;
  const ctx = cv.getContext("2d");
  const scale = Math.min(CW/imgEl.naturalWidth, CH/imgEl.naturalHeight);
  const w_ = imgEl.naturalWidth*scale, h_ = imgEl.naturalHeight*scale;
  ctx.fillStyle = "#fff"; ctx.fillRect(0,0,CW,CH);
  ctx.drawImage(imgEl, (CW-w_)/2, (CH-h_)/2, w_, h_);
  return ctx.getImageData(0,0,CW,CH);
}

function toGray(rgba, contrast) {
  const gray = new Float32Array(CW*CH);
  for (let i = 0; i < CW*CH; i++) {
    const idx=i*4, a=rgba[idx+3];
    if (a<128) { gray[i]=255; continue; }
    let lum = 0.299*rgba[idx] + 0.587*rgba[idx+1] + 0.114*rgba[idx+2];
    gray[i] = Math.min(255, Math.max(0, ((lum-128)*contrast)+128));
  }
  return gray;
}

function gaussianBlur(gray, w, h) {
  const K=[1,4,7,4,1,4,16,26,16,4,7,26,41,26,7,4,16,26,16,4,1,4,7,4,1];
  const out = new Float32Array(w*h);
  for (let y=2;y<h-2;y++)
    for (let x=2;x<w-2;x++) {
      let s=0;
      for (let j=-2;j<=2;j++) for (let i=-2;i<=2;i++)
        s += gray[(y+j)*w+(x+i)] * K[(j+2)*5+(i+2)];
      out[y*w+x] = s/273;
    }
  for (let y=0;y<h;y++) for (let x=0;x<w;x++)
    if (y<2||y>=h-2||x<2||x>=w-2) out[y*w+x] = gray[y*w+x];
  return out;
}

function buildBackgroundMaskAuto(gray, w, h) {
  const N=10; let sum=0;
  for (let i=0;i<N;i++) {
    sum+=gray[Math.floor(i*w/N)]; sum+=gray[(h-1)*w+Math.floor(i*w/N)];
    sum+=gray[Math.floor(i*h/N)*w]; sum+=gray[Math.floor(i*h/N)*w+(w-1)];
  }
  const edgeAvg = sum/(N*4);
  const isDarkBg = edgeAvg < 100;
  const bgLevel = isDarkBg ? 55 : 220;
  const isBgPx = isDarkBg ? v=>v<=bgLevel : v=>v>=bgLevel;
  const bg=new Uint8Array(w*h), stack=[];
  const tryAdd=(x,y)=>{ if(x<0||x>=w||y<0||y>=h)return; const i=y*w+x; if(!bg[i]&&isBgPx(gray[i])){bg[i]=1;stack.push(i);} };
  for (let x=0;x<w;x++){tryAdd(x,0);tryAdd(x,h-1);}
  for (let y=1;y<h-1;y++){tryAdd(0,y);tryAdd(w-1,y);}
  while (stack.length) {
    const i=stack.pop(), x=i%w, y=Math.floor(i/w);
    if(x>0){const ni=i-1;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
    if(x<w-1){const ni=i+1;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
    if(y>0){const ni=i-w;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
    if(y<h-1){const ni=i+w;if(!bg[ni]&&isBgPx(gray[ni])){bg[ni]=1;stack.push(ni);}}
  }
  return { bg, isDarkBg };
}

function isSkinPixel(r,g,b) {
  const Y = 0.299*r+0.587*g+0.114*b;
  if (Y<50||Y>235) return false;
  const Cb = 128-0.16874*r-0.33126*g+0.5*b;
  const Cr = 128+0.5*r-0.41869*g-0.08131*b;
  return (Cb>=80&&Cb<=122)&&(Cr>=133&&Cr<=173);
}

function sampleColorNeighborhood(x1, x2, y, rgba, w) {
  const mx=Math.round((x1+x2)/2);
  const clamp=(v,max)=>Math.max(0,Math.min(max-1,v));
  let r=0,g=0,b=0,ws=0;
  const wt=[[1,2,1],[2,4,2],[1,2,1]];
  for (let dy=-1;dy<=1;dy++) for (let dx=-1;dx<=1;dx++) {
    const nx=clamp(mx+dx,w), ny=clamp(y+dy,CH);
    const idx=(ny*w+nx)*4, w_=wt[dy+1][dx+1];
    r+=rgba[idx]*w_; g+=rgba[idx+1]*w_; b+=rgba[idx+2]*w_; ws+=w_;
  }
  return `rgb(${Math.round(r/ws)},${Math.round(g/ws)},${Math.round(b/ws)})`;
}

function sampleColorRect(x, y, w, h, rgba, rw) {
  let r=0,g=0,b=0,cnt=0;
  const x2=Math.min(x+w,CW), y2=Math.min(y+h,CH);
  const step=Math.max(1,Math.floor(Math.min(w,h)/4));
  for (let py=y;py<y2;py+=step) for (let px=x;px<x2;px+=step) {
    const i=(py*rw+px)*4; r+=rgba[i]; g+=rgba[i+1]; b+=rgba[i+2]; cnt++;
  }
  if (!cnt) return 'rgb(128,128,128)';
  return `rgb(${Math.round(r/cnt)},${Math.round(g/cnt)},${Math.round(b/cnt)})`;
}

// --- çizgi birleştirme ---

function dist(ax,ay,bx,by) { return Math.sqrt((ax-bx)**2+(ay-by)**2); }

function angleBetween(x1,y1,x2,y2,x3,y3) {
  const a1=Math.atan2(y2-y1,x2-x1), a2=Math.atan2(y3-y2,x3-x2);
  let diff=Math.abs(a1-a2);
  if (diff>Math.PI) diff=2*Math.PI-diff;
  return diff*(180/Math.PI);
}

function mergeCollinearSegments(segs, angleThr=18, gapThr=4) {
  if (!segs.length) return [];
  const merged = [];
  let cur = {...segs[0]};
  for (let i=1;i<segs.length;i++) {
    const s=segs[i];
    const d=dist(cur.x2,cur.y2,s.x1,s.y1);
    const ang=angleBetween(cur.x1,cur.y1,cur.x2,cur.y2,s.x1,s.y1);
    if (d<=gapThr&&ang<=angleThr&&(!cur.color||cur.color===s.color)) {
      cur.x2=s.x2; cur.y2=s.y2;
    } else { merged.push(cur); cur={...s}; }
  }
  merged.push(cur);
  return merged;
}

// --- FAST modu ---

function collectPortraitFill(gray, bgMask, rgba, w, h, maxStep, skipSkin, colorMode) {
  const rowScore=new Float32Array(h);
  for (let y=1;y<h-1;y++) {
    let score=0,cnt=0;
    for (let x=1;x<w-1;x++) {
      if (bgMask[y*w+x]) continue; cnt++;
      const gx=Math.abs(gray[y*w+(x+1)]-gray[y*w+(x-1)]);
      const gy=Math.abs(gray[(y+1)*w+x]-gray[(y-1)*w+x]);
      score+=gx+gy;
    }
    rowScore[y]=cnt>0 ? score/cnt+cnt*0.08 : 0;
  }
  const smoothed=new Float32Array(h); let maxVal=1e-6;
  for (let y=0;y<h;y++) {
    let s=0,c=0;
    for (let dy=-2;dy<=2;dy++) {
      const ny=y+dy; if(ny<0||ny>=h)continue;
      const wt=3-Math.abs(dy); s+=rowScore[ny]*wt; c+=wt;
    }
    smoothed[y]=s/c;
    if (smoothed[y]>maxVal) maxVal=smoothed[y];
  }
  let segs=[],y=0;
  while (y<h) {
    const t=smoothed[y]/maxVal;
    const step=Math.max(1,Math.round(maxStep*Math.pow(1-t,0.45)+0.5));
    let run=false,sx=0;
    for (let x=0;x<w;x++) {
      const i=y*w+x; let draw=false;
      if (!bgMask[i]) {
        const lum=gray[i];
        if (lum<70) draw=true;
        else {
          const ri=i*4;
          if (!(skipSkin&&isSkinPixel(rgba[ri],rgba[ri+1],rgba[ri+2]))&&lum<185) draw=true;
        }
      }
      if (draw&&!run){run=true;sx=x;}
      else if (!draw&&run) {
        run=false;
        const seg={x1:sx,y1:y,x2:x,y2:y};
        if (colorMode) seg.color=sampleColorNeighborhood(sx,x,y,rgba,w);
        segs.push(seg);
      }
    }
    if (run) { const seg={x1:sx,y1:y,x2:w-1,y2:y}; if(colorMode)seg.color=sampleColorNeighborhood(sx,w-1,y,rgba,w); segs.push(seg); }
    y += smoothed[y]/maxVal>0.6 ? 1 : step;
  }
  return mergeCollinearSegments(segs,25,6);
}

// --- QUAD modu ---

function getAvgColorAndVariance(rgba, gray, x, y, w, h, rw) {
  let r=0,g=0,b=0,gs=0,gsq=0,cnt=0;
  for (let dy=y;dy<y+h;dy++) for (let dx=x;dx<x+w;dx++) {
    const i=(dy*rw+dx)*4;
    r+=rgba[i]; g+=rgba[i+1]; b+=rgba[i+2];
    const v=gray[dy*rw+dx]; gs+=v; gsq+=v*v; cnt++;
  }
  if (!cnt) return {var:99999,color:'rgb(0,0,0)'};
  const mean=gs/cnt;
  return { var:(gsq/cnt)-mean*mean, color:`rgb(${Math.round(r/cnt)},${Math.round(g/cnt)},${Math.round(b/cnt)})` };
}

function quadtreeSegments(rgba, gray, x, y, w, h, rw, maxDepth, varThr, depth=0) {
  const segs=[], info=getAvgColorAndVariance(rgba,gray,x,y,w,h,rw);
  if (depth>=maxDepth||w<8||h<8||info.var<varThr) {
    if (w>1&&h>1) segs.push({x1:x,y1:y,x2:x+w,y2:y+h,color:info.color,tool:'op2'});
    return segs;
  }
  const hw=Math.floor(w/2),hh=Math.floor(h/2),rw2=w-hw,rh2=h-hh;
  segs.push(...quadtreeSegments(rgba,gray,x,y,hw,hh,rw,maxDepth,varThr,depth+1));
  segs.push(...quadtreeSegments(rgba,gray,x+hw,y,rw2,hh,rw,maxDepth,varThr,depth+1));
  segs.push(...quadtreeSegments(rgba,gray,x,y+hh,hw,rh2,rw,maxDepth,varThr,depth+1));
  segs.push(...quadtreeSegments(rgba,gray,x+hw,y+hh,rw2,rh2,rw,maxDepth,varThr,depth+1));
  return segs;
}

function processQuadMode(imgEl, maxDepth, varThr, colorMode) {
  const raw=getImageData(imgEl);
  const gray=toGray(raw.data,1.0);
  const segs=quadtreeSegments(raw.data,gray,0,0,CW,CH,CW,maxDepth,varThr);
  if (!colorMode) segs.forEach(s=>delete s.color);
  return segs;
}

// --- VECTOR modu (marching squares) ---

function vectorGaussianBlur(gray, w, h, sigma) {
  if (sigma<=0) return gray;
  const radius=Math.ceil(sigma*3), size=radius*2+1;
  const kernel=new Float32Array(size); let sum=0;
  for (let i=-radius;i<=radius;i++) { const v=Math.exp(-(i*i)/(2*sigma*sigma)); kernel[i+radius]=v; sum+=v; }
  for (let i=0;i<size;i++) kernel[i]/=sum;
  const tmp=new Float32Array(w*h);
  for (let y=0;y<h;y++) for (let x=0;x<w;x++) {
    let s=0;
    for (let k=-radius;k<=radius;k++) s+=gray[y*w+Math.min(Math.max(x+k,0),w-1)]*kernel[k+radius];
    tmp[y*w+x]=s;
  }
  const out=new Float32Array(w*h);
  for (let y=0;y<h;y++) for (let x=0;x<w;x++) {
    let s=0;
    for (let k=-radius;k<=radius;k++) s+=tmp[Math.min(Math.max(y+k,0),h-1)*w+x]*kernel[k+radius];
    out[y*w+x]=s;
  }
  return out;
}

function vectorImageToGrayscale(imageData) {
  const data=imageData.data, gray=new Float32Array(CW*CH);
  for (let i=0;i<CW*CH;i++) {
    const idx=i*4;
    if (data[idx+3]<128){gray[i]=255;continue;}
    gray[i]=0.299*data[idx]+0.587*data[idx+1]+0.114*data[idx+2];
  }
  return gray;
}

function marchingSquares(blurred, w, h, threshold, cellSize) {
  const gridW=Math.floor(w/cellSize)+1, gridH=Math.floor(h/cellSize)+1;
  const grid=new Float32Array(gridW*gridH);
  for (let y=0;y<gridH;y++) for (let x=0;x<gridW;x++)
    grid[y*gridW+x]=blurred[Math.min(y*cellSize,h-1)*w+Math.min(x*cellSize,w-1)];

  const edges=[];
  function interp(v1,v2,faceLen,bx,by,horiz) {
    if (Math.abs(v2-v1)<1e-6) return {x:bx,y:by};
    const t=(threshold-v1)/(v2-v1);
    return horiz?{x:bx+t*faceLen,y:by}:{x:bx,y:by+t*faceLen};
  }
  for (let j=0;j<gridH-1;j++) for (let i=0;i<gridW-1;i++) {
    const x0=i*cellSize, y0=j*cellSize;
    const v0=grid[j*gridW+i], v1=grid[j*gridW+(i+1)];
    const v2=grid[(j+1)*gridW+(i+1)], v3=grid[(j+1)*gridW+i];
    const b0=v0<threshold, b1=v1<threshold, b2=v2<threshold, b3=v3<threshold;
    const idx=(b0?1:0)|(b1?2:0)|(b2?4:0)|(b3?8:0);
    if (idx===0||idx===15) continue;
    let top,right,bot,left;
    if (b0!==b1) top  =interp(v0,v1,cellSize,x0,y0,true);
    if (b1!==b2) right=interp(v1,v2,cellSize,x0+cellSize,y0,false);
    if (b2!==b3) bot  =interp(v3,v2,cellSize,x0,y0+cellSize,true);
    if (b3!==b0) left =interp(v0,v3,cellSize,x0,y0,false);
    switch(idx){
      case 1:edges.push([top,left]);break; case 2:edges.push([top,right]);break;
      case 3:edges.push([right,left]);break; case 4:edges.push([right,bot]);break;
      case 5:edges.push([top,left]);edges.push([bot,right]);break;
      case 6:edges.push([top,bot]);break; case 7:edges.push([bot,left]);break;
      case 8:edges.push([left,bot]);break; case 9:edges.push([top,bot]);break;
      case 10:edges.push([top,right]);edges.push([bot,left]);break;
      case 11:edges.push([right,bot]);break; case 12:edges.push([right,left]);break;
      case 13:edges.push([top,right]);break; case 14:edges.push([top,left]);break;
    }
  }
  return edges;
}

function buildVectorPaths(edges) {
  const key=pt=>`${pt.x.toFixed(2)},${pt.y.toFixed(2)}`;
  const ptToEdges=new Map();
  edges.forEach(([p1,p2],idx)=>{
    [p1,p2].forEach(p=>{ const k=key(p); if(!ptToEdges.has(k))ptToEdges.set(k,[]); ptToEdges.get(k).push(idx); });
  });
  const visited=new Set(), paths=[];
  for (let i=0;i<edges.length;i++) {
    if (visited.has(i)) continue;
    let [startP,nextP]=edges[i]; visited.add(i);
    const path=[startP,nextP]; let cur=nextP, curIdx=i;
    while (true) {
      const conn=ptToEdges.get(key(cur))||[];
      let nextIdx=-1, nextPt=null;
      for (const eIdx of conn) {
        if (eIdx===curIdx||visited.has(eIdx)) continue;
        const [ep1,ep2]=edges[eIdx];
        if (key(ep1)===key(cur)){nextPt=ep2;nextIdx=eIdx;break;}
        if (key(ep2)===key(cur)){nextPt=ep1;nextIdx=eIdx;break;}
      }
      if (nextIdx===-1) break;
      visited.add(nextIdx); path.push(nextPt); cur=nextPt; curIdx=nextIdx;
    }
    paths.push(path.length>2?path:[edges[i][0],edges[i][1]]);
  }
  paths.sort((a,b)=>a[0].y-b[0].y);
  return paths;
}

function startVectorDraw(paths, speed) {
  const obj=getDrawer();
  if (!obj){setStatus("❌ Çizim alanı bulunamadı!",false);return;}
  const {d,rect}=obj;
  const rand=(min,max)=>min+Math.random()*(max-min);
  const events=[]; let t=0;
  paths.forEach(path=>{
    if (!path.length) return;
    t+=rand(80,250);
    events.push({type:"down",x:path[0].x,y:path[0].y,time:t});
    for (let i=1;i<path.length;i++) {
      const prev=path[i-1],cur=path[i];
      const dx=cur.x-prev.x, dy=cur.y-prev.y;
      let dt=Math.max(18,(Math.sqrt(dx*dx+dy*dy)/speed)*1000)+rand(-12,12);
      if (Math.sqrt(dx*dx+dy*dy)<2) dt=Math.max(dt,35);
      t+=dt;
      events.push({type:"move",x:cur.x,y:cur.y,time:t});
    }
    const last=path[path.length-1];
    events.push({type:"up",x:last.x,y:last.y,time:t});
    t+=rand(150,400);
  });
  if (!events.length){setStatus("⚠ Çizilecek kontur bulunamadı",false);return;}

  const start=performance.now(); let idx=0; vectorDrawCancel=false;
  function dispatch(type,x,y) {
    const mk={clientX:rect.left+x,clientY:rect.top+y,preventDefault(){}};
    if(type==="down")d._eventMouseDown(mk);
    else if(type==="move")d._eventMouseMove(mk);
    else d._eventMouseUp(mk);
  }
  function frame() {
    if (vectorDrawCancel){setStatus("⛔ Çizim durduruldu",false);return;}
    const now=performance.now()-start;
    while (idx<events.length&&events[idx].time<=now) { dispatch(events[idx].type,events[idx].x,events[idx].y); idx++; }
    if (idx>=events.length){setStatus(`✅ Bitti! (${paths.length} kontur)`,false);vectorAnimationId=null;return;}
    setStatus(`✏ Çiziliyor… ${idx}/${events.length}`,true);
    vectorAnimationId=requestAnimationFrame(frame);
  }
  vectorAnimationId=requestAnimationFrame(frame);
  setStatus(`▶ Başlıyor (${paths.length} kontur)`,true);
}

function stopVectorDrawing() {
  vectorDrawCancel=true;
  if (vectorAnimationId){cancelAnimationFrame(vectorAnimationId);vectorAnimationId=null;}
}

function processVectorMode(imgEl, threshold, blurSigma, cellSize, speed) {
  const cv=document.createElement("canvas"); cv.width=CW; cv.height=CH;
  const ctx=cv.getContext("2d");
  const scale=Math.min(CW/imgEl.naturalWidth,CH/imgEl.naturalHeight);
  const w=imgEl.naturalWidth*scale, h=imgEl.naturalHeight*scale;
  ctx.fillStyle="#fff"; ctx.fillRect(0,0,CW,CH);
  ctx.drawImage(imgEl,(CW-w)/2,(CH-h)/2,w,h);
  const imageData=ctx.getImageData(0,0,CW,CH);
  const gray=vectorImageToGrayscale(imageData);
  const blurred=vectorGaussianBlur(gray,CW,CH,blurSigma);
  const edges=marchingSquares(blurred,CW,CH,threshold,cellSize);
  if (!edges.length){setStatus("⚠ Kontur bulunamadı — ayarları değiştirin",false);return null;}
  return {paths:buildVectorPaths(edges),speed};
}

// --- ana pipeline ---

function processImage(imgEl, mode, threshold, contrast, step, colorMode, skipSkin, maxDepth, varThr) {
  if (mode==='quad') return processQuadMode(imgEl,maxDepth,varThr,colorMode);
  if (mode==='vector') return null;
  if (mode==='classic') {
    const processed=processImageClassic(imgEl,threshold,contrast);
    const baseColor=colorMode?hexToRgb(document.getElementById("colorRangeInput").value):null;
    return collectImageSegmentsClassic(processed,step,colorMode,baseColor);
  }
  const raw=getImageData(imgEl);
  const gray=toGray(raw.data,contrast);
  const blurred=gaussianBlur(gray,CW,CH);
  const {bg}=buildBackgroundMaskAuto(blurred,CW,CH);
  return collectPortraitFill(blurred,bg,raw.data,CW,CH,step,skipSkin,colorMode);
}

// --- çizim başlatıcı ---

function startDraw(segs, startFrom=0) {
  const obj=getDrawer();
  if (!obj){setStatus("❌ Çizim alanı yok!",false);return;}
  if (drawing&&startFrom===0){setStatus("⏳ Zaten çiziyor!",false);return;}
  if (!segs.length){setStatus("⚠️ Çizilecek piksel yok",false);return;}
  const {d,rect}=obj;
  drawing=true; window.__garticBotStop=false; timeoutIds.clear();
  globalSegs=segs; globalColorMode=false; lastIndex=startFrom;
  setStatus(`⏳ ${startFrom} / ${segs.length}`,true);
  let delay=0;
  for (let i=startFrom;i<segs.length;i++) {
    if (window.__garticBotStop) break;
    const s=segs[i];
    sendLine(d,rect,s.x1,s.y1,s.x2,s.y2,delay,s.tool);
    delay+=Math.max(18,CFG.delay+Math.random()*5-2);
    const id=setTimeout(()=>{
      if (window.__garticBotStop) return;
      lastIndex=i+1;
      setStatus(`⏳ ${i+1} / ${segs.length}`,true);
      if (i+1>=segs.length){drawing=false;lastIndex=0;setStatus(`✅ Bitti! (${segs.length} çizgi)`,false);document.getElementById('gu-resume').style.display='none';}
    },delay);
    timeoutIds.add(id);
  }
}

function startDrawColored(segs, startFrom=0) {
  const obj=getDrawer();
  if (!obj){setStatus("❌ Çizim alanı yok!",false);return;}
  if (drawing&&startFrom===0){setStatus("⏳ Zaten çiziyor!",false);return;}
  if (!segs.length){setStatus("⚠️ Çizilecek piksel yok",false);return;}
  const {d,rect}=obj;
  drawing=true; window.__garticBotStop=false; timeoutIds.clear();
  globalSegs=segs; globalColorMode=true; lastIndex=startFrom;
  const total=segs.length; let done=startFrom, gDelay=0;
  setStatus(`⏳ ${done} / ${total} (Renkli)`,true);
  for (let i=startFrom;i<segs.length;i++) {
    if (window.__garticBotStop) break;
    const s=segs[i];
    if (s.color){
      const cd=gDelay;
      timeoutIds.add(setTimeout(()=>{ if(!window.__garticBotStop)setGarticColor(s.color); },cd));
      gDelay+=30;
    }
    sendLine(d,rect,s.x1,s.y1,s.x2,s.y2,gDelay,s.tool);
    gDelay+=Math.max(18,CFG.delay+Math.random()*5-2);
    timeoutIds.add(setTimeout(()=>{
      if (window.__garticBotStop) return;
      done++; lastIndex=done;
      setStatus(`⏳ ${done} / ${total} (Renkli)`,true);
      if (done>=total){drawing=false;lastIndex=0;setStatus(`✅ Bitti! ${total} blok (Renkli)`,false);document.getElementById('gu-resume').style.display='none';}
    },gDelay));
  }
}

function setGarticColor(color) {
  const el=document.getElementById("colorRangeInput");
  if (el){el.value=color;el.dispatchEvent(new Event('input',{bubbles:true}));}
}

function drawImage(imgEl, mode, threshold, contrast, step, colorMode, skipSkin, maxDepth, varThr, vectorSettings) {
  setStatus("⚙️ Hesaplanıyor...",true);
  setTimeout(()=>{
    if (mode==='vector') {
      const result=processVectorMode(imgEl,vectorSettings.threshold,vectorSettings.blur,vectorSettings.cellSize,vectorSettings.speed);
      if (result) startVectorDraw(result.paths,result.speed);
      return;
    }
    const segs=processImage(imgEl,mode,threshold,contrast,step,colorMode,skipSkin,maxDepth,varThr);
    setStatus(`📊 ${segs.length} segment hazır`,true);
    setTimeout(()=>{ if(colorMode)startDrawColored(segs); else startDraw(segs); },300);
  },50);
}

/* ─────────────────────────── UI ─────────────────────────── */

function setStatus(msg, busy) {
  const el=document.getElementById("gu-status");
  const bim=document.getElementById("gu-draw-img");
  const stp=document.getElementById("gu-stop");
  if (el){el.textContent=msg;el.style.color=busy?"#f4c06a":"#4dffd6";}
  if (bim){bim.disabled=busy;bim.style.opacity=busy?"0.4":"1";}
  if (stp) stp.style.display=busy?"block":"none";
}

function makeDraggable(panel) {
  let sx,sy,sl,st,drag=false;
  function onStart(ex,ey){sx=ex;sy=ey;const r=panel.getBoundingClientRect();sl=r.left;st=r.top;drag=true;}
  function onMove(ex,ey){if(!drag)return;panel.style.left=Math.max(0,sl+ex-sx)+"px";panel.style.top=Math.max(0,st+ey-sy)+"px";panel.style.right="auto";panel.style.bottom="auto";}
  function onEnd(){drag=false;}
  const hdr=document.getElementById("gu-handle");
  hdr.addEventListener("mousedown",e=>{onStart(e.clientX,e.clientY);e.preventDefault();});
  document.addEventListener("mousemove",e=>onMove(e.clientX,e.clientY));
  document.addEventListener("mouseup",onEnd);
  hdr.addEventListener("touchstart",e=>{const t=e.touches[0];onStart(t.clientX,t.clientY);},{passive:true});
  document.addEventListener("touchmove",e=>{if(!drag)return;const t=e.touches[0];onMove(t.clientX,t.clientY);},{passive:true});
  document.addEventListener("touchend",onEnd);
}

function toggleMinimize() {
  const body=document.getElementById("gu-body");
  const btn=document.getElementById("gu-min-btn");
  const c=body.style.display==="none";
  body.style.display=c?"block":"none";
  if(btn)btn.textContent=c?"▾":"▸";
}

/* ─── YENİ TEMA ─────────────────────────────────────────── */
const CSS = `
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap');

  #gu-panel {
    position: fixed; left: 14px; bottom: 14px;
    width: min(300px, calc(100vw - 28px));
    background: #0e0e14;
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 18px;
    z-index: 999999;
    font-family: 'Inter', 'Segoe UI', sans-serif;
    box-shadow: 0 0 0 1px rgba(255,255,255,0.03), 0 24px 60px rgba(0,0,0,0.7);
    color: #c9c4e8;
    user-select: none; touch-action: none;
    max-height: 94vh; overflow: hidden;
    display: flex; flex-direction: column;
  }
  #gu-panel * { box-sizing: border-box; }

  #gu-handle {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 14px 10px;
    cursor: grab;
    background: linear-gradient(135deg, #13111e 0%, #0e0e14 100%);
    border-radius: 18px 18px 0 0;
    border-bottom: 1px solid rgba(255,255,255,0.05);
    flex-shrink: 0;
  }
  #gu-handle:active { cursor: grabbing; }

  .gu-handle-title {
    font-size: 11px; font-weight: 800; letter-spacing: 2px;
    color: #7c5fe6; text-transform: uppercase;
  }
  .gu-handle-badge {
    font-size: 8px; color: rgba(124,95,230,0.45);
    letter-spacing: 1px; margin-top: 1px;
  }
  #gu-min-btn {
    background: rgba(124,95,230,0.12);
    border: 1px solid rgba(124,95,230,0.2);
    color: #7c5fe6; font-size: 12px;
    cursor: pointer; padding: 2px 7px; line-height: 1;
    border-radius: 6px;
    -webkit-tap-highlight-color: transparent;
    transition: background .15s;
  }
  #gu-min-btn:hover { background: rgba(124,95,230,0.22); }

  #gu-body {
    overflow-y: auto; padding: 12px 12px 12px;
    max-height: calc(94vh - 54px);
    scrollbar-width: thin;
    scrollbar-color: rgba(124,95,230,0.25) transparent;
  }
  #gu-body::-webkit-scrollbar { width: 3px; }
  #gu-body::-webkit-scrollbar-thumb { background: rgba(124,95,230,0.25); border-radius: 99px; }

  .gu-label {
    font-size: 9px; color: rgba(124,95,230,0.55);
    margin-bottom: 4px; letter-spacing: 1.2px; text-transform: uppercase; font-weight: 600;
  }

  .gu-input, .gu-range, .gu-select {
    width: 100%;
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.07);
    color: #c9c4e8; padding: 8px 10px;
    border-radius: 10px; font-size: 12px; outline: none;
    margin-bottom: 8px; transition: border-color .2s;
    font-family: inherit; -webkit-appearance: none;
  }
  .gu-input:focus, .gu-select:focus {
    border-color: rgba(124,95,230,0.5);
    background: rgba(124,95,230,0.06);
  }
  .gu-range {
    padding: 4px 0; cursor: pointer; height: 24px;
    accent-color: #7c5fe6; border: none; background: transparent;
  }
  .gu-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' fill='%237c5fe6'%3E%3Cpath d='M5 7L1 3h8z'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 10px center; padding-right: 26px;
  }

  .gu-btn {
    width: 100%; border: none; padding: 10px 12px;
    border-radius: 11px; cursor: pointer;
    font-weight: 700; font-size: 12px; letter-spacing: 1px;
    transition: transform .1s, opacity .15s, box-shadow .15s;
    text-transform: uppercase; margin-bottom: 6px;
    min-height: 42px; font-family: inherit;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  }
  .gu-btn:hover:not(:disabled) { transform: translateY(-1px); }
  .gu-btn:active:not(:disabled) { transform: scale(0.97); }
  .gu-btn:disabled { opacity: 0.4; cursor: not-allowed; }

  #gu-draw-img {
    background: linear-gradient(135deg, #5b38c8, #8b5cf6);
    color: #fff;
    box-shadow: 0 4px 18px rgba(124,95,230,0.3);
  }
  #gu-draw-img:hover:not(:disabled) {
    box-shadow: 0 6px 24px rgba(124,95,230,0.45);
  }
  #gu-stop {
    background: linear-gradient(135deg, #8b1c1c, #c0392b);
    color: #fff; display: none;
  }
  #gu-resume {
    background: linear-gradient(135deg, #1a6b3a, #27ae60);
    color: #fff; display: none;
  }
  #gu-clear {
    background: rgba(255,255,255,0.04);
    color: rgba(201,196,232,0.45);
    border: 1px solid rgba(255,255,255,0.07);
    font-size: 11px;
  }
  #gu-clear:hover { color: rgba(201,196,232,0.7); }

  #gu-status {
    font-size: 10px; color: #4dffd6;
    text-align: center; min-height: 14px;
    letter-spacing: .5px; margin-bottom: 6px;
    font-weight: 600;
  }

  #gu-img-preview {
    width: 100%; height: 76px; object-fit: contain;
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.07);
    border-radius: 10px; margin-bottom: 8px; display: none;
  }

  #gu-img-drop {
    width: 100%; height: 60px;
    border: 1.5px dashed rgba(124,95,230,0.25);
    border-radius: 12px; display: flex;
    align-items: center; justify-content: center;
    font-size: 11px; color: rgba(124,95,230,0.45);
    cursor: pointer; margin-bottom: 8px;
    transition: border-color .2s, color .2s, background .2s;
    background: rgba(124,95,230,0.04);
    min-height: 42px; font-weight: 600;
    -webkit-tap-highlight-color: transparent;
  }
  #gu-img-drop:hover, #gu-img-drop:active {
    border-color: rgba(124,95,230,0.6);
    color: #a88bff;
    background: rgba(124,95,230,0.1);
  }
  #gu-img-drop.drag {
    border-color: #a88bff; color: #c4a8ff;
    background: rgba(124,95,230,0.14);
  }
  #gu-img-input { display: none; }

  #gu-color-picker {
    display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.07);
    border-radius: 10px; padding: 8px 10px;
  }
  #colorRangeInput {
    width: 40px; height: 32px; border: none;
    border-radius: 6px; cursor: pointer; background: none; flex-shrink: 0;
  }
  .gu-color-label { font-size: 11px; color: #a88bff; font-weight: 700; letter-spacing: .5px; flex: 1; }

  .gu-slider-row { margin-bottom: 8px; }
  .gu-slider-label {
    display: flex; justify-content: space-between; margin-bottom: 2px;
  }
  .gu-slider-label span:first-child { font-size: 9px; color: rgba(124,95,230,0.5); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; }
  .gu-slider-label span:last-child  { font-size: 10px; color: #9d86e8; font-weight: 700; }

  .gu-toggle-row {
    display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.07);
    border-radius: 10px; padding: 10px; cursor: pointer; min-height: 42px;
    -webkit-tap-highlight-color: transparent;
    transition: background .15s;
  }
  .gu-toggle-row:hover { background: rgba(124,95,230,0.08); }
  .gu-toggle-row input[type="checkbox"] { accent-color: #7c5fe6; width: 16px; height: 16px; cursor: pointer; flex-shrink: 0; }
  .gu-toggle-row span { font-size: 11px; color: #a88bff; font-weight: 700; letter-spacing: .4px; }

  .gu-skin-box {
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 10px; padding: 10px; margin-bottom: 8px;
  }
  .gu-skin-box label { display: flex; align-items: center; gap: 8px; cursor: pointer; min-height: 30px; }
  .gu-skin-box input[type="checkbox"] { accent-color: #e87cad; width: 16px; height: 16px; flex-shrink: 0; }
  .gu-skin-box span { font-size: 11px; color: #e8a8cc; font-weight: 700; letter-spacing: .4px; }
  .gu-skin-box p  { font-size: 9px; color: rgba(232,168,204,0.4); margin: 4px 0 0; line-height: 1.5; }

  .gu-delay-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
  .gu-delay-row .gu-label { margin: 0; white-space: nowrap; }
  .gu-delay-row .gu-input { margin-bottom: 0; flex: 1; }

  .gu-url-row { display: flex; gap: 6px; margin-bottom: 8px; }
  .gu-url-row .gu-input { margin-bottom: 0; font-size: 11px; flex: 1; }
  #gu-img-url-load {
    margin-bottom: 0; width: auto; padding: 8px 12px;
    font-size: 12px; flex-shrink: 0; border-radius: 10px;
    background: linear-gradient(135deg, #3a2080, #6344c8);
    color: #e0d8ff; border: none; cursor: pointer; font-weight: 800;
    min-height: 42px; min-width: 42px; font-family: inherit;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation;
    transition: opacity .15s;
  }
  #gu-img-url-load:hover { opacity: .85; }

  #gu-img-url-status { font-size: 9px; color: #e8906a; text-align: center; min-height: 12px; margin-bottom: 6px; }
  #gu-img-remove {
    margin-bottom: 8px; background: rgba(255,255,255,0.04);
    color: rgba(201,80,80,0.7);
    border: 1px solid rgba(255,80,80,0.12);
    font-size: 11px; padding: 8px; width: 100%;
    border-radius: 10px; font-family: inherit;
  }
  #gu-img-remove:hover { color: #ff6b6b; border-color: rgba(255,80,80,0.25); }

  .gu-divider { border: none; border-top: 1px solid rgba(255,255,255,0.05); margin: 10px 0; }

  .gu-mode-info {
    font-size: 9px; color: rgba(124,95,230,0.45); line-height: 1.55;
    margin-bottom: 8px; padding: 7px 9px;
    background: rgba(124,95,230,0.05);
    border-radius: 8px; border: 1px solid rgba(124,95,230,0.1);
  }

  .gu-tag {
    display: inline-block; font-size: 8px; font-weight: 700;
    padding: 2px 7px; border-radius: 99px; letter-spacing: 1px;
    text-transform: uppercase; margin-bottom: 8px;
    background: rgba(124,95,230,0.12);
    border: 1px solid rgba(124,95,230,0.2);
    color: rgba(124,95,230,0.7);
  }
`;

const styleTag = document.createElement("style");
styleTag.textContent = CSS;
document.head.appendChild(styleTag);

const panel = document.createElement("div");
panel.id = "gu-panel";

panel.innerHTML = `
  <div id="gu-handle">
    <div>
      <div class="gu-handle-title">✦ gartic çizim botu</div>
      <div class="gu-handle-badge">made by zerask</div>
    </div>
    <button id="gu-min-btn">▾</button>
  </div>

  <div id="gu-body">

    <div id="gu-img-drop">📂 tıkla veya sürükle</div>
    <input type="file" id="gu-img-input" accept="image/*">
    <img id="gu-img-preview">

    <div id="gu-img-actions" style="display:none;">
      <button class="gu-btn" id="gu-img-remove">✕ Resmi Kaldır</button>
    </div>

    <div class="gu-label">URL ile yükle</div>
    <div class="gu-url-row">
      <input class="gu-input" id="gu-img-url" placeholder="https://..." type="url" inputmode="url">
      <button id="gu-img-url-load">↵</button>
    </div>
    <div id="gu-img-url-status"></div>

    <hr class="gu-divider">

    <div class="gu-label">Çizim Modu</div>
    <select id="gu-mode" class="gu-select">
      <option value="classic" selected>⬛Satır Dolgu</option>
      <option value="fast">⚡Adaptif Dolgu</option>
      <option value="quad">🟦Dikdörtgen Blok Portre</option>
      <option value="vector">🎯Kontur Çizgi</option>
    </select>
    <div class="gu-mode-info" id="gu-mode-desc">
      Klasik satır tabanlı dolgu.
    </div>

    <div id="gu-fast-controls">
      <div class="gu-slider-row" id="gu-thr-row">
        <div class="gu-slider-label"><span>Eşik</span><span id="gu-thr-val">240</span></div>
        <input class="gu-range" type="range" id="gu-thr" min="20" max="240" value="240">
      </div>

      <div class="gu-slider-row" id="gu-con-row">
        <div class="gu-slider-label"><span>Kontrast</span><span id="gu-con-val">3.3</span></div>
        <input class="gu-range" type="range" id="gu-con" min="0.5" max="4" step="0.1" value="3.3">
      </div>

      <div class="gu-slider-row" id="gu-step-row">
        <div class="gu-slider-label"><span>Maks Adım (px)</span><span id="gu-img-step-val">5</span></div>
        <input class="gu-range" type="range" id="gu-img-step" min="2" max="20" value="5">
      </div>

      <div class="gu-slider-row" id="gu-quad-row" style="display:none;">
        <div class="gu-slider-label"><span>Quad Derinlik</span><span id="gu-quad-depth-val">6</span></div>
        <input class="gu-range" type="range" id="gu-quad-depth" min="3" max="8" value="8">
        <div class="gu-slider-label" style="margin-top:6px;"><span>Varyans Eşiği</span><span id="gu-quad-var-val">400</span></div>
        <input class="gu-range" type="range" id="gu-quad-var" min="50" max="2000" step="50" value="200">
      </div>

      <div class="gu-delay-row">
        <span class="gu-label">Gecikme (ms)</span>
        <input class="gu-input" id="gu-img-delay" type="number" inputmode="numeric" value="120" min="40" max="3000" step="20">
      </div>

      <hr class="gu-divider">

      <div class="gu-skin-box" id="gu-skin-section">
        <label>
          <input type="checkbox" id="gu-skip-skin" checked>
          <span>🎭 Ten Tonu Atla</span>
        </label>
        <p>Ten piksellerini atlar (yalnızca Fast modda).</p>
      </div>

      <div class="gu-label">Renk Seçici</div>
      <div id="gu-color-picker">
        <input type="color" id="colorRangeInput" value="#000000">
        <span class="gu-color-label">🎨 Palet</span>
      </div>
      <div class="gu-toggle-row">
        <input type="checkbox" id="gu-color-mode" checked>
        <span>🌈 Renkli Çizim</span>
      </div>
    </div>

    <div id="gu-vector-controls" style="display:none;">
      <div class="gu-slider-row">
        <div class="gu-slider-label"><span>Eşik</span><span id="gu-v-thr-val">200</span></div>
        <input class="gu-range" type="range" id="gu-v-thr" min="20" max="340" value="200">
      </div>
      <div class="gu-slider-row">
        <div class="gu-slider-label"><span>Bulanıklık</span><span id="gu-v-blur-val">1.0</span></div>
        <input class="gu-range" type="range" id="gu-v-blur" min="0" max="5" step="0.1" value="1.0">
      </div>
      <div class="gu-slider-row">
        <div class="gu-slider-label"><span>Hücre Boyutu</span><span id="gu-v-cell-val">3</span></div>
        <input class="gu-range" type="range" id="gu-v-cell" min="2" max="20" value="3">
      </div>
      <div class="gu-slider-row">
        <div class="gu-slider-label"><span>Hız (px/s)</span><span id="gu-v-speed-val">2000</span></div>
        <input class="gu-range" type="range" id="gu-v-speed" min="50" max="5000" value="2000">
      </div>
      <div class="gu-mode-info">Marching Squares ile kenar tespiti. Kenarları takip eden sürekli çizgiler üretir.</div>
    </div>

    <button class="gu-btn" id="gu-draw-img">▶ ÇİZ</button>
    <button class="gu-btn" id="gu-stop">⛔ DURDUR</button>
    <button class="gu-btn" id="gu-resume">▶ DEVAM ET</button>
    <button class="gu-btn" id="gu-clear">🗑 Tuvali Temizle</button>
    <div id="gu-status">hazır</div>

  </div>
`;

document.body.appendChild(panel);

/* ─── event bağlantıları ─────────────────────────────────── */

makeDraggable(panel);
document.getElementById("gu-min-btn").addEventListener("click", toggleMinimize);

const modeSelect   = document.getElementById("gu-mode");
const modeDesc     = document.getElementById("gu-mode-desc");
const stepRow      = document.getElementById("gu-step-row");
const quadRow      = document.getElementById("gu-quad-row");
const thrRow       = document.getElementById("gu-thr-row");
const conRow       = document.getElementById("gu-con-row");
const skinSection  = document.getElementById("gu-skin-section");
const fastControls = document.getElementById("gu-fast-controls");
const vecControls  = document.getElementById("gu-vector-controls");

const modeInfos = {
  classic: "Klasik satır tabanlı dolgu. Threshold + kontrast ayarları ile kontrol edilir.",
  fast:    "Adaptif adım + satır birleştirme. Hız ve kalite dengesi.",
  quad:    "İçi dolu dikdörtgen (op2). Piksel mükemmel bloklar.",
  vector:  "Marching Squares ile kenar tespiti. Kenarları takip eden sürekli çizgiler."
};

modeSelect.addEventListener("change", () => {
  const m = modeSelect.value;
  modeDesc.textContent = modeInfos[m] || "";
  const isQuad=m==='quad', isFast=m==='fast', isVec=m==='vector', isCls=m==='classic';
  fastControls.style.display = (!isVec) ? 'block' : 'none';
  vecControls.style.display  = isVec   ? 'block' : 'none';
  stepRow.style.display      = (isFast||isCls) ? 'block' : 'none';
  quadRow.style.display      = isQuad  ? 'block' : 'none';
  thrRow.style.display       = (isFast||isCls) ? 'block' : 'none';
  conRow.style.display       = (isFast||isCls) ? 'block' : 'none';
  skinSection.style.display  = isFast  ? 'block' : 'none';
});

// slider etiketleri
const sliders = [
  ["gu-thr","gu-thr-val",v=>v],
  ["gu-con","gu-con-val",v=>parseFloat(v).toFixed(1)],
  ["gu-img-step","gu-img-step-val",v=>v],
  ["gu-quad-depth","gu-quad-depth-val",v=>v],
  ["gu-quad-var","gu-quad-var-val",v=>v],
  ["gu-v-thr","gu-v-thr-val",v=>v],
  ["gu-v-blur","gu-v-blur-val",v=>v],
  ["gu-v-cell","gu-v-cell-val",v=>v],
  ["gu-v-speed","gu-v-speed-val",v=>v],
];
sliders.forEach(([id,valId,fmt])=>{
  document.getElementById(id).addEventListener("input",function(){ document.getElementById(valId).textContent=fmt(this.value); });
});

const imgDrop    = document.getElementById("gu-img-drop");
const imgInput   = document.getElementById("gu-img-input");
const imgPreview = document.getElementById("gu-img-preview");

function openFilePicker(){ imgInput.value=""; imgInput.click(); }

imgDrop.addEventListener("click", openFilePicker);
imgInput.addEventListener("change", e=>{
  const f=e.target.files&&e.target.files[0];
  if (f&&f.type.startsWith("image/")) loadPreview(f);
  else if (f) setStatus("❌ Geçerli bir resim seçin",false);
});
imgDrop.addEventListener("dragover",e=>{e.preventDefault();e.stopPropagation();imgDrop.classList.add("drag");});
imgDrop.addEventListener("dragleave",e=>{e.preventDefault();e.stopPropagation();imgDrop.classList.remove("drag");});
imgDrop.addEventListener("drop",e=>{
  e.preventDefault();e.stopPropagation();imgDrop.classList.remove("drag");
  const f=e.dataTransfer.files&&e.dataTransfer.files[0];
  if (f&&f.type.startsWith("image/")) loadPreview(f);
  else if (f) setStatus("❌ Sadece resim desteklenir",false);
});
document.addEventListener("paste",e=>{
  if (!e.clipboardData) return;
  const items=e.clipboardData.items||[];
  for (let i=0;i<items.length;i++) {
    if (items[i].type.startsWith("image/")){const f=items[i].getAsFile();if(f){loadPreview(f);break;}}
  }
});

let loadedImg = null;

function loadPreview(file) {
  setStatus("⏳ Yükleniyor...",true);
  const reader=new FileReader();
  reader.onerror=()=>setStatus("❌ Okunamadı",false);
  reader.onload=ev=>{
    const img=new Image();
    img.onload=()=>{
      loadedImg=img; imgPreview.src=ev.target.result;
      imgPreview.style.display="block"; imgDrop.style.display="none";
      document.getElementById("gu-img-actions").style.display="block";
      document.getElementById("gu-img-url-status").textContent="";
      setStatus(`📷 ${(file.name||'resim').slice(0,22)}`,false);
    };
    img.onerror=()=>setStatus("❌ Render hatası",false);
    img.src=ev.target.result;
  };
  reader.readAsDataURL(file);
}

function loadPreviewFromUrl(url) {
  const s=document.getElementById("gu-img-url-status");
  s.textContent="⏳ Yükleniyor..."; s.style.color="#f4c06a";
  const img=new Image(); img.crossOrigin="anonymous";
  img.onload=()=>{
    loadedImg=img; imgPreview.src=url; imgPreview.style.display="block";
    imgDrop.style.display="none";
    document.getElementById("gu-img-actions").style.display="block";
    s.textContent="✅ Yüklendi"; s.style.color="#4dffd6";
    setStatus("🔗 URL'den yüklendi",false);
  };
  img.onerror=()=>{s.textContent="❌ CORS hatası"; s.style.color="#ff6b6b";};
  img.src=url;
}

document.getElementById("gu-img-remove").addEventListener("click",()=>{
  loadedImg=null; imgPreview.src=""; imgPreview.style.display="none";
  imgDrop.style.display="flex";
  document.getElementById("gu-img-actions").style.display="none";
  document.getElementById("gu-img-url").value="";
  document.getElementById("gu-img-url-status").textContent="";
  imgInput.value="";
  setStatus("resim kaldırıldı",false);
});

document.getElementById("gu-img-url-load").addEventListener("click",()=>{
  const url=document.getElementById("gu-img-url").value.trim();
  if (url) loadPreviewFromUrl(url);
});
document.getElementById("gu-img-url").addEventListener("keydown",e=>{
  if (e.key==="Enter") document.getElementById("gu-img-url-load").click();
});

document.getElementById("gu-draw-img").addEventListener("click",()=>{
  if (!loadedImg){setStatus("📂 önce resim yükle",false);return;}
  const mode=document.getElementById("gu-mode").value;
  if (mode==='vector') {
    drawImage(loadedImg,mode,0,0,0,false,false,0,0,{
      threshold: parseInt(document.getElementById("gu-v-thr").value),
      blur:      parseFloat(document.getElementById("gu-v-blur").value),
      cellSize:  parseInt(document.getElementById("gu-v-cell").value),
      speed:     parseInt(document.getElementById("gu-v-speed").value)
    });
    return;
  }
  CFG.delay = parseInt(document.getElementById("gu-img-delay").value)||120;
  drawImage(
    loadedImg, mode,
    parseInt(document.getElementById("gu-thr").value),
    parseFloat(document.getElementById("gu-con").value),
    parseInt(document.getElementById("gu-img-step").value),
    document.getElementById("gu-color-mode").checked,
    document.getElementById("gu-skip-skin").checked,
    parseInt(document.getElementById("gu-quad-depth").value),
    parseInt(document.getElementById("gu-quad-var").value),
    null
  );
});

document.getElementById("gu-stop").addEventListener("click", stopDrawing);
document.getElementById("gu-resume").addEventListener("click", resumeDrawing);
document.getElementById("gu-clear").addEventListener("click", clearCanvas);

})();