Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
Thumbs.db
|
||||||
+326
@@ -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 & 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 & 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>
|
||||||
@@ -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
|
||||||
@@ -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); }
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 382 KiB |
Reference in New Issue
Block a user