Initial commit

This commit is contained in:
Jonah
2026-07-06 00:46:21 -05:00
commit a281b7deeb
6 changed files with 2740 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
Thumbs.db
+326
View File
@@ -0,0 +1,326 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSG Image Upload Tool — CollisionStoneGuard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Header -->
<header id="app-header">
<div class="header-inner">
<div class="header-brand">
<img id="header-logo" src="watermark.png" alt="CSG" onerror="this.style.display='none'">
<div>
<h1>CSG Image Tool</h1>
<p>CollisionStoneGuard Product Photo Uploader</p>
</div>
</div>
<button id="btn-settings" class="btn-icon" title="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
</button>
</div>
</header>
<!-- Step Progress -->
<nav id="steps-nav">
<div class="step active" data-step="1">
<div class="step-num">1</div><span>Import</span>
</div>
<div class="step-connector"></div>
<div class="step" data-step="2">
<div class="step-num">2</div><span>Process</span>
</div>
<div class="step-connector"></div>
<div class="step" data-step="3">
<div class="step-num">3</div><span>Upload</span>
</div>
</nav>
<main id="app-main">
<!-- STEP 1: Import -->
<section id="step-1" class="step-panel active">
<div class="panel-header">
<h2>Import Photos</h2>
</div>
<div id="drop-zone">
<div class="drop-zone-inner">
<div class="drop-icon">
<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2">
<rect x="8" y="8" width="48" height="48" rx="4" />
<path d="M8 44l12-12 8 8 10-14 10 18" />
<circle cx="20" cy="24" r="4" />
</svg>
</div>
<h3>Drop photos here</h3>
<p>From your DPP4 output folder or any folder</p>
<div class="drop-actions">
<button id="btn-browse-files" class="btn-primary">Browse Files</button>
<button id="btn-browse-folder" class="btn-secondary">Browse Folder</button>
</div>
<input type="file" id="input-files" accept="image/*" multiple hidden>
<input type="file" id="input-folder" accept="image/*" webkitdirectory multiple hidden>
</div>
</div>
<div id="import-preview" class="hidden">
<div class="preview-toolbar">
<span id="import-count"></span>
<button id="btn-clear-import" class="btn-ghost">✕ Clear</button>
</div>
<div id="import-grid" class="thumb-grid"></div>
<div class="step-actions">
<button id="btn-to-process" class="btn-primary btn-lg">Process Images →</button>
</div>
</div>
</section>
<!-- STEP 2: Process -->
<section id="step-2" class="step-panel">
<div class="panel-header">
<h2>Process &amp; Preview</h2>
<button id="btn-process-all" class="btn-primary">⚡ Process All</button>
</div>
<div class="options-bar">
<div class="option-group" title="Adjust JPEG/WebP compression. Higher = better quality, lower = smaller file size.">
<label>Quality <span id="quality-display">75</span>%</label>
<input type="range" id="quality-slider" min="30" max="100" value="75">
</div>
<div class="option-group" title="Sensitivity of background removal. Higher removes more background, lower preserves more detail.">
<label>BG Tolerance <span id="bg-tol-display">25</span></label>
<input type="range" id="bg-tol-slider" min="5" max="200" value="25">
</div>
<label class="toggle-label" title="Enable to automatically remove the photo background.">
<input type="checkbox" id="toggle-bg" checked>
<span class="toggle-track"></span>
Remove BG
</label>
<label class="toggle-label" title="Turn the part into a solid black silhouette (perfect for black parts with glare).">
<input type="checkbox" id="toggle-silhouette">
<span class="toggle-track"></span>
Solid Black
</label>
<label class="toggle-label" title="Add a soft, professional drop shadow to the subject.">
<input type="checkbox" id="toggle-shadow" checked>
<span class="toggle-track"></span>
Drop Shadow
</label>
<label class="toggle-label" title="Overlay the CSG watermark on the processed image.">
<input type="checkbox" id="toggle-watermark" checked>
<span class="toggle-track"></span>
Watermark
</label>
<label class="toggle-label" title="Automatically crop to the subject and center it on a square canvas.">
<input type="checkbox" id="toggle-crop" checked>
<span class="toggle-track"></span>
Center &amp; Crop
</label>
</div>
<div id="process-grid" class="process-grid"></div>
<div class="step-actions">
<button id="btn-back-to-1" class="btn-ghost">← Back</button>
<button id="btn-to-upload" class="btn-primary btn-lg">Upload to WooCommerce →</button>
</div>
</section>
<!-- STEP 3: Upload -->
<section id="step-3" class="step-panel">
<div class="panel-header">
<h2>Upload to WooCommerce</h2>
<button id="btn-upload-all" class="btn-success">⬆ Upload All</button>
</div>
<div id="upload-grid" class="upload-grid"></div>
<div class="step-actions">
<button id="btn-back-to-2" class="btn-ghost">← Back</button>
<button id="btn-start-over" class="btn-secondary" title="Clear everything and start from the beginning.">↺ Start Over</button>
</div>
<div id="upload-log" class="hidden">
<h3>Upload Log</h3>
<div id="log-entries"></div>
</div>
</section>
</main>
<!-- Settings Modal -->
<div id="settings-modal" class="modal hidden">
<div class="modal-backdrop" id="modal-backdrop"></div>
<div class="modal-content">
<div class="modal-header">
<h2>Settings</h2>
<button id="btn-close-settings" class="btn-icon"></button>
</div>
<div class="modal-body">
<div class="settings-section">
<h3>WooCommerce API</h3>
<div class="form-group">
<label for="s-url">Site URL</label>
<input type="url" id="s-url" placeholder="https://collisionstoneguard.com">
</div>
<div class="form-group input-reveal-row">
<label for="s-ck">Consumer Key</label>
<input type="password" id="s-ck" placeholder="ck_..." title="WooCommerce REST API Consumer Key (found in WC > Settings > Advanced > REST API)">
<button class="btn-reveal" data-target="s-ck">👁</button>
</div>
<div class="form-group input-reveal-row">
<label for="s-cs">Consumer Secret</label>
<input type="password" id="s-cs" placeholder="cs_..." title="WooCommerce REST API Consumer Secret">
<button class="btn-reveal" data-target="s-cs">👁</button>
</div>
<button id="btn-test-api" class="btn-secondary">Test Connection</button>
<div id="api-status"></div>
</div>
<div class="settings-section">
<h3>WordPress Auth (Media Library)</h3>
<p style="font-size: 11px; color: var(--text-dim); margin-bottom: 10px;">
If media uploads fail with 401/Unauthorized, enter a WordPress User and <a href="https://wordpress.org/support/article/application-passwords/" target="_blank" style="color: var(--accent);">Application Password</a>.
</p>
<div class="form-group">
<label>WP Username</label>
<input type="text" id="s-wp-user" placeholder="admin">
</div>
<div class="form-group input-reveal-row">
<label>Application Password</label>
<input type="password" id="s-wp-pass" placeholder="xxxx xxxx xxxx xxxx">
<button class="btn-reveal" data-target="s-wp-pass">👁</button>
</div>
</div>
<div class="settings-section">
<h3>Watermark Image</h3>
<div class="form-group">
<div class="watermark-row">
<img id="wm-preview" src="watermark.png" alt="Watermark preview"
onerror="this.src=''; this.alt='No image loaded'">
<button id="btn-load-wm" class="btn-secondary">Change Image</button>
<input type="file" id="input-wm" accept="image/png,image/svg+xml" hidden>
</div>
</div>
<div class="form-group">
<label>Opacity <span id="wm-opacity-display">30</span>%</label>
<input type="range" id="s-wm-opacity" min="5" max="80" value="30">
</div>
<div class="form-group">
<label>Size <span id="wm-size-display">25</span>% of image width</label>
<input type="range" id="s-wm-size" min="5" max="50" value="25">
</div>
</div>
<div class="settings-section">
<h3>Background Removal</h3>
<div class="form-group">
<label>Color Tolerance <span id="bg-thresh-display">35</span></label>
<input type="range" id="s-bg-thresh" min="5" max="80" value="35">
<small>Higher = removes more background variation. Lower = more precise edges.</small>
</div>
</div>
<div class="settings-section">
<h3>Output Size</h3>
<div class="form-group">
<label>Max Dimension <span id="max-dim-display">1500</span>px</label>
<input type="range" id="s-max-dim" min="600" max="4000" step="100" value="1500" title="The maximum width or height of the exported image.">
<small>Longest side limit. Transparent images export as WebP (alpha + lossy = small file).</small>
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-save-settings" class="btn-primary">Save Settings</button>
</div>
</div>
</div>
<!-- Editor Modal -->
<div id="editor-modal" class="modal hidden">
<div class="modal-backdrop" id="editor-backdrop"></div>
<div class="editor-box">
<div class="editor-top">
<span id="editor-filename"></span>
<div class="editor-top-actions">
<button id="ed-apply" class="btn-success">✓ Apply</button>
<button id="ed-close-btn" class="btn-icon"></button>
</div>
</div>
<div class="editor-body">
<div class="editor-preview-wrap" id="ed-preview-wrap">
<div id="ed-zoom-container">
<canvas id="ed-canvas"></canvas>
<canvas id="ed-overlay-canvas" style="position: absolute; pointer-events: none; z-index: 8;"></canvas>
<img id="ed-wm-img" class="ed-wm-draggable" draggable="false" alt="watermark">
<div id="ed-selection-box" class="selection-box hidden"></div>
</div>
<div id="ed-loader" class="editor-loader hidden">
<div class="spinner"></div>
<span>Processing...</span>
</div>
</div>
<div class="editor-sidebar">
<div class="editor-tool-group">
<button id="ed-tool-move" class="btn-tool active" title="Move Watermark: Click and drag the logo to reposition it."></button>
<button id="ed-tool-box" class="btn-tool" title="Box Cutout: Draw a rectangle to clear anything outside the subject."></button>
<button id="ed-tool-lasso" class="btn-tool" title="Lasso Cutout: Draw a freehand shape around the subject to clear the background."></button>
<button id="ed-tool-erase" class="btn-tool" title="Lasso Erase: Draw a shape around any leftover shadow/background to delete it.">🧽</button>
<button id="ed-tool-nudge" class="btn-tool" title="Nudge Part: Click and drag the product to fine-tune its position in the frame.">🎯</button>
</div>
<div class="form-group" title="Adjust sensitivity for this specific image.">
<label>BG Tolerance <strong id="ed-thresh-val">25</strong></label>
<input type="range" id="ed-thresh" min="5" max="200" value="25">
</div>
<div class="form-group" title="Add extra breathing room around the product.">
<label>Crop Padding <strong id="ed-pad-val">5</strong>%</label>
<input type="range" id="ed-pad" min="0" max="25" value="5">
</div>
<div class="form-group" title="Adjust the scale of the watermark for this image.">
<label>Watermark Size <strong id="ed-wm-sz-val">25</strong>%</label>
<input type="range" id="ed-wm-sz" min="5" max="50" value="25">
</div>
<div class="form-group" title="Adjust the darkness of the drop shadow.">
<label>Shadow Intensity <strong id="ed-shadow-val">22</strong>%</label>
<input type="range" id="ed-shadow" min="0" max="100" value="22">
</div>
<label class="toggle-label" title="Show a solid white background instead of the checkerboard.">
<span>White Preview</span>
<input type="checkbox" id="ed-white-bg">
<span class="toggle-track"></span>
</label>
<label class="toggle-label" title="Turn the subject into a solid black silhouette.">
<span>Solid Black</span>
<input type="checkbox" id="ed-solid-black">
<span class="toggle-track"></span>
</label>
<button id="ed-clear-erase" class="btn-secondary" style="width:100%;margin-top:8px" title="Clear all manual erase paths.">✕ Clear Erases</button>
<button id="ed-reset" class="btn-danger-outline" style="width:100%;margin-top:8px" title="Undo all manual changes for this image and reset to global defaults.">↺ Reset All Changes</button>
<p class="editor-hint">Use Box/Lasso tools to trace the subject, and Move/Nudge tools to position items.</p>
</div>
</div>
</div>
</div>
<!-- Hidden processing canvas -->
<canvas id="proc-canvas" style="display:none"></canvas>
<script src="script.js"></script>
</body>
</html>
+1721
View File
File diff suppressed because it is too large Load Diff
+88
View File
@@ -0,0 +1,88 @@
@echo off
title CSG Image Upload Tool
color 0B
echo.
echo ==========================================
echo CSG Image Upload Tool ^| Launcher
echo ==========================================
echo.
set PORT=3000
:: Change to the folder where this bat file lives
cd /d "%~dp0"
echo Looking for a web server on your machine...
echo.
:: ── Try Python 3 (most common) ─────────────────────────────
python --version >nul 2>&1
if %errorlevel% == 0 (
echo [OK] Found Python! Starting on http://localhost:%PORT%
echo.
echo Leave this window open while using the tool.
echo Press Ctrl+C here to stop the server when done.
echo.
timeout /t 2 /nobreak >nul
start "" "http://localhost:%PORT%"
python -m http.server %PORT%
goto :end
)
:: ── Try "py" launcher (Windows Python Launcher) ────────────
py -3 --version >nul 2>&1
if %errorlevel% == 0 (
echo [OK] Found Python (py launcher)! Starting on http://localhost:%PORT%
echo.
echo Leave this window open while using the tool.
echo Press Ctrl+C here to stop the server when done.
echo.
timeout /t 2 /nobreak >nul
start "" "http://localhost:%PORT%"
py -3 -m http.server %PORT%
goto :end
)
:: ── Try Node.js / npx ──────────────────────────────────────
npx --version >nul 2>&1
if %errorlevel% == 0 (
echo [OK] Found Node.js! Starting on http://localhost:%PORT%
echo.
echo Leave this window open while using the tool.
echo Press Ctrl+C here to stop the server when done.
echo.
timeout /t 2 /nobreak >nul
start "" "http://localhost:%PORT%"
npx -y serve . -l %PORT%
goto :end
)
:: ── Nothing found ──────────────────────────────────────────
color 0C
echo.
echo ==========================================
echo No web server found on your machine!
echo ==========================================
echo.
echo This tool requires a local web server because
echo browsers block camera + API access from files
echo opened directly (file://).
echo.
echo Fix: Install Python (free, easy) then try again:
echo.
echo https://www.python.org/downloads/
echo.
echo During install, CHECK the box:
echo "Add Python to PATH"
echo.
echo Then double-click start.bat again.
echo.
echo ── OR install Node.js: https://nodejs.org/ ──
echo.
pause
goto :eof
:end
echo.
echo Server stopped.
pause
+604
View File
@@ -0,0 +1,604 @@
/* ─── Reset & Base ─────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg-deep: #080d18;
--bg-card: rgba(255,255,255,0.04);
--bg-card2: rgba(255,255,255,0.07);
--border: rgba(255,255,255,0.08);
--border2: rgba(255,255,255,0.14);
--accent1: #3b7dd8;
--accent2: #6c5ce7;
--accent-g: linear-gradient(135deg, #3b7dd8, #6c5ce7);
--success: #00c896;
--warning: #f4a535;
--danger: #e05c5c;
--text: #e8eaf0;
--text-muted: #7a82a0;
--text-dim: #4a5270;
--radius: 12px;
--radius-sm: 8px;
--shadow-lg: 0 24px 48px rgba(0,0,0,0.5);
--shadow-md: 0 8px 24px rgba(0,0,0,0.35);
}
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', system-ui, sans-serif;
background: var(--bg-deep);
color: var(--text);
min-height: 100vh;
font-size: 14px;
line-height: 1.6;
background-image:
radial-gradient(ellipse 60% 40% at 20% 0%, rgba(59,125,216,0.12) 0%, transparent 60%),
radial-gradient(ellipse 50% 40% at 80% 100%, rgba(108,92,231,0.1) 0%, transparent 60%);
}
/* ─── Header ───────────────────────────────────────────────────────────── */
#app-header {
position: sticky; top: 0; z-index: 100;
background: rgba(8,13,24,0.85);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
}
.header-inner {
max-width: 1200px; margin: 0 auto;
padding: 12px 24px;
display: flex; align-items: center; justify-content: space-between;
}
.header-brand {
display: flex; align-items: center; gap: 14px;
}
.header-brand img {
height: 44px; width: auto;
filter: drop-shadow(0 2px 8px rgba(59,125,216,0.4));
}
.header-brand h1 {
font-size: 18px; font-weight: 700;
background: var(--accent-g); -webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.header-brand p { font-size: 11px; color: var(--text-muted); }
/* ─── Step Nav ─────────────────────────────────────────────────────────── */
#steps-nav {
display: flex; align-items: center; justify-content: center;
gap: 0; padding: 20px 24px;
max-width: 600px; margin: 0 auto;
}
.step {
display: flex; flex-direction: column; align-items: center; gap: 6px;
cursor: default; transition: opacity 0.3s;
}
.step-num {
width: 36px; height: 36px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 14px;
background: var(--bg-card); border: 2px solid var(--border2);
color: var(--text-muted);
transition: all 0.3s;
}
.step span { font-size: 11px; font-weight: 500; color: var(--text-muted); transition: color 0.3s; }
.step.active .step-num {
background: var(--accent-g); border-color: transparent;
color: #fff; box-shadow: 0 0 20px rgba(59,125,216,0.5);
}
.step.active span { color: var(--text); }
.step.done .step-num { background: var(--success); border-color: transparent; color: #fff; }
.step.done span { color: var(--success); }
.step-connector {
flex: 1; height: 2px; margin: 0 8px; margin-bottom: 22px;
background: var(--border2);
transition: background 0.3s;
}
.step-connector.done { background: var(--success); }
/* ─── Main / Panels ────────────────────────────────────────────────────── */
#app-main { max-width: 1200px; margin: 0 auto; padding: 0 24px 80px; }
.step-panel { display: none; animation: fadeIn 0.35s ease; }
.step-panel.active { display: block; }
@keyframes fadeIn { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:none; } }
.panel-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 24px;
}
.panel-header h2 { font-size: 22px; font-weight: 700; }
/* ─── Mode Toggle ──────────────────────────────────────────────────────── */
.mode-toggle {
display: flex; background: var(--bg-card); border: 1px solid var(--border);
border-radius: 8px; overflow: hidden;
}
.mode-btn {
padding: 6px 18px; border: none; background: none;
color: var(--text-muted); font-size: 13px; font-weight: 500;
cursor: pointer; transition: all 0.2s;
}
.mode-btn.active { background: var(--accent-g); color: #fff; }
/* ─── Drop Zone ────────────────────────────────────────────────────────── */
#drop-zone {
border: 2px dashed var(--border2);
border-radius: var(--radius);
background: var(--bg-card);
transition: all 0.25s;
margin-bottom: 24px;
}
#drop-zone.drag-over {
border-color: var(--accent1);
background: rgba(59,125,216,0.08);
}
.drop-zone-inner {
display: flex; flex-direction: column; align-items: center;
padding: 64px 32px; gap: 12px; text-align: center;
}
.drop-icon svg { width: 64px; height: 64px; stroke: var(--text-dim); }
.drop-zone-inner h3 { font-size: 18px; font-weight: 600; }
.drop-zone-inner p { color: var(--text-muted); font-size: 13px; }
.drop-actions { display: flex; gap: 12px; margin-top: 8px; }
/* ─── Import Preview ───────────────────────────────────────────────────── */
.preview-toolbar {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 16px;
}
.preview-toolbar span { font-weight: 600; color: var(--accent1); }
.thumb-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 10px; margin-bottom: 24px;
}
.thumb-item {
aspect-ratio: 1; border-radius: var(--radius-sm);
overflow: hidden; background: var(--bg-card);
border: 1px solid var(--border); position: relative;
}
.thumb-item img { width: 100%; height: 100%; object-fit: cover; }
.thumb-item .thumb-name {
position: absolute; bottom: 0; left: 0; right: 0;
padding: 3px 5px; font-size: 9px; color: #fff;
background: rgba(0,0,0,0.6); white-space: nowrap;
overflow: hidden; text-overflow: ellipsis;
}
/* ─── Options Bar ──────────────────────────────────────────────────────── */
.options-bar {
display: flex; flex-wrap: wrap; gap: 20px; align-items: center;
padding: 16px 20px; margin-bottom: 24px;
background: var(--bg-card); border: 1px solid var(--border);
border-radius: var(--radius);
}
.option-group { display: flex; flex-direction: column; gap: 6px; }
.option-group label { font-size: 12px; font-weight: 500; color: var(--text-muted); }
input[type="range"] {
-webkit-appearance: none; appearance: none;
width: 140px; height: 4px;
background: var(--border2); border-radius: 4px; outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 16px; height: 16px; border-radius: 50%;
background: var(--accent1); cursor: pointer;
box-shadow: 0 0 8px rgba(59,125,216,0.5);
}
/* Toggle */
.toggle-label {
display: flex; align-items: center; gap: 10px;
cursor: pointer; font-size: 13px; font-weight: 500; color: var(--text);
user-select: none; white-space: nowrap;
}
.toggle-label input[type="checkbox"] { display: none; }
.toggle-track {
width: 40px; height: 22px; border-radius: 11px;
background: #333a50; position: relative;
transition: background 0.2s; flex-shrink: 0;
border: 1px solid rgba(255,255,255,0.1);
}
.toggle-track::after {
content: ''; position: absolute;
width: 16px; height: 16px; border-radius: 50%;
background: #fff; top: 3px; left: 3px;
transition: transform 0.2s;
}
.toggle-label input:checked + .toggle-track { background: var(--accent1); }
.toggle-label input:checked + .toggle-track::after { transform: translateX(18px); }
/* ─── Process Grid ─────────────────────────────────────────────────────── */
.process-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 16px; margin-bottom: 24px;
}
.process-card {
background: var(--bg-card); border: 1px solid var(--border);
border-radius: var(--radius); overflow: hidden;
transition: border-color 0.2s, transform 0.2s;
}
.process-card:hover { border-color: var(--border2); transform: translateY(-2px); }
.process-card-images {
display: grid; grid-template-columns: 1fr 1fr;
height: 240px;
}
.process-card-images .img-pane {
position: relative; overflow: hidden; background: #1a1a2e;
}
.process-card-images .img-pane img {
width: 100%; height: 100%; object-fit: contain;
}
.process-card-images .img-pane .pane-label {
position: absolute; top: 6px; left: 6px;
font-size: 9px; font-weight: 700; text-transform: uppercase;
padding: 2px 6px; border-radius: 4px;
background: rgba(0,0,0,0.6); color: var(--text-muted);
letter-spacing: 0.5px;
}
.process-card-images .img-divider { width: 1px; background: var(--border); }
.process-card-body { padding: 12px 14px; }
.process-card-name {
font-size: 11px; color: var(--text-muted);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
margin-bottom: 8px;
}
.process-card-actions { display: flex; gap: 8px; }
.process-status {
display: flex; align-items: center; gap: 6px;
font-size: 12px; margin-top: 8px; min-height: 20px;
}
.status-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--text-dim); flex-shrink: 0;
}
.status-dot.processing { background: var(--warning); animation: pulse 1s infinite; }
.status-dot.done { background: var(--success); }
.status-dot.error { background: var(--danger); }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.3; } }
/* ─── Editor Modal ─────────────────────────────────────────────────────── */
.editor-box {
position: relative; z-index: 1;
width: 96vw; max-width: 1100px;
background: #0e1528; border: 1px solid var(--border2);
border-radius: 16px; box-shadow: var(--shadow-lg);
display: flex; flex-direction: column;
max-height: 95vh; overflow: hidden;
animation: slideUp 0.3s ease;
}
.editor-top {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 20px; border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.editor-top span { font-size: 13px; font-weight: 600; color: var(--text-muted); }
.editor-top-actions { display: flex; gap: 8px; }
.editor-body {
display: grid; grid-template-columns: 1fr 220px;
flex: 1; overflow: hidden;
}
.editor-preview-wrap {
position: relative; overflow: hidden;
background: repeating-conic-gradient(#1a1a2e 0% 25%, #232840 0% 50%) 0 0 / 24px 24px;
display: flex; align-items: center; justify-content: center;
}
.editor-preview-wrap.preview-white { background: #fff !important; }
.editor-preview-wrap canvas {
max-width: 100%; max-height: calc(95vh - 120px);
object-fit: contain; display: block;
}
.ed-wm-draggable {
position: absolute;
cursor: grab; user-select: none;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
touch-action: none;
}
.ed-wm-draggable:active { cursor: grabbing; }
.editor-sidebar {
padding: 16px; border-left: 1px solid var(--border);
display: flex; flex-direction: column; gap: 12px;
overflow-y: auto; background: rgba(0,0,0,0.2);
}
.editor-sidebar .form-group { margin-bottom: 0; }
.editor-sidebar .toggle-label {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background: rgba(255,255,255,0.03);
border: 1px solid var(--border);
border-radius: 8px;
margin: 4px 0;
width: 100%;
box-sizing: border-box;
}
.editor-sidebar .toggle-label:hover {
background: rgba(255,255,255,0.06);
}
.editor-tool-group {
display: flex; gap: 8px; margin-bottom: 16px;
}
.btn-tool {
flex: 1; background: var(--bg-card); border: 1px solid var(--border);
color: var(--text-muted); padding: 10px; border-radius: 8px;
font-size: 18px; cursor: pointer; transition: all 0.2s;
}
.btn-tool:hover { background: var(--border); }
.btn-tool.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
box-shadow: 0 0 12px rgba(59, 125, 216, 0.4);
}
.editor-preview-wrap.mode-crop { cursor: crosshair; }
.editor-preview-wrap.mode-lasso { cursor: crosshair; }
.editor-preview-wrap.mode-erase { cursor: crosshair; }
.editor-preview-wrap.mode-move { cursor: default; }
.editor-preview-wrap.mode-nudge { cursor: move; }
#ed-zoom-container {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
transform-origin: 0 0;
display: flex;
align-items: center;
justify-content: center;
}
.selection-box {
position: absolute; border: 2px dashed var(--accent);
background: rgba(59, 125, 216, 0.15); pointer-events: none;
z-index: 10;
}
.editor-sidebar label { font-size: 12px; color: var(--text-muted); display: block; margin-bottom: 6px; }
.editor-sidebar input[type="range"] { width: 100%; }
.editor-hint {
font-size: 11px; color: var(--text-dim); text-align: center;
margin-top: auto; padding-top: 12px; line-height: 1.5;
}
/* ─── Upload Grid ──────────────────────────────────────────────────────── */
.upload-grid {
display: flex; flex-direction: column; gap: 12px;
margin-bottom: 24px;
}
.upload-row {
display: grid;
grid-template-columns: 80px 1fr 200px 160px auto;
gap: 16px; align-items: center;
padding: 12px 16px;
background: var(--bg-card); border: 1px solid var(--border);
border-radius: var(--radius);
transition: border-color 0.2s;
}
.upload-row:hover { border-color: var(--border2); }
.upload-row img { width: 80px; height: 80px; object-fit: contain; border-radius: var(--radius-sm); background: #1a1a2e; }
.upload-row-info .filename { font-size: 12px; color: var(--text-muted); margin-bottom: 4px; word-break: break-all; }
.sku-input-wrap { display: flex; flex-direction: column; gap: 6px; }
.sku-input-wrap label { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
.sku-search-row { display: flex; gap: 6px; }
.upload-row-status { display: flex; flex-direction: column; gap: 4px; }
.upload-status-text { font-size: 12px; }
.product-match { font-size: 11px; color: var(--success); }
.product-nomatch { font-size: 11px; color: var(--danger); }
.upload-progress {
height: 4px; border-radius: 4px; background: var(--border2);
overflow: hidden;
}
.upload-progress-bar { height: 100%; background: var(--accent-g); transition: width 0.3s; }
/* ─── Upload Log ───────────────────────────────────────────────────────── */
#upload-log {
margin-top: 32px; padding: 20px;
background: var(--bg-card); border: 1px solid var(--border);
border-radius: var(--radius);
}
#upload-log h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--text-muted); }
#log-entries { display: flex; flex-direction: column; gap: 6px; font-size: 12px; font-family: 'Courier New', monospace; max-height: 240px; overflow-y: auto; }
.log-entry { padding: 4px 8px; border-radius: 4px; }
.log-entry.info { color: var(--text-muted); }
.log-entry.success { color: var(--success); background: rgba(0,200,150,0.06); }
.log-entry.error { color: var(--danger); background: rgba(224,92,92,0.06); }
/* ─── Buttons ──────────────────────────────────────────────────────────── */
.btn-primary {
padding: 9px 20px; border-radius: var(--radius-sm);
background: var(--accent-g); color: #fff; border: none;
font-size: 13px; font-weight: 600; cursor: pointer;
transition: opacity 0.2s, transform 0.15s, box-shadow 0.2s;
box-shadow: 0 4px 16px rgba(59,125,216,0.3);
}
.btn-primary:hover { opacity: 0.9; transform: translateY(-1px); box-shadow: 0 6px 20px rgba(59,125,216,0.45); }
.btn-primary:active { transform: none; }
.btn-secondary {
padding: 9px 20px; border-radius: var(--radius-sm);
background: var(--bg-card2); color: var(--text);
border: 1px solid var(--border2);
font-size: 13px; font-weight: 500; cursor: pointer;
transition: all 0.2s;
}
.btn-secondary:hover { background: rgba(255,255,255,0.12); border-color: var(--accent1); }
.btn-success {
padding: 9px 20px; border-radius: var(--radius-sm);
background: var(--success); color: #fff; border: none;
font-size: 13px; font-weight: 600; cursor: pointer;
transition: all 0.2s; box-shadow: 0 4px 16px rgba(0,200,150,0.3);
}
.btn-success:hover { opacity: 0.9; transform: translateY(-1px); }
.btn-ghost {
padding: 9px 16px; border-radius: var(--radius-sm);
background: none; color: var(--text-muted);
border: 1px solid transparent;
font-size: 13px; cursor: pointer; transition: all 0.2s;
}
.btn-ghost:hover { color: var(--text); border-color: var(--border2); background: var(--bg-card); }
.btn-icon {
width: 38px; height: 38px; border-radius: var(--radius-sm);
background: var(--bg-card); border: 1px solid var(--border);
color: var(--text-muted); cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all 0.2s; font-size: 16px;
}
.btn-icon svg { width: 18px; height: 18px; }
.btn-icon:hover { color: var(--text); border-color: var(--border2); background: var(--bg-card2); }
.btn-sm { padding: 6px 12px !important; font-size: 12px !important; }
.btn-lg { padding: 12px 28px !important; font-size: 15px !important; }
.btn-danger-outline {
padding: 9px 20px; border-radius: var(--radius-sm);
background: rgba(224,92,92,0.05); color: var(--danger);
border: 1px solid rgba(224,92,92,0.3);
font-size: 13px; font-weight: 500; cursor: pointer;
transition: all 0.2s;
}
.btn-danger-outline:hover { background: rgba(224,92,92,0.12); border-color: var(--danger); }
.step-actions {
display: flex; gap: 12px; align-items: center;
justify-content: flex-end; margin-top: 8px;
}
/* ─── Inputs ────────────────────────────────────────────────────────────── */
input[type="text"], input[type="url"], input[type="password"] {
background: rgba(255,255,255,0.05);
border: 1px solid var(--border2); border-radius: var(--radius-sm);
color: var(--text); font-size: 13px; font-family: inherit;
padding: 9px 12px; width: 100%; outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus,
input[type="url"]:focus,
input[type="password"]:focus {
border-color: var(--accent1);
box-shadow: 0 0 0 3px rgba(59,125,216,0.15);
}
input::placeholder { color: var(--text-dim); }
.sku-input { width: 100%; }
/* ─── Settings Modal ───────────────────────────────────────────────────── */
.modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
.modal.hidden { display: none; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); }
.modal-content {
position: relative; z-index: 1;
width: min(560px, 96vw);
background: #0e1528; border: 1px solid var(--border2);
border-radius: 16px; box-shadow: var(--shadow-lg);
animation: slideUp 0.3s ease;
max-height: 90vh; display: flex; flex-direction: column;
}
@keyframes slideUp { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:none; } }
.modal-header {
display: flex; align-items: center; justify-content: space-between;
padding: 20px 24px; border-bottom: 1px solid var(--border);
}
.modal-header h2 { font-size: 18px; font-weight: 700; }
.modal-body { padding: 24px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 28px; }
.modal-footer { padding: 16px 24px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; }
.settings-section h3 {
font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;
color: var(--accent1); margin-bottom: 16px;
}
.form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.form-group label { font-size: 12px; font-weight: 500; color: var(--text-muted); }
.form-group small { font-size: 11px; color: var(--text-dim); }
.input-reveal-row { position: relative; }
.input-reveal-row input { padding-right: 40px; }
.btn-reveal {
position: absolute; right: 10px; bottom: 8px;
background: none; border: none; cursor: pointer;
color: var(--text-muted); font-size: 14px; padding: 0;
}
.watermark-row {
display: flex; align-items: center; gap: 12px;
}
.watermark-row img {
height: 60px; width: auto; max-width: 120px;
border-radius: var(--radius-sm); border: 1px solid var(--border);
background: white; object-fit: contain; padding: 4px;
}
#api-status { margin-top: 10px; font-size: 12px; min-height: 18px; }
#api-status.ok { color: var(--success); }
#api-status.err { color: var(--danger); }
/* ─── Utility ──────────────────────────────────────────────────────────── */
.hidden { display: none !important; }
/* ─── Custom Tooltips ─────────────────────────────────────────────────── */
.custom-tooltip {
position: fixed;
z-index: 9999;
background: rgba(15, 23, 42, 0.9);
backdrop-filter: blur(8px);
border: 1px solid var(--border2);
color: var(--text);
padding: 8px 12px;
border-radius: 8px;
font-size: 11px;
line-height: 1.4;
max-width: 200px;
pointer-events: none;
box-shadow: var(--shadow-md);
opacity: 0;
transform: translateY(4px);
transition: opacity 0.2s, transform 0.2s;
}
.custom-tooltip.visible {
opacity: 1;
transform: none;
}
.custom-tooltip::after {
content: '';
position: absolute;
border: 6px solid transparent;
}
/* Bottom placement (arrow on top) */
.custom-tooltip.pos-bottom::after {
bottom: 100%; left: 50%; transform: translateX(-50%);
border-bottom-color: var(--border2);
}
/* Top placement (arrow on bottom) */
.custom-tooltip.pos-top::after {
top: 100%; left: 50%; transform: translateX(-50%);
border-top-color: var(--border2);
}
/* ─── Editor Loader ──────────────────────────────────────────────────── */
.editor-loader {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.4);
backdrop-filter: blur(2px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
gap: 12px;
z-index: 20;
border-radius: var(--radius-md);
}
.spinner {
width: 24px; height: 24px;
border: 3px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB