Hatasız Sekme Geçişli, Mini Önizlemeli Proje Geçmişi ve Uyumlu Renk Temaları
// ==UserScript==
// @name Zepp Bot Pixelplace
// @namespace http://tampermonkey.net/
// @version 27.1
// @description Hatasız Sekme Geçişli, Mini Önizlemeli Proje Geçmişi ve Uyumlu Renk Temaları
// @author Sen
// @match https://pixelplace.io/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const PRESETS = {
default: {
bg1: 'rgba(26, 27, 30, 0.85)', bg2: 'rgba(42, 43, 46, 0.95)', angle: '135', border: '#3e4044', text: '#ffffff'
},
discordDark: {
bg1: 'rgba(49, 51, 56, 0.85)', bg2: 'rgba(43, 45, 49, 0.95)', angle: '135', border: '#1f2023', text: '#dbdee1'
},
cyberpunkNeon: {
bg1: 'rgba(18, 0, 36, 0.85)', bg2: 'rgba(64, 0, 48, 0.95)', angle: '135', border: '#ff007f', text: '#00ffff'
},
sunsetVibe: {
bg1: 'rgba(60, 20, 50, 0.85)', bg2: 'rgba(30, 10, 40, 0.95)', angle: '135', border: '#ff66b2', text: '#ffccf9'
},
matrixTerminal: {
bg1: 'rgba(3, 20, 3, 0.9)', bg2: 'rgba(6, 41, 6, 0.95)', angle: '135', border: '#00ff00', text: '#00ff00'
},
oceanAbyss: {
bg1: 'rgba(1, 22, 39, 0.85)', bg2: 'rgba(46, 196, 182, 0.85)', angle: '135', border: '#011627', text: '#fdfffc'
},
emeraldForest: {
bg1: 'rgba(27, 67, 50, 0.85)', bg2: 'rgba(149, 213, 178, 0.85)', angle: '135', border: '#081c15', text: '#d8f3dc'
},
deepCosmos: {
bg1: 'rgba(15, 5, 30, 0.85)', bg2: 'rgba(35, 10, 60, 0.95)', angle: '135', border: '#9933ff', text: '#ffffff'
},
vaporwaveSynth: {
bg1: 'rgba(50, 10, 60, 0.85)', bg2: 'rgba(10, 40, 60, 0.95)', angle: '135', border: '#ff71ce', text: '#01cdfe'
},
sakuraBlossom: {
bg1: 'rgba(50, 20, 30, 0.85)', bg2: 'rgba(70, 30, 45, 0.95)', angle: '135', border: '#ff99bb', text: '#fff0f5'
},
custom: {
bg1: 'rgba(26, 27, 30, 0.85)', bg2: 'rgba(42, 43, 46, 0.95)', angle: '135', border: '#3e4044', text: '#ffffff'
}
};
const PIXELPLACE_PALETTE = [
'#ffffff', '#c4c4c4', '#888888', '#555555', '#222222', '#000000',
'#003638', '#006600', '#477050', '#22b14c', '#02be01', '#51e119',
'#94e044', '#98fb98', '#fbff5b', '#e5d900', '#e6be0c', '#e59500',
'#ff7000', '#ff3904', '#e50000', '#ce2939', '#ff416a', '#9f0000',
'#6b0000', '#ff755f', '#a06a42', '#633c1f', '#99530d', '#bb4f00',
'#ffc49f', '#ffdfcc', '#ffa7d1', '#cf6ee4', '#7d26cd', '#ec08ec',
'#820080', '#330077', '#020763', '#5100ff', '#0000ea', '#044bff',
'#005ba1', '#6583cf', '#36baff', '#0083c7', '#00d3dd', '#45ffc8', '#b5e8ee'
];
let scaleX = 1;
let rotationDeg = 0;
let extractedColorMap = {};
window.addEventListener('load', () => {
const imgContainer = document.createElement('div');
imgContainer.id = 'zepp-template-container';
imgContainer.style.position = 'absolute';
imgContainer.style.pointerEvents = 'none';
imgContainer.style.zIndex = '999';
imgContainer.style.opacity = '0.5';
const displayImg = document.createElement('img');
displayImg.style.display = 'block';
displayImg.style.pointerEvents = 'none';
displayImg.style.imageRendering = 'pixelated';
displayImg.style.transformOrigin = 'center center';
imgContainer.appendChild(displayImg);
let originalWidth = 0;
let originalHeight = 0;
const attachToCanvas = () => {
const board = document.querySelector('#board') || document.querySelector('canvas');
if (board && board.parentElement) {
board.parentElement.appendChild(imgContainer);
} else {
setTimeout(attachToCanvas, 1000);
}
};
attachToCanvas();
const mainBtn = document.createElement('button');
mainBtn.innerText = '🎨';
mainBtn.title = 'Zepp Bot Panelini Aç/Kapat';
mainBtn.style.position = 'fixed';
mainBtn.style.top = '0px';
mainBtn.style.left = '75px';
mainBtn.style.zIndex = '1000000';
mainBtn.style.width = '42px';
mainBtn.style.height = '42px';
mainBtn.style.borderRadius = '50%';
mainBtn.style.display = 'flex';
mainBtn.style.alignItems = 'center';
mainBtn.style.justifyContent = 'center';
mainBtn.style.boxSizing = 'border-box';
mainBtn.style.backgroundColor = '#2a2b2e';
mainBtn.style.color = '#fff';
mainBtn.style.border = '2px solid #3e4044';
mainBtn.style.cursor = 'pointer';
mainBtn.style.fontSize = '18px';
mainBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.5)';
mainBtn.style.outline = 'none';
document.body.appendChild(mainBtn);
const savedTop = localStorage.getItem('zepp_panel_top') || '46px';
const savedLeft = localStorage.getItem('zepp_panel_left') || '75px';
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.top = savedTop;
panel.style.left = savedLeft;
panel.style.padding = '10px';
panel.style.borderRadius = '8px';
panel.style.zIndex = '999999';
panel.style.fontFamily = 'sans-serif';
panel.style.fontSize = '11px';
panel.style.display = 'none';
panel.style.flexDirection = 'column';
panel.style.gap = '6px';
panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.6)';
panel.style.userSelect = 'none';
panel.style.width = '265px';
panel.style.backdropFilter = 'blur(14px) saturate(180%)';
panel.style.webkitBackdropFilter = 'blur(14px) saturate(180%)';
panel.innerHTML = `
<div id="drag-header" style="cursor: move; text-align: center; padding-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.2);">
<b>Zepp Bot Pixelplace v27.1</b>
</div>
<div style="display:flex; gap:2px; background:rgba(0,0,0,0.3); padding:2px; border-radius:4px;">
<button class="zepp-tab-btn active" data-tab="tab-main" style="flex:1; cursor:pointer; padding:4px 2px; border-radius:3px; font-size:9.5px;">🖼️ Şablon</button>
<button class="zepp-tab-btn" data-tab="tab-history" style="flex:1; cursor:pointer; padding:4px 2px; border-radius:3px; font-size:9.5px;">📜 Geçmiş</button>
<button class="zepp-tab-btn" data-tab="tab-filter" style="flex:1; cursor:pointer; padding:4px 2px; border-radius:3px; font-size:9.5px;">☀️ Filtre</button>
<button class="zepp-tab-btn" data-tab="tab-palette" style="flex:1; cursor:pointer; padding:4px 2px; border-radius:3px; font-size:9.5px;">🖌️ Palet</button>
<button class="zepp-tab-btn" data-tab="tab-theme" style="flex:1; cursor:pointer; padding:4px 2px; border-radius:3px; font-size:9.5px;">🎨 Tema</button>
</div>
<div id="tab-main" class="zepp-tab-content" style="display:flex; flex-direction:column; gap:6px;">
<label style="display:flex; flex-direction:column; gap:2px;">
<b>Dosya Seç:</b>
<input type="file" id="img-file" accept="image/*" style="font-size:10px;">
</label>
<label style="display:flex; flex-direction:column; gap:2px;">
<b>Veya URL:</b>
<input type="text" id="img-url" placeholder="https://..." class="zepp-input" style="width:100%; box-sizing:border-box; border-radius:3px; padding:3px;">
</label>
<div style="display:flex; gap:6px; align-items:center;">
<label>X: <input type="number" id="img-x" value="0" class="zepp-input" style="width:45px; border-radius:3px;"></label>
<label>Y: <input type="number" id="img-y" value="0" class="zepp-input" style="width:45px; border-radius:3px;"></label>
<button id="btn-center" class="zepp-btn" style="cursor:pointer; padding:3px 5px; border-radius:3px; font-size:10px;">🎯 Ortala</button>
</div>
<div style="display:flex; gap:4px;">
<button id="btn-flip" class="zepp-btn" style="flex:1; cursor:pointer; padding:3px; border-radius:3px;">🪞 Aynala</button>
<button id="btn-rotate" class="zepp-btn" style="flex:1; cursor:pointer; padding:3px; border-radius:3px;">🔄 Döndür</button>
</div>
<div style="display:flex; gap:6px; align-items:center;">
<label>Ölçek(%): <input type="number" id="img-scale" value="100" min="1" max="1000" class="zepp-input" style="width:50px; border-radius:3px;"></label>
<label>Opaklık: <input type="range" id="img-opacity" min="0.1" max="1" step="0.1" value="0.5" style="width:80px;"></label>
</div>
<div style="display:flex; gap:4px;">
<button id="btn-toggle-img" class="zepp-btn" style="flex:1; cursor:pointer; padding:3px; border-radius:3px;">Göster/Gizle</button>
<button id="btn-export" class="zepp-btn" style="cursor:pointer; padding:3px 5px; border-radius:3px; font-size:10px;">💾 Dışa Aktar</button>
<button id="btn-import" class="zepp-btn" style="cursor:pointer; padding:3px 5px; border-radius:3px; font-size:10px;">📂 İçe Aktar</button>
<input type="file" id="import-file" style="display:none" accept=".json">
<button id="btn-clear-save" class="zepp-btn" style="cursor:pointer; padding:3px 5px; border-radius:3px; background:#aa3333;" title="Sıfırla">🗑️</button>
</div>
<div id="status-msg" style="opacity:0.7; font-size:10px;">Hazır. (Kısayol: 8/2/4/6 ve +/-)</div>
</div>
<div id="tab-history" class="zepp-tab-content" style="display:none; flex-direction:column; gap:6px;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<b>Geçmiş & Mini Önizleme</b>
<button id="btn-add-history" class="zepp-btn" style="cursor:pointer; padding:2px 6px; border-radius:3px; font-size:9px;" title="Şuanki konumu ve resmi kaydet">➕ Konumu Kaydet</button>
</div>
<div id="history-list" style="display:flex; flex-direction:column; gap:4px; max-height:140px; overflow-y:auto; padding:3px; background:rgba(0,0,0,0.2); border-radius:4px;">
<span style="opacity:0.6; font-size:10px;">Henüz kayıtlı proje yok.</span>
</div>
<button id="btn-clear-history" class="zepp-btn" style="cursor:pointer; padding:3px; border-radius:3px; background:#aa3333; font-size:10px;">🗑️ Geçmişi Temizle</button>
</div>
<div id="tab-filter" class="zepp-tab-content" style="display:none; flex-direction:column; gap:6px;">
<label style="display:flex; justify-content:space-between; align-items:center;">
Parlaklık (%): <input type="range" id="filter-brightness" min="0" max="200" value="100" style="width:110px;">
</label>
<label style="display:flex; justify-content:space-between; align-items:center;">
Kontrast (%): <input type="range" id="filter-contrast" min="0" max="200" value="100" style="width:110px;">
</label>
<label style="display:flex; justify-content:space-between; align-items:center;">
Renk Tonu (°): <input type="range" id="filter-hue" min="0" max="360" value="0" style="width:110px;">
</label>
<label style="display:flex; justify-content:space-between; align-items:center; cursor:pointer;">
<span>Ters Çevir (Invert):</span> <input type="checkbox" id="filter-invert" style="cursor:pointer;">
</label>
<button id="btn-reset-filters" class="zepp-btn" style="cursor:pointer; padding:3px; border-radius:3px;">Filtreleri Sıfırla</button>
</div>
<div id="tab-palette" class="zepp-tab-content" style="display:none; flex-direction:column; gap:6px;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<span>Sıralama:</span>
<select id="palette-sort" class="zepp-input" style="font-size:10px; border-radius:3px; padding:2px;">
<option value="count">Kullanım Sıklığı</option>
<option value="hue">Renk Tonu (Hue)</option>
</select>
</div>
<button id="btn-copy-all" class="zepp-btn" style="cursor:pointer; padding:3px; border-radius:3px;">📋 Tüm Renkleri Kopyala</button>
<div id="color-palette" style="display:flex; flex-wrap:wrap; gap:3px; max-height:130px; overflow-y:auto; padding:3px; background:rgba(0,0,0,0.2); border-radius:4px;">
<span style="opacity:0.6; font-size:10px;">Resim bekleniyor...</span>
</div>
</div>
<div id="tab-theme" class="zepp-tab-content" style="display:none; flex-direction:column; gap:6px;">
<label style="display:flex; justify-content:space-between; align-items:center;">
Uyumlu Geçişli Tema:
<select id="theme-preset" class="zepp-input" style="border-radius:3px; padding:2px; font-size:10px;">
<option value="default">Karanlık (Varsayılan)</option>
<option value="discordDark">Discord Koyu Gri</option>
<option value="cyberpunkNeon">🌃 Cyberpunk Neon</option>
<option value="sunsetVibe">🌅 Gün Batımı</option>
<option value="matrixTerminal">💻 Matrix Yeşil</option>
<option value="oceanAbyss">🌊 Okyanus Derinliği</option>
<option value="emeraldForest">🌲 Zümrüt Orman</option>
<option value="deepCosmos">🌌 Derin Kozmos</option>
<option value="vaporwaveSynth">🌴 Vaporwave Synth</option>
<option value="sakuraBlossom">🌸 Sakura Çiçeği</option>
<option value="custom">Özel Renkler</option>
</select>
</label>
<div style="display:flex; justify-content:space-between; align-items:center;"><span>Geçiş Rengi 1:</span><input type="color" id="theme-bg1-color" value="#1a1b1e"></div>
<div style="display:flex; justify-content:space-between; align-items:center;"><span>Geçiş Rengi 2:</span><input type="color" id="theme-bg2-color" value="#2a2b2e"></div>
<div style="display:flex; justify-content:space-between; align-items:center;"><span>Geçiş Açısı (°):</span><input type="number" id="theme-angle" value="135" min="0" max="360" class="zepp-input" style="width:45px; border-radius:3px;"></div>
<div style="display:flex; justify-content:space-between; align-items:center;"><span>Yazı Rengi:</span><input type="color" id="theme-text-color" value="#ffffff"></div>
<div style="display:flex; justify-content:space-between; align-items:center;"><span>Kenarlık:</span><input type="color" id="theme-border-color" value="#3e4044"></div>
</div>
`;
document.body.appendChild(panel);
const tabBtns = panel.querySelectorAll('.zepp-tab-btn');
const tabContents = panel.querySelectorAll('.zepp-tab-content');
const presetSelect = panel.querySelector('#theme-preset');
const bg1Picker = panel.querySelector('#theme-bg1-color');
const bg2Picker = panel.querySelector('#theme-bg2-color');
const angleInput = panel.querySelector('#theme-angle');
const textPicker = panel.querySelector('#theme-text-color');
const borderPicker = panel.querySelector('#theme-border-color');
const applyTheme = (bg1, bg2, angle, textColor, borderColor, presetKey = 'custom') => {
let gradientBg = `linear-gradient(${angle}deg, ${bg1}, ${bg2})`;
panel.style.background = gradientBg;
panel.style.color = textColor;
panel.style.border = `1px solid ${borderColor}`;
mainBtn.style.backgroundColor = bg1;
mainBtn.style.border = `2px solid ${borderColor}`;
tabBtns.forEach(b => {
if (b.classList.contains('active')) {
b.style.backgroundColor = borderColor;
b.style.color = textColor;
b.style.fontWeight = 'bold';
} else {
b.style.backgroundColor = 'rgba(0,0,0,0.2)';
b.style.color = textColor;
b.style.fontWeight = 'normal';
}
b.style.border = `1px solid ${borderColor}`;
});
panel.querySelectorAll('.zepp-input').forEach(i => { i.style.background = 'rgba(0,0,0,0.3)'; i.style.color = textColor; i.style.border = `1px solid ${borderColor}`; });
panel.querySelectorAll('.zepp-btn').forEach(b => { b.style.background = 'rgba(255,255,255,0.1)'; b.style.color = textColor; b.style.border = `1px solid ${borderColor}`; });
localStorage.setItem('zepp_theme_v27', JSON.stringify({ preset: presetKey, bg1, bg2, angle, text: textColor, border: borderColor }));
};
let savedTheme = PRESETS.default;
try {
const raw = localStorage.getItem('zepp_theme_v27');
if (raw) savedTheme = JSON.parse(raw);
} catch(e){}
presetSelect.value = savedTheme.preset || 'default';
bg1Picker.value = savedTheme.bg1 || PRESETS.default.bg1;
bg2Picker.value = savedTheme.bg2 || PRESETS.default.bg2;
angleInput.value = savedTheme.angle || '135';
textPicker.value = savedTheme.text || PRESETS.default.text;
borderPicker.value = savedTheme.border || PRESETS.default.border;
applyTheme(bg1Picker.value, bg2Picker.value, angleInput.value, textPicker.value, borderPicker.value, presetSelect.value);
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
tabBtns.forEach(b => b.classList.remove('active'));
tabContents.forEach(c => c.style.display = 'none');
btn.classList.add('active');
const targetTab = panel.querySelector(`#${btn.getAttribute('data-tab')}`);
if (targetTab) targetTab.style.display = 'flex';
applyTheme(bg1Picker.value, bg2Picker.value, angleInput.value, textPicker.value, borderPicker.value, presetSelect.value);
});
});
presetSelect.addEventListener('change', (e) => {
const theme = PRESETS[e.target.value];
if (theme) {
bg1Picker.value = theme.bg1;
bg2Picker.value = theme.bg2;
angleInput.value = theme.angle;
textPicker.value = theme.text;
borderPicker.value = theme.border;
applyTheme(theme.bg1, theme.bg2, theme.angle, theme.text, theme.border, e.target.value);
}
});
[bg1Picker, bg2Picker, angleInput, textPicker, borderPicker].forEach(p => {
p.addEventListener('input', () => {
presetSelect.value = 'custom';
applyTheme(bg1Picker.value, bg2Picker.value, angleInput.value, textPicker.value, borderPicker.value, 'custom');
});
});
const fileInput = panel.querySelector('#img-file');
const urlInput = panel.querySelector('#img-url');
const xInput = panel.querySelector('#img-x');
const yInput = panel.querySelector('#img-y');
const scaleInput = panel.querySelector('#img-scale');
const opacityInput = panel.querySelector('#img-opacity');
const toggleImgBtn = panel.querySelector('#btn-toggle-img');
const clearSaveBtn = panel.querySelector('#btn-clear-save');
const flipBtn = panel.querySelector('#btn-flip');
const rotateBtn = panel.querySelector('#btn-rotate');
const centerBtn = panel.querySelector('#btn-center');
const exportBtn = panel.querySelector('#btn-export');
const importBtn = panel.querySelector('#btn-import');
const importFile = panel.querySelector('#import-file');
const copyAllBtn = panel.querySelector('#btn-copy-all');
const statusMsg = panel.querySelector('#status-msg');
const paletteContainer = panel.querySelector('#color-palette');
const paletteSortSelect = panel.querySelector('#palette-sort');
const historyListContainer = panel.querySelector('#history-list');
const addHistoryBtn = panel.querySelector('#btn-add-history');
const clearHistoryBtn = panel.querySelector('#btn-clear-history');
const brightnessInput = panel.querySelector('#filter-brightness');
const contrastInput = panel.querySelector('#filter-contrast');
const hueInput = panel.querySelector('#filter-hue');
const invertCheckbox = panel.querySelector('#filter-invert');
const resetFiltersBtn = panel.querySelector('#btn-reset-filters');
const updateTransforms = () => {
displayImg.style.transform = `scaleX(${scaleX}) rotate(${rotationDeg}deg)`;
saveTemplateData();
};
flipBtn.addEventListener('click', () => { scaleX *= -1; updateTransforms(); });
rotateBtn.addEventListener('click', () => { rotationDeg = (rotationDeg + 90) % 360; updateTransforms(); });
centerBtn.addEventListener('click', () => {
const board = document.querySelector('#board') || document.querySelector('canvas');
if (board) {
const cx = Math.round((board.clientWidth - displayImg.width) / 2);
const cy = Math.round((board.clientHeight - displayImg.height) / 2);
xInput.value = cx >= 0 ? cx : 0;
yInput.value = cy >= 0 ? cy : 0;
imgContainer.style.left = `${xInput.value}px`;
imgContainer.style.top = `${yInput.value}px`;
saveTemplateData();
statusMsg.innerText = "Şablon ortalandı! 🎯";
}
});
window.addEventListener('keydown', (e) => {
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
const key = e.key;
let currentX = parseInt(xInput.value) || 0;
let currentY = parseInt(yInput.value) || 0;
let moved = false;
if (key === '8') { currentY--; moved = true; }
if (key === '2') { currentY++; moved = true; }
if (key === '4') { currentX--; moved = true; }
if (key === '6') { currentX++; moved = true; }
if (key === '0') { currentX = 0; currentY = 0; moved = true; }
if (moved) {
e.preventDefault();
xInput.value = currentX; yInput.value = currentY;
imgContainer.style.left = `${currentX}px`;
imgContainer.style.top = `${currentY}px`;
saveTemplateData();
}
if (key === '+' || key === '=') {
let op = Math.min(1, parseFloat(opacityInput.value) + 0.1);
opacityInput.value = op.toFixed(1); imgContainer.style.opacity = op; saveTemplateData();
}
if (key === '-' || key === '_') {
let op = Math.max(0.1, parseFloat(opacityInput.value) - 0.1);
opacityInput.value = op.toFixed(1); imgContainer.style.opacity = op; saveTemplateData();
}
});
const renderHistory = () => {
historyListContainer.innerHTML = '';
let historyData = [];
try {
const raw = localStorage.getItem('zepp_history_v27');
if (raw) historyData = JSON.parse(raw);
} catch(e){}
if (historyData.length === 0) {
historyListContainer.innerHTML = '<span style="opacity:0.6; font-size:10px;">Henüz kayıtlı proje yok.</span>';
return;
}
historyData.forEach((item, index) => {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.justifyContent = 'space-between';
row.style.alignItems = 'center';
row.style.padding = '3px 5px';
row.style.background = 'rgba(255,255,255,0.05)';
row.style.borderRadius = '3px';
row.style.border = '1px solid rgba(255,255,255,0.1)';
row.style.cursor = 'pointer';
const leftWrapper = document.createElement('div');
leftWrapper.style.display = 'flex';
leftWrapper.style.alignItems = 'center';
leftWrapper.style.gap = '6px';
leftWrapper.style.flex = '1';
const thumb = document.createElement('img');
thumb.src = item.src || '';
thumb.style.width = '22px';
thumb.style.height = '22px';
thumb.style.objectFit = 'cover';
thumb.style.borderRadius = '2px';
thumb.style.border = '1px solid rgba(255,255,255,0.2)';
thumb.style.imageRendering = 'pixelated';
const info = document.createElement('span');
info.style.fontSize = '9.5px';
info.innerText = `X: ${item.x}, Y: ${item.y} (${item.date || 'Proje'})`;
leftWrapper.appendChild(thumb);
leftWrapper.appendChild(info);
leftWrapper.addEventListener('click', () => {
xInput.value = item.x;
yInput.value = item.y;
scaleInput.value = item.scale || 100;
opacityInput.value = item.opacity || 0.5;
imgContainer.style.left = `${item.x}px`;
imgContainer.style.top = `${item.y}px`;
imgContainer.style.opacity = item.opacity || 0.5;
scaleX = item.scaleX !== undefined ? item.scaleX : 1;
rotationDeg = item.rotationDeg || 0;
updateTransforms();
if (item.src) {
displayImg.src = item.src;
displayImg.style.display = 'block';
}
statusMsg.innerText = `Proje yüklendi: X:${item.x}, Y:${item.y} 🚀`;
saveTemplateData();
});
const delBtn = document.createElement('button');
delBtn.innerText = '❌';
delBtn.style.cursor = 'pointer';
delBtn.style.background = 'transparent';
delBtn.style.border = 'none';
delBtn.style.fontSize = '9px';
delBtn.title = 'Bu kaydı sil';
delBtn.addEventListener('click', (e) => {
e.stopPropagation();
historyData.splice(index, 1);
localStorage.setItem('zepp_history_v27', JSON.stringify(historyData));
renderHistory();
});
row.appendChild(leftWrapper);
row.appendChild(delBtn);
historyListContainer.appendChild(row);
});
};
addHistoryBtn.addEventListener('click', () => {
let historyData = [];
try {
const raw = localStorage.getItem('zepp_history_v27');
if (raw) historyData = JSON.parse(raw);
} catch(e){}
const now = new Date();
const dateStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
const newItem = {
src: displayImg.src || '',
x: xInput.value || '0',
y: yInput.value || '0',
scale: scaleInput.value || '100',
opacity: opacityInput.value || '0.5',
scaleX,
rotationDeg,
date: dateStr
};
historyData.unshift(newItem);
if (historyData.length > 15) historyData.pop();
localStorage.setItem('zepp_history_v27', JSON.stringify(historyData));
renderHistory();
statusMsg.innerText = "Konum ve önizleme kaydedildi! 📌";
});
clearHistoryBtn.addEventListener('click', () => {
localStorage.removeItem('zepp_history_v27');
renderHistory();
statusMsg.innerText = "Geçmiş temizlendi.";
});
const applyFilters = () => {
displayImg.style.filter = `brightness(${brightnessInput.value}%) contrast(${contrastInput.value}%) hue-rotate(${hueInput.value}deg) invert(${invertCheckbox.checked ? 100 : 0}%)`;
saveTemplateData();
};
brightnessInput.addEventListener('input', applyFilters);
contrastInput.addEventListener('input', applyFilters);
hueInput.addEventListener('input', applyFilters);
invertCheckbox.addEventListener('change', applyFilters);
resetFiltersBtn.addEventListener('click', () => {
brightnessInput.value = 100; contrastInput.value = 100; hueInput.value = 0; invertCheckbox.checked = false; applyFilters();
});
const hexToRgb = (hex) => {
let bigint = parseInt(hex.replace('#', ''), 16);
return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
};
const getColorDistance = (rgb1, rgb2) => Math.sqrt(Math.pow(rgb1[0]-rgb2[0], 2) + Math.pow(rgb1[1]-rgb2[1], 2) + Math.pow(rgb1[2]-rgb2[2], 2));
const findClosestPixelplaceColor = (hex) => {
let rgb = hexToRgb(hex);
let closest = PIXELPLACE_PALETTE[0];
let minDist = Infinity;
for (let pHex of PIXELPLACE_PALETTE) {
let dist = getColorDistance(rgb, hexToRgb(pHex));
if (dist < minDist) { minDist = dist; closest = pHex; }
}
return closest;
};
const hexToHue = (hex) => {
let rgb = hexToRgb(hex);
let r = rgb[0]/255, g = rgb[1]/255, b = rgb[2]/255;
let max = Math.max(r,g,b), min = Math.min(r,g,b);
let h, d = max - min;
if (max === min) h = 0;
else h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
return (h / 6) * 360;
};
const renderPalette = () => {
paletteContainer.innerHTML = '';
let colors = Object.keys(extractedColorMap);
if (colors.length === 0) {
paletteContainer.innerHTML = '<span style="opacity:0.6; font-size:10px;">Renk bulunamadı.</span>';
return;
}
if (paletteSortSelect.value === 'count') colors.sort((a, b) => extractedColorMap[b] - extractedColorMap[a]);
else colors.sort((a, b) => hexToHue(a) - hexToHue(b));
colors.slice(0, 40).forEach(hex => {
const mappedHex = findClosestPixelplaceColor(hex);
const swatch = document.createElement('div');
swatch.style.width = '20px'; swatch.style.height = '20px';
swatch.style.backgroundColor = mappedHex; swatch.style.borderRadius = '3px';
swatch.style.cursor = 'pointer'; swatch.style.border = '1px solid rgba(255,255,255,0.3)';
swatch.title = `Orijinal: ${hex} -> PP: ${mappedHex} (${extractedColorMap[hex]} piks)`;
swatch.addEventListener('click', () => {
navigator.clipboard.writeText(mappedHex);
swatch.style.border = '2px solid #55ff55';
setTimeout(() => swatch.style.border = '1px solid rgba(255,255,255,0.3)', 500);
});
paletteContainer.appendChild(swatch);
});
};
paletteSortSelect.addEventListener('change', renderPalette);
copyAllBtn.addEventListener('click', () => {
let colors = Object.keys(extractedColorMap);
if (colors.length === 0) return;
navigator.clipboard.writeText([...new Set(colors.map(h => findClosestPixelplaceColor(h)))].join(', '));
statusMsg.innerText = "Palet renkleri kopyalandı! 📋";
});
const saveTemplateData = () => {
const data = {
src: displayImg.src || '', x: xInput.value, y: yInput.value,
scale: scaleInput.value, opacity: opacityInput.value,
scaleX, rotationDeg, visible: displayImg.style.display !== 'none',
filters: { b: brightnessInput.value, c: contrastInput.value, h: hueInput.value, inv: invertCheckbox.checked }
};
try { localStorage.setItem('zepp_saved_template_v27', JSON.stringify(data)); } catch(e){}
};
const loadSavedTemplate = () => {
try {
const raw = localStorage.getItem('zepp_saved_template_v27');
if (!raw) return;
const data = JSON.parse(raw);
if (data.x !== undefined) { xInput.value = data.x; imgContainer.style.left = `${data.x}px`; }
if (data.y !== undefined) { yInput.value = data.y; imgContainer.style.top = `${data.y}px`; }
if (data.scale !== undefined) scaleInput.value = data.scale;
if (data.opacity !== undefined) { opacityInput.value = data.opacity; imgContainer.style.opacity = data.opacity; }
if (data.scaleX !== undefined) scaleX = data.scaleX;
if (data.rotationDeg !== undefined) rotationDeg = data.rotationDeg;
updateTransforms();
if (data.filters) {
brightnessInput.value = data.filters.b || 100;
contrastInput.value = data.filters.c || 100;
hueInput.value = data.filters.h || 0;
invertCheckbox.checked = !!data.filters.inv;
applyFilters();
}
if (data.src) {
displayImg.src = data.src;
displayImg.style.display = data.visible ? 'block' : 'none';
statusMsg.innerText = "Kayıt yüklendi! 💾";
}
} catch(e){}
};
exportBtn.addEventListener('click', () => {
const raw = localStorage.getItem('zepp_saved_template_v27');
if (!raw) return;
const blob = new Blob([raw], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = 'zepp_backup.json'; a.click();
URL.revokeObjectURL(url);
});
importBtn.addEventListener('click', () => importFile.click());
importFile.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
localStorage.setItem('zepp_saved_template_v27', ev.target.result);
loadSavedTemplate();
statusMsg.innerText = "Yedek yüklendi! ✅";
};
reader.readAsText(file);
});
const extractColors = (img) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth || img.width;
canvas.height = img.naturalHeight || img.height;
if (!canvas.width || !canvas.height) return;
ctx.drawImage(img, 0, 0);
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
extractedColorMap = {};
for (let i = 0; i < data.length; i += 4) {
if (data[i+3] < 50) continue;
let hex = `#${((1<<24) + (data[i]<<16) + (data[i+1]<<8) + data[i+2]).toString(16).slice(1).toUpperCase()}`;
extractedColorMap[hex] = (extractedColorMap[hex] || 0) + 1;
}
renderPalette();
} catch(e) {}
};
const dragHeader = panel.querySelector('#drag-header');
let isDragging = false, startX, startY, initialLeft, initialTop;
dragHeader.addEventListener('mousedown', (e) => {
isDragging = true; startX = e.clientX; startY = e.clientY;
initialLeft = panel.offsetLeft; initialTop = panel.offsetTop;
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
const newLeft = `${initialLeft + (e.clientX - startX)}px`;
const newTop = `${initialTop + (e.clientY - startY)}px`;
panel.style.left = newLeft; panel.style.top = newTop;
localStorage.setItem('zepp_panel_left', newLeft);
localStorage.setItem('zepp_panel_top', newTop);
}
});
document.addEventListener('mouseup', () => isDragging = false);
mainBtn.addEventListener('click', () => { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; });
const updateImageSize = () => {
if (!originalWidth || !originalHeight) return;
const scale = parseFloat(scaleInput.value) / 100;
const newW = Math.round(originalWidth * scale);
const newH = Math.round(originalHeight * scale);
displayImg.style.width = `${newW}px`;
displayImg.style.height = `${newH}px`;
saveTemplateData();
};
displayImg.addEventListener('load', () => {
originalWidth = displayImg.naturalWidth;
originalHeight = displayImg.naturalHeight;
updateImageSize();
extractColors(displayImg);
saveTemplateData();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (ev) => {
displayImg.src = ev.target.result;
displayImg.style.display = 'block';
statusMsg.innerText = "Resim yüklendi! ✅";
saveTemplateData();
};
reader.readAsDataURL(file);
}
});
urlInput.addEventListener('change', (e) => {
if (e.target.value) {
displayImg.crossOrigin = "Anonymous";
displayImg.src = e.target.value;
displayImg.style.display = 'block';
statusMsg.innerText = "URL yüklendi! ✅";
saveTemplateData();
}
});
xInput.addEventListener('input', (e) => { imgContainer.style.left = `${e.target.value}px`; saveTemplateData(); });
yInput.addEventListener('input', (e) => { imgContainer.style.top = `${e.target.value}px`; saveTemplateData(); });
scaleInput.addEventListener('input', updateImageSize);
opacityInput.addEventListener('input', (e) => { imgContainer.style.opacity = e.target.value; saveTemplateData(); });
toggleImgBtn.addEventListener('click', () => {
displayImg.style.display = displayImg.style.display === 'none' ? 'block' : 'none';
saveTemplateData();
});
clearSaveBtn.addEventListener('click', () => {
localStorage.removeItem('zepp_saved_template_v27');
displayImg.src = ''; displayImg.style.display = 'none';
paletteContainer.innerHTML = '<span style="opacity:0.6; font-size:10px;">Kayıt temizlendi.</span>';
statusMsg.innerText = "Sıfırlandı.";
});
loadSavedTemplate();
renderHistory();
});
})();