Gartic Auto Draw Bot

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Gartic Auto Draw Bot
// @namespace    https://greatest.deepsurf.us/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);

})();