From f7d2353d8fa56fdbfb7441ca161f336423420670 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Thu, 5 Feb 2026 11:15:14 +0500 Subject: [PATCH] docs: add reference design --- reference/index.html | 250 ++++++++++++++ reference/script.js | 570 ++++++++++++++++++++++++++++++++ reference/styles.css | 763 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1583 insertions(+) create mode 100644 reference/index.html create mode 100644 reference/script.js create mode 100644 reference/styles.css diff --git a/reference/index.html b/reference/index.html new file mode 100644 index 0000000..ca6d3ec --- /dev/null +++ b/reference/index.html @@ -0,0 +1,250 @@ + + + + + + Twitch Panels Creator + + + +
+
+
+

Twitch Panels

+ +
+
+ +
+ +
+
+

Тексты панелей

+ +
+ + +
+ +
+
+ +
+

Настройки текста

+ +
+
+ +
+ + 18 +
+
+ +
+ + +
+ +
+ +
+ + #ffffff +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + 10 +
+
+ +
+ +
+ + 0 +
+
+
+
+
+ + +
+
+

Фоновое изображение

+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + +
+ +
+
+ +
+ + 100 +
+
+ +
+ +
+ + 100 +
+
+
+
+
+ +
+
+

Панели 0

+
+ + 0 / 0 + +
+
+ +
+
+
+ + + + +

Добавьте тексты для создания панелей

+
+
+ +
+ + +
+
+
+
+
+
+ + + + diff --git a/reference/script.js b/reference/script.js new file mode 100644 index 0000000..8955e59 --- /dev/null +++ b/reference/script.js @@ -0,0 +1,570 @@ +// State +let texts = []; +let panels = []; +let currentPanelIndex = 0; +let backgroundImage = null; +let originalImage = null; +let settings = { + fontSize: 18, + fontFamily: "Arial", + textColor: "#ffffff", + alignment: "left", + sidePadding: 10, + centerOffset: 0, + bgBrightness: 100, + bgContrast: 100, +}; + +// Crop state +let cropBox = { + x: 0, + y: 0, + width: 0, + height: 0, +}; +let isDragging = false; +let isResizing = false; +let resizeHandle = null; +let dragStart = { x: 0, y: 0 }; +let cropBoxStart = { x: 0, y: 0, width: 0, height: 0 }; + +// Initialize +document.addEventListener("DOMContentLoaded", () => { + initializeTheme(); + initializeEventListeners(); + loadDefaultBackground(); + addDefaultTexts(); +}); + +// Theme Management +function initializeTheme() { + const savedTheme = localStorage.getItem("theme") || "light"; + document.documentElement.setAttribute("data-theme", savedTheme); +} + +function toggleTheme() { + const currentTheme = document.documentElement.getAttribute("data-theme"); + const newTheme = currentTheme === "light" ? "dark" : "light"; + document.documentElement.setAttribute("data-theme", newTheme); + localStorage.setItem("theme", newTheme); +} + +// Event Listeners +function initializeEventListeners() { + // Theme toggle + document.getElementById("themeToggle").addEventListener("click", toggleTheme); + + // Add text + document.getElementById("addTextBtn").addEventListener("click", addText); + document.getElementById("panelTextInput").addEventListener("keypress", (e) => { + if (e.key === "Enter") addText(); + }); + + // Settings + document.getElementById("fontSize").addEventListener("input", (e) => { + settings.fontSize = parseInt(e.target.value); + document.getElementById("fontSizeValue").textContent = e.target.value; + updateAllPanels(); + }); + + document.getElementById("fontFamily").addEventListener("change", (e) => { + settings.fontFamily = e.target.value; + updateAllPanels(); + }); + + document.getElementById("textColor").addEventListener("input", (e) => { + settings.textColor = e.target.value; + document.getElementById("textColorValue").textContent = e.target.value; + updateAllPanels(); + }); + + document.querySelectorAll(".align-btn").forEach((btn) => { + btn.addEventListener("click", (e) => { + document.querySelectorAll(".align-btn").forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + settings.alignment = btn.dataset.align; + updateAllPanels(); + }); + }); + + document.getElementById("sidePadding").addEventListener("input", (e) => { + settings.sidePadding = parseInt(e.target.value); + document.getElementById("sidePaddingValue").textContent = e.target.value; + updateAllPanels(); + }); + + document.getElementById("centerOffset").addEventListener("input", (e) => { + settings.centerOffset = parseInt(e.target.value); + document.getElementById("centerOffsetValue").textContent = e.target.value; + updateAllPanels(); + }); + + // Background controls + document.getElementById("uploadBgBtn").addEventListener("click", () => { + document.getElementById("bgImageInput").click(); + }); + + document.getElementById("bgImageInput").addEventListener("change", handleBackgroundUpload); + + document.getElementById("bgBrightness").addEventListener("input", (e) => { + settings.bgBrightness = parseInt(e.target.value); + document.getElementById("bgBrightnessValue").textContent = e.target.value; + drawCropCanvas(); + updateAllPanels(); + }); + + document.getElementById("bgContrast").addEventListener("input", (e) => { + settings.bgContrast = parseInt(e.target.value); + document.getElementById("bgContrastValue").textContent = e.target.value; + drawCropCanvas(); + updateAllPanels(); + }); + + document.getElementById("resetCropBtn").addEventListener("click", resetCrop); + + // Crop canvas events + const canvas = document.getElementById("cropCanvas"); + canvas.addEventListener("mousedown", handleCropMouseDown); + canvas.addEventListener("mousemove", handleCropMouseMove); + canvas.addEventListener("mouseup", handleCropMouseUp); + canvas.addEventListener("mouseleave", handleCropMouseUp); + + // Panel navigation + document.getElementById("prevPanel").addEventListener("click", () => { + if (currentPanelIndex > 0) { + currentPanelIndex--; + displayCurrentPanel(); + } + }); + + document.getElementById("nextPanel").addEventListener("click", () => { + if (currentPanelIndex < panels.length - 1) { + currentPanelIndex++; + displayCurrentPanel(); + } + }); + + document.getElementById("downloadCurrentBtn").addEventListener("click", downloadCurrentPanel); + document.getElementById("downloadAllBtn").addEventListener("click", downloadAllPanels); +} + +// Load default background +function loadDefaultBackground() { + const img = new Image(); + img.crossOrigin = "anonymous"; + img.onload = () => { + originalImage = img; + backgroundImage = img; + initializeCropBox(); + drawCropCanvas(); + updateAllPanels(); + }; + img.src = "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=800&h=250&fit=crop"; +} + +// Add default texts +function addDefaultTexts() { + const defaultTexts = ["links", "about me", "projects"]; + defaultTexts.forEach((text) => { + texts.push({ id: Date.now() + Math.random(), text }); + }); + renderTexts(); + updateAllPanels(); +} + +// Add text +function addText() { + const input = document.getElementById("panelTextInput"); + const text = input.value.trim(); + + if (!text) return; + + texts.push({ id: Date.now(), text }); + input.value = ""; + + renderTexts(); + updateAllPanels(); +} + +// Render texts list +function renderTexts() { + const container = document.getElementById("textsList"); + + if (texts.length === 0) { + container.innerHTML = ` +
+ + + +

Добавьте тексты

+
+ `; + return; + } + + container.innerHTML = texts + .map( + (item) => ` +
+ + +
+ `, + ) + .join(""); +} + +// Update text +window.updateText = function (id, newText) { + const item = texts.find((t) => t.id === id); + if (item) { + item.text = newText; + updateAllPanels(); + } +}; + +// Delete text +window.deleteText = function (id) { + texts = texts.filter((t) => t.id !== id); + renderTexts(); + updateAllPanels(); +}; +// Handle background upload +function handleBackgroundUpload(e) { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + const img = new Image(); + img.onload = () => { + originalImage = img; + backgroundImage = img; + initializeCropBox(); + drawCropCanvas(); + updateAllPanels(); + }; + img.src = event.target.result; + }; + reader.readAsDataURL(file); +} + +// Initialize crop box +function initializeCropBox() { + const canvas = document.getElementById("cropCanvas"); + const container = document.getElementById("cropCanvasContainer"); + const rect = container.getBoundingClientRect(); + + canvas.width = rect.width; + canvas.height = rect.height; + + // Set crop box to full canvas initially + cropBox = { + x: 0, + y: 0, + width: canvas.width, + height: canvas.height, + }; + + updateCropBoxElement(); + document.getElementById("cropBox").classList.add("active"); +} + +// Draw crop canvas +function drawCropCanvas() { + const canvas = document.getElementById("cropCanvas"); + const ctx = canvas.getContext("2d"); + + if (!originalImage) return; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Apply filters + ctx.filter = `brightness(${settings.bgBrightness}%) contrast(${settings.bgContrast}%)`; + + // Draw image to fit canvas + const scale = Math.max(canvas.width / originalImage.width, canvas.height / originalImage.height); + const scaledWidth = originalImage.width * scale; + const scaledHeight = originalImage.height * scale; + const x = (canvas.width - scaledWidth) / 2; + const y = (canvas.height - scaledHeight) / 2; + + ctx.drawImage(originalImage, x, y, scaledWidth, scaledHeight); + ctx.filter = "none"; +} + +// Update crop box element +function updateCropBoxElement() { + const element = document.getElementById("cropBox"); + element.style.left = cropBox.x + "px"; + element.style.top = cropBox.y + "px"; + element.style.width = cropBox.width + "px"; + element.style.height = cropBox.height + "px"; +} + +// Crop mouse handlers +function handleCropMouseDown(e) { + const rect = e.target.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + // Check if clicking on a handle + const handles = document.querySelectorAll(".crop-handle"); + let clickedHandle = null; + + handles.forEach((handle) => { + const handleRect = handle.getBoundingClientRect(); + const handleX = handleRect.left - rect.left + handleRect.width / 2; + const handleY = handleRect.top - rect.top + handleRect.height / 2; + + if (Math.abs(x - handleX) < 10 && Math.abs(y - handleY) < 10) { + clickedHandle = handle.classList[1]; // Get handle class (nw, ne, etc.) + } + }); + + if (clickedHandle) { + isResizing = true; + resizeHandle = clickedHandle; + } else if (x >= cropBox.x && x <= cropBox.x + cropBox.width && y >= cropBox.y && y <= cropBox.y + cropBox.height) { + isDragging = true; + } + + dragStart = { x, y }; + cropBoxStart = { ...cropBox }; +} + +function handleCropMouseMove(e) { + if (!isDragging && !isResizing) return; + + const rect = e.target.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const dx = x - dragStart.x; + const dy = y - dragStart.y; + + if (isDragging) { + cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, rect.width - cropBox.width)); + cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, rect.height - cropBox.height)); + } else if (isResizing) { + const minSize = 50; + + switch (resizeHandle) { + case "nw": + cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize)); + cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize)); + cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); + cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); + break; + case "ne": + cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize)); + cropBox.width = Math.max(minSize, Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x)); + cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); + break; + case "sw": + cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize)); + cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); + cropBox.height = Math.max(minSize, Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y)); + break; + case "se": + cropBox.width = Math.max(minSize, Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x)); + cropBox.height = Math.max(minSize, Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y)); + break; + case "n": + cropBox.y = Math.max(0, Math.min(cropBoxStart.y + dy, cropBoxStart.y + cropBoxStart.height - minSize)); + cropBox.height = cropBoxStart.height - (cropBox.y - cropBoxStart.y); + break; + case "s": + cropBox.height = Math.max(minSize, Math.min(cropBoxStart.height + dy, rect.height - cropBoxStart.y)); + break; + case "w": + cropBox.x = Math.max(0, Math.min(cropBoxStart.x + dx, cropBoxStart.x + cropBoxStart.width - minSize)); + cropBox.width = cropBoxStart.width - (cropBox.x - cropBoxStart.x); + break; + case "e": + cropBox.width = Math.max(minSize, Math.min(cropBoxStart.width + dx, rect.width - cropBoxStart.x)); + break; + } + } + + updateCropBoxElement(); +} + +function handleCropMouseUp() { + if (isDragging || isResizing) { + applyCrop(); + } + isDragging = false; + isResizing = false; + resizeHandle = null; +} + +// Apply crop +function applyCrop() { + if (!originalImage) return; + + const canvas = document.getElementById("cropCanvas"); + const tempCanvas = document.createElement("canvas"); + const tempCtx = tempCanvas.getContext("2d"); + + // Calculate scale factor + const scale = Math.max(canvas.width / originalImage.width, canvas.height / originalImage.height); + const scaledWidth = originalImage.width * scale; + const scaledHeight = originalImage.height * scale; + const offsetX = (canvas.width - scaledWidth) / 2; + const offsetY = (canvas.height - scaledHeight) / 2; + + // Calculate crop area in original image coordinates + const cropX = (cropBox.x - offsetX) / scale; + const cropY = (cropBox.y - offsetY) / scale; + const cropWidth = cropBox.width / scale; + const cropHeight = cropBox.height / scale; + + tempCanvas.width = cropWidth; + tempCanvas.height = cropHeight; + + tempCtx.drawImage(originalImage, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight); + + const img = new Image(); + img.onload = () => { + backgroundImage = img; + updateAllPanels(); + }; + img.src = tempCanvas.toDataURL(); +} + +// Reset crop +function resetCrop() { + loadDefaultBackground(); +} +// Update all panels +function updateAllPanels() { + panels = texts.map((item) => ({ + id: item.id, + text: item.text, + })); + + currentPanelIndex = Math.min(currentPanelIndex, Math.max(0, panels.length - 1)); + displayCurrentPanel(); + updatePanelNavigation(); +} + +// Display current panel +function displayCurrentPanel() { + const container = document.getElementById("panelDisplay"); + + if (panels.length === 0) { + container.innerHTML = ` +
+ + + + +

Добавьте тексты для создания панелей

+
+ `; + return; + } + + const panel = panels[currentPanelIndex]; + container.innerHTML = ``; + + setTimeout(() => { + drawPanel(panel, "currentPanelCanvas"); + }, 0); +} + +// Update panel navigation +function updatePanelNavigation() { + document.getElementById("panelCount").textContent = panels.length; + document.getElementById("panelIndicator").textContent = + panels.length > 0 ? `${currentPanelIndex + 1} / ${panels.length}` : "0 / 0"; + + document.getElementById("prevPanel").disabled = currentPanelIndex === 0 || panels.length === 0; + document.getElementById("nextPanel").disabled = currentPanelIndex >= panels.length - 1 || panels.length === 0; + document.getElementById("downloadCurrentBtn").disabled = panels.length === 0; + document.getElementById("downloadAllBtn").disabled = panels.length === 0; +} + +// Draw panel +function drawPanel(panel, canvasId) { + const canvas = document.getElementById(canvasId); + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + canvas.width = 320; + canvas.height = 100; + + // Draw background + if (backgroundImage) { + ctx.filter = `brightness(${settings.bgBrightness}%) contrast(${settings.bgContrast}%)`; + + const scale = Math.max(canvas.width / backgroundImage.width, canvas.height / backgroundImage.height); + const scaledWidth = backgroundImage.width * scale; + const scaledHeight = backgroundImage.height * scale; + const x = (canvas.width - scaledWidth) / 2; + const y = (canvas.height - scaledHeight) / 2; + + ctx.drawImage(backgroundImage, x, y, scaledWidth, scaledHeight); + ctx.filter = "none"; + } else { + ctx.fillStyle = "#667eea"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + // Draw text + ctx.font = `${settings.fontSize}px ${settings.fontFamily}`; + ctx.fillStyle = settings.textColor; + ctx.textBaseline = "middle"; + + const text = panel.text; + + let x; + if (settings.alignment === "left") { + x = settings.sidePadding; + ctx.textAlign = "left"; + } else if (settings.alignment === "center") { + x = canvas.width / 2 + settings.centerOffset; + ctx.textAlign = "center"; + } else { + x = canvas.width - settings.sidePadding; + ctx.textAlign = "right"; + } + + const y = canvas.height / 2; + ctx.fillText(text, x, y); +} + +// Download current panel +function downloadCurrentPanel() { + if (panels.length === 0) return; + + const canvas = document.getElementById("currentPanelCanvas"); + const panel = panels[currentPanelIndex]; + + if (canvas && panel) { + const link = document.createElement("a"); + link.download = `${panel.text}.png`; + link.href = canvas.toDataURL(); + link.click(); + } +} + +// Download all panels +function downloadAllPanels() { + panels.forEach((panel, index) => { + setTimeout(() => { + const tempCanvas = document.createElement("canvas"); + tempCanvas.id = `temp-canvas-${panel.id}`; + document.body.appendChild(tempCanvas); + + drawPanel(panel, tempCanvas.id); + + const link = document.createElement("a"); + link.download = `${panel.text}.png`; + link.href = tempCanvas.toDataURL(); + link.click(); + + document.body.removeChild(tempCanvas); + }, 100 * index); + }); +} diff --git a/reference/styles.css b/reference/styles.css new file mode 100644 index 0000000..13b8474 --- /dev/null +++ b/reference/styles.css @@ -0,0 +1,763 @@ +:root { + --bg-primary: #ffffff; + --bg-secondary: #fafafa; + --bg-card: #ffffff; + --bg-hover: #f5f5f5; + + --text-primary: #1a1a1a; + --text-secondary: #666666; + --text-tertiary: #999999; + + --border-color: #e5e5e5; + --border-hover: #d4d4d4; + + --accent-primary: #6366f1; + --accent-hover: #4f46e5; + --accent-light: #eef2ff; + + --danger: #ef4444; + + --shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); + + --radius: 8px; + --transition: all 0.15s ease; +} + +[data-theme="dark"] { + --bg-primary: #0a0a0a; + --bg-secondary: #1a1a1a; + --bg-card: #1a1a1a; + --bg-hover: #2a2a2a; + + --text-primary: #f5f5f5; + --text-secondary: #a3a3a3; + --text-tertiary: #737373; + + --border-color: #2a2a2a; + --border-hover: #3a3a3a; + + --accent-primary: #818cf8; + --accent-hover: #6366f1; + --accent-light: #312e81; + + --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; + padding: 16px; + transition: var(--transition); + line-height: 1.5; +} + +.container { + max-width: 1400px; + margin: 0 auto; +} + +/* Header */ +.header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 16px 20px; + border-radius: var(--radius); + margin-bottom: 16px; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.header h1 { + font-size: 20px; + font-weight: 600; +} + +.theme-toggle { + width: 36px; + height: 36px; + border-radius: 6px; + border: none; + background: rgba(255, 255, 255, 0.2); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); + position: relative; +} + +.theme-toggle:hover { + background: rgba(255, 255, 255, 0.3); +} + +.theme-toggle svg { + width: 18px; + height: 18px; + position: absolute; + transition: var(--transition); +} + +.sun-icon { + opacity: 1; + transform: rotate(0deg); +} + +.moon-icon { + opacity: 0; + transform: rotate(90deg); +} + +[data-theme="dark"] .sun-icon { + opacity: 0; + transform: rotate(90deg); +} + +[data-theme="dark"] .moon-icon { + opacity: 1; + transform: rotate(0deg); +} + +/* Main Grid */ +.main-grid { + display: grid; + grid-template-columns: 1fr; + gap: 16px; +} + +@media (min-width: 1024px) { + .main-grid { + grid-template-columns: 1fr 1fr; + } +} + +/* Card */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius); + padding: 16px; + margin-bottom: 16px; + transition: var(--transition); +} + +.card-title { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 8px; +} + +.card-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +/* Badge */ +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + background: var(--accent-light); + color: var(--accent-primary); + border-radius: 10px; + font-size: 11px; + font-weight: 600; +} + +/* Input Group */ +.input-group { + display: flex; + gap: 8px; + margin-bottom: 12px; +} + +.text-input { + flex: 1; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); + transition: var(--transition); + font-family: inherit; +} + +.text-input:focus { + outline: none; + border-color: var(--accent-primary); +} + +.text-input::placeholder { + color: var(--text-tertiary); +} + +/* Buttons */ +.btn { + padding: 10px 16px; + border: none; + border-radius: var(--radius); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 6px; + font-family: inherit; + white-space: nowrap; +} + +.btn svg { + width: 16px; + height: 16px; +} + +.btn-primary { + background: var(--accent-primary); + color: white; +} + +.btn-primary:hover:not(:disabled) { + background: var(--accent-hover); +} + +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-secondary { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + background: var(--bg-hover); +} + +.btn-outline { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border-color); +} + +.btn-outline:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--accent-primary); + color: var(--accent-primary); +} + +.btn-outline:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-delete { + background: var(--danger); + color: white; + padding: 6px 10px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + transition: var(--transition); + min-width: 32px; + display: flex; + align-items: center; + justify-content: center; +} + +.btn-delete:hover { + background: #dc2626; +} + +/* Texts List */ +.texts-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.text-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + transition: var(--transition); +} + +.text-item:hover { + border-color: var(--border-hover); +} + +.text-item input { + flex: 1; + padding: 6px 10px; + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); + transition: var(--transition); + font-family: inherit; +} + +.text-item input:focus { + outline: none; + border-color: var(--accent-primary); +} + +/* Settings */ +.settings-grid { + display: flex; + flex-direction: column; + gap: 12px; +} + +.setting-row { + display: grid; + grid-template-columns: 100px 1fr; + align-items: center; + gap: 12px; +} + +.setting-label { + color: var(--text-secondary); + font-size: 13px; + font-weight: 500; +} + +.setting-control { + display: flex; + align-items: center; + gap: 10px; +} + +.slider { + flex: 1; + height: 4px; + border-radius: 2px; + background: var(--bg-secondary); + outline: none; + -webkit-appearance: none; + appearance: none; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent-primary); + cursor: pointer; + transition: var(--transition); +} + +.slider::-webkit-slider-thumb:hover { + transform: scale(1.1); +} + +.slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent-primary); + cursor: pointer; + border: none; + transition: var(--transition); +} + +.slider::-moz-range-thumb:hover { + transform: scale(1.1); +} + +.value-display { + color: var(--text-primary); + font-weight: 500; + min-width: 40px; + text-align: right; + font-size: 13px; + padding: 4px 8px; + background: var(--bg-secondary); + border-radius: 4px; +} + +.select-input { + padding: 8px 10px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; + transition: var(--transition); + font-family: inherit; + width: 100%; +} + +.select-input:focus { + outline: none; + border-color: var(--accent-primary); +} + +.color-input { + width: 40px; + height: 32px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + cursor: pointer; + transition: var(--transition); + background: var(--bg-primary); +} + +.color-input:hover { + border-color: var(--accent-primary); +} + +.color-value { + font-family: 'Courier New', monospace; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + padding: 4px 8px; + background: var(--bg-secondary); + border-radius: 4px; +} + +.alignment-buttons { + display: flex; + gap: 6px; +} + +.align-btn { + flex: 1; + padding: 8px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + border-radius: var(--radius); + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; +} + +.align-btn svg { + width: 16px; + height: 16px; + stroke: var(--text-secondary); + transition: var(--transition); +} + +.align-btn:hover { + background: var(--bg-hover); + border-color: var(--accent-primary); +} + +.align-btn:hover svg { + stroke: var(--accent-primary); +} + +.align-btn.active { + background: var(--accent-primary); + border-color: var(--accent-primary); +} + +.align-btn.active svg { + stroke: white; +} + +/* Crop Editor */ +.crop-editor { + display: flex; + flex-direction: column; + gap: 12px; +} + +.crop-canvas-container { + position: relative; + width: 100%; + aspect-ratio: 16 / 5; + background: var(--bg-secondary); + border-radius: var(--radius); + overflow: hidden; + cursor: crosshair; +} + +.crop-canvas { + width: 100%; + height: 100%; + display: block; +} + +.crop-box { + position: absolute; + border: 2px solid var(--accent-primary); + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); + cursor: move; + display: none; +} + +.crop-box.active { + display: block; +} + +.crop-handle { + position: absolute; + width: 10px; + height: 10px; + background: white; + border: 2px solid var(--accent-primary); + border-radius: 50%; +} + +.crop-handle.nw { + top: -5px; + left: -5px; + cursor: nw-resize; +} + +.crop-handle.ne { + top: -5px; + right: -5px; + cursor: ne-resize; +} + +.crop-handle.sw { + bottom: -5px; + left: -5px; + cursor: sw-resize; +} + +.crop-handle.se { + bottom: -5px; + right: -5px; + cursor: se-resize; +} + +.crop-handle.n { + top: -5px; + left: 50%; + transform: translateX(-50%); + cursor: n-resize; +} + +.crop-handle.s { + bottom: -5px; + left: 50%; + transform: translateX(-50%); + cursor: s-resize; +} + +.crop-handle.w { + left: -5px; + top: 50%; + transform: translateY(-50%); + cursor: w-resize; +} + +.crop-handle.e { + right: -5px; + top: 50%; + transform: translateY(-50%); + cursor: e-resize; +} + +.crop-controls { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +/* Panel Viewer */ +.panel-nav { + display: flex; + align-items: center; + gap: 8px; +} + +.nav-btn { + width: 32px; + height: 32px; + padding: 0; + border: 1px solid var(--border-color); + background: var(--bg-primary); + border-radius: var(--radius); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); +} + +.nav-btn:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--accent-primary); +} + +.nav-btn:hover:not(:disabled) svg { + stroke: var(--accent-primary); +} + +.nav-btn:disabled { + opacity: 0.3; + cursor: not-allowed; +} + +.nav-btn svg { + width: 16px; + height: 16px; + stroke: var(--text-secondary); +} + +.panel-indicator { + font-size: 13px; + color: var(--text-secondary); + font-weight: 500; + min-width: 50px; + text-align: center; +} + +.panel-viewer { + display: flex; + flex-direction: column; + gap: 12px; +} + +.panel-display { + background: var(--bg-secondary); + border-radius: var(--radius); + padding: 20px; + display: flex; + justify-content: center; + align-items: center; + min-height: 150px; +} + +.panel-canvas { + border-radius: var(--radius); + display: block; + max-width: 100%; + box-shadow: var(--shadow-md); +} + +.panel-actions { + display: flex; + gap: 8px; +} + +.panel-actions .btn { + flex: 1; + justify-content: center; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 32px 16px; + color: var(--text-tertiary); +} + +.empty-state svg { + width: 48px; + height: 48px; + margin: 0 auto 12px; + opacity: 0.5; +} + +.empty-state p { + font-size: 14px; +} + +/* Responsive */ +@media (max-width: 768px) { + body { + padding: 12px; + } + + .header { + padding: 12px 16px; + } + + .header h1 { + font-size: 18px; + } + + .setting-row { + grid-template-columns: 80px 1fr; + gap: 10px; + } + + .crop-controls { + flex-direction: column; + } + + .crop-controls .btn { + width: 100%; + justify-content: center; + } +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.text-item { + animation: fadeIn 0.2s ease-out; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--border-hover); +}