collageiov4 / index.html
rththr's picture
Upload 3 files
447b3d3 verified
Raw
History Blame Contribute Delete
28.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MLBB Collage Maker & Skin Manager</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
<link rel="stylesheet" href="style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
</head>
<body data-theme="light">
<div class="app-container">
<aside id="control-panel" class="glass-panel">
<header class="panel-header">
<div class="logo-icon">
<svg width="48" height="48" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#gradient)"/>
<path d="M16 8L24 24H8L16 8Z" fill="white" fill-opacity="0.9"/>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="32" y2="32">
<stop stop-color="#0f766e"/>
<stop offset="1" stop-color="#0ea5e9"/>
</linearGradient>
</defs>
</svg>
</div>
<div class="header-text">
<h1>MLBB Collage Maker</h1>
<p class="subtitle">Skin Collection Manager</p>
</div>
<button type="button" id="btnToggleSidebar" class="btn panel-toggle-btn" title="Toggle sidebar">
<span class="btn-icon">&#9776;</span>
Panel
</button>
</header>
<div class="tab-switcher">
<button type="button" id="btnTabCollage" class="btn btn-primary tab-btn active">Collage</button>
<button type="button" id="btnTabOcr" class="btn tab-btn">OCR Stack</button>
<button type="button" id="btnTabProfile" class="btn tab-btn">Profile</button>
</div>
<nav id="tabCollage" class="control-sections tab-panel active">
<section class="control-section file-ops-section">
<h3>File Operations</h3>
<div class="button-grid">
<button type="button" id="btnLoadImages" class="btn btn-primary">
<span class="btn-icon">&#128247;</span>
Load Images
</button>
<button type="button" id="btnLoadZip" class="btn">
<span class="btn-icon">&#128230;</span>
Load ZIP
</button>
</div>
<div class="button-grid one-col">
<button type="button" id="btnLoadProjectImage" class="btn">
<span class="btn-icon">&#128279;</span>
Load Collage Project
</button>
</div>
<div class="button-grid">
<button type="button" id="btnSaveTemplate" class="btn">
<span class="btn-icon">&#128190;</span>
Save Template
</button>
<button type="button" id="btnLoadTemplate" class="btn">
<span class="btn-icon">&#128194;</span>
Load Template
</button>
</div>
<div class="button-grid one-col">
<button type="button" id="btnDownload" class="btn btn-primary">
<span class="btn-icon">&#11015;</span>
Download
</button>
</div>
<div class="button-grid one-col">
<button type="button" id="btnStitchProfile" class="btn">
<span class="btn-icon">&#129525;</span>
Stitch Profile + Collage
</button>
</div>
<div class="button-grid">
<button type="button" id="btnSetStartup" class="btn">
<span class="btn-icon">&#9881;</span>
Set Startup
</button>
<button type="button" id="btnClearStartup" class="btn">
<span class="btn-icon">&#10006;</span>
Clear Startup
</button>
</div>
<div class="button-grid">
<button type="button" id="btnFetchEndpoint" class="btn btn-primary">
<span class="btn-icon">&#128246;</span>
Fetch from Endpoint
</button>
<button type="button" id="btnResetFromCache" class="btn">
<span class="btn-icon">&#10227;</span>
Reset from Saved ZIP
</button>
</div>
</section>
<section class="control-section">
<h3>Grid & Layout</h3>
<div class="input-group">
<label for="gridCols">Columns</label>
<input type="number" id="gridCols" min="1" max="200" value="5">
</div>
<div class="input-group">
<label for="gridRows">Rows</label>
<input type="number" id="gridRows" min="1" max="5000" value="1">
</div>
<div class="input-group">
<label for="skinW">Skin Width (px)</label>
<input type="number" id="skinW" min="20" max="1000" value="335">
</div>
<div class="input-group">
<label for="skinH">Skin Height (px)</label>
<input type="number" id="skinH" min="20" max="1000" value="300">
</div>
<div class="input-group">
<label for="gapSize">Spacing: <span id="gapValue">0px</span></label>
<input type="range" id="gapSize" min="0" max="40" step="1" value="0">
</div>
<div class="input-group">
<label for="bgColor">Background Color</label>
<input type="color" id="bgColor" value="#ffffff">
</div>
<div class="input-group">
<label for="zoomLevel">Zoom: <span id="zoomValue">50%</span></label>
<input type="range" id="zoomLevel" min="0.1" max="2" step="0.05" value="0.5">
</div>
</section>
<section class="control-section">
<h3>Display</h3>
<div class="input-group">
<label for="groupFilter">Group Filter</label>
<select id="groupFilter">
<option value="all">All Groups</option>
</select>
</div>
<div class="input-group">
<label for="groupOrderList">Group Sequence</label>
<div id="groupOrderList" class="group-order-list"></div>
</div>
<div class="input-group">
<label for="groupPosSlider">Move Group Position</label>
<input type="range" id="groupPosSlider" min="1" max="1" value="1">
<div id="groupPosLabel" class="hint-text">Position: -</div>
</div>
<div class="button-grid two-col">
<button type="button" id="btnGroupUp" class="btn">Move Up</button>
<button type="button" id="btnGroupDown" class="btn">Move Down</button>
</div>
<div class="input-group">
<label for="themeSelect">Theme</label>
<select id="themeSelect">
<option value="light">Light</option>
<option value="night">Night</option>
<option value="ocean">Ocean</option>
<option value="sunset">Sunset</option>
</select>
</div>
<div class="input-group">
<label>UI Toggles</label>
<div class="ui-switches">
<label class="ui-switch">
<input type="checkbox" id="toggleCompactUi">
<span>Compact Layout</span>
</label>
<label class="ui-switch">
<input type="checkbox" id="toggleStrongContrast">
<span>High Contrast</span>
</label>
</div>
</div>
</section>
<section class="control-section">
<h3>Editing</h3>
<div class="button-grid one-col">
<button type="button" id="btnInsertImage" class="btn btn-primary">
<span class="btn-icon">&#10133;</span>
Insert Image (Cropper)
</button>
</div>
<div class="input-group">
<label for="multiOrder">Make Auto Collage</label>
<select id="multiOrder">
<option value="asc">Paste Ascending</option>
<option value="desc">Paste Descending</option>
</select>
</div>
<div class="input-group">
<label for="autoOrderInput">Name List (one per line)</label>
<textarea id="autoOrderInput" rows="6" placeholder="Paste names from JSON, one per line"></textarea>
</div>
<div class="input-group">
<label for="insertNameInput">Insert Name at Position</label>
<div class="insert-position-grid">
<input type="text" id="insertNameInput" placeholder="New image name">
<input type="number" id="insertPositionInput" min="1" value="1" placeholder="Position">
</div>
<div class="insert-position-grid">
<input type="text" id="insertGroupInput" placeholder="Group (optional)">
<button type="button" id="btnInsertNameAtPos" class="btn">Insert & Shift</button>
</div>
</div>
<div class="button-grid three-col">
<button type="button" id="btnApplyAutoOrder" class="btn btn-primary">Apply Auto Collage</button>
<button type="button" id="btnImportOcr" class="btn">OCR Import</button>
<button type="button" id="btnClearAutoOrder" class="btn">Clear List</button>
</div>
<div class="button-grid three-col">
<button type="button" id="btnMulti" class="btn">
<span class="btn-icon">&#9745;</span>
Multi
</button>
<button type="button" id="btnSwap" class="btn" disabled>
<span class="btn-icon">&#8646;</span>
Swap
</button>
<button type="button" id="btnDelete" class="btn btn-danger" disabled>
<span class="btn-icon">&#128465;</span>
Delete
</button>
</div>
<div class="button-grid">
<button type="button" id="btnFillEmpty" class="btn" disabled>
<span class="btn-icon">&#10507;</span>
Fill Empty Slots
</button>
<button type="button" id="btnOpenDrawer" class="btn">
<span class="btn-icon">&#128269;</span>
Search & Bin
</button>
</div>
<div class="input-group">
<label for="autoOrderMissing">Missing (not matched)</label>
<textarea id="autoOrderMissing" rows="4" readonly placeholder="Missing names will appear here"></textarea>
<div id="autoSuggestionList" class="auto-suggestion-list"></div>
</div>
<div class="button-grid">
<button type="button" id="btnUndo" class="btn" disabled>
<span class="btn-icon">&#8630;</span>
Undo
</button>
<button type="button" id="btnRedo" class="btn" disabled>
<span class="btn-icon">&#8631;</span>
Redo
</button>
</div>
</section>
<section class="control-section danger-zone">
<h3>Danger Zone</h3>
<button type="button" id="btnClearAll" class="btn btn-danger full-width">
<span class="btn-icon">&#9888;</span>
Clear All Images
</button>
</section>
</nav>
<section id="tabOcr" class="control-sections tab-panel">
<div class="control-section">
<h3>OCR Stack Tool</h3>
<div class="input-group">
<label for="ocrBatchLoader">Upload Images</label>
<button type="button" id="btnOcrLoadImages" class="btn btn-primary full-width">Choose Images</button>
<input type="file" id="ocrBatchLoader" style="display:none" multiple accept="image/*">
</div>
<div class="input-group">
<label for="ocrBatchSize">Batch Size</label>
<input type="number" id="ocrBatchSize" min="1" max="100" value="5">
</div>
<div class="hint-text">Crop is fixed: cut top 25% and bottom 30% (keeps 25%-70%), then stacked vertically.</div>
<div class="button-grid">
<button type="button" id="btnProcessOcrStack" class="btn btn-primary">Process</button>
<button type="button" id="btnDownloadOcrZip" class="btn" disabled>Download ZIP</button>
</div>
<div id="ocrStatus" class="hint-text">No OCR stack output yet.</div>
</div>
<div class="control-section">
<h3>Gemini AI</h3>
<div class="input-group">
<label for="geminiApiKey">Gemini API Key</label>
<input type="password" id="geminiApiKey" placeholder="AIza..." autocomplete="off">
</div>
<div class="input-group">
<label for="geminiModel">Model</label>
<input type="text" id="geminiModel" value="gemini-3-flash-preview" placeholder="gemini-3-flash-preview" list="geminiModelList">
<datalist id="geminiModelList"></datalist>
</div>
<div class="button-grid">
<button type="button" id="btnFetchGeminiModels" class="btn">Fetch Models</button>
</div>
<div id="geminiModelInfo" class="hint-text">Model list not loaded yet.</div>
<div class="input-group">
<label for="geminiModelLimits">Free-tier Model Limits</label>
<textarea id="geminiModelLimits" rows="6" readonly placeholder="Fetch models to view limits"></textarea>
</div>
<div class="input-group">
<label for="geminiPrompt">Prompt</label>
<textarea id="geminiPrompt" rows="4" placeholder="Explain and process this ZIP. Return structured output."></textarea>
</div>
<div class="button-grid">
<button type="button" id="btnChooseGeminiZip" class="btn">Choose ZIP</button>
<button type="button" id="btnSendGemini" class="btn btn-primary">Send to Gemini</button>
</div>
<div class="input-group">
<label class="ui-switch">
<input type="checkbox" id="geminiAutoApplyCollage" checked>
<span>Auto apply Gemini result to collage</span>
</label>
</div>
<input type="file" id="geminiZipLoader" style="display:none" accept=".zip,application/zip,image/png,image/jpeg,.png,.jpg,.jpeg">
<div id="geminiZipInfo" class="hint-text">Uses processed OCR image (all-at-once) by default, or choose your own file.</div>
<div class="input-group">
<label for="geminiResult">Gemini Response</label>
<textarea id="geminiResult" rows="8" readonly placeholder="Response will appear here"></textarea>
</div>
<div class="button-grid">
<button type="button" id="btnDownloadGeminiResult" class="btn" disabled>Download Response</button>
<button type="button" id="btnToggleMarkdownPreview" class="btn">Markdown Preview</button>
</div>
<div id="geminiMarkdownPreview" class="markdown-preview hidden"></div>
</div>
</section>
<section id="tabProfile" class="control-sections tab-panel">
<div class="control-section">
<h3>Profile Studio</h3>
<div class="hint-text">Loads the profile tool from `../profile/try.html` inside this tab.</div>
<div class="button-grid one-col">
<button type="button" id="btnOpenProfileStandalone" class="btn">Open Profile Standalone</button>
</div>
</div>
</section>
<input type="file" id="imgLoader" style="display:none" multiple accept="image/*">
<input type="file" id="zipLoader" style="display:none" accept=".zip">
<input type="file" id="insertImageLoader" style="display:none" accept="image/*">
<input type="file" id="projectImageLoader" style="display:none" accept="image/*">
<input type="file" id="templateLoader" style="display:none" accept=".json">
<input type="file" id="startupLoader" style="display:none" accept=".json">
<input type="file" id="ocrLoader" style="display:none" accept="image/*">
</aside>
<main id="viewport">
<div id="activeSkinsBadge" class="active-skins-badge">Active Skins: 0</div>
<div id="collage-area"></div>
<div id="empty-state" class="empty-state">
<div class="empty-icon">&#127912;</div>
<h2>No Images Loaded</h2>
<p>Load images or a ZIP file to get started</p>
<button type="button" id="btnQuickLoad" class="btn btn-primary">Load Sample Data</button>
</div>
</main>
<section id="ocr-workspace" class="ocr-workspace hidden">
<div class="ocr-workspace-header">
<h2>OCR Batch Output</h2>
<p>Cropped (25%-70%), sharpened, and stacked in batch order.</p>
</div>
<div id="ocrResults" class="ocr-results"></div>
</section>
<section id="profile-workspace" class="profile-workspace hidden">
<iframe id="profileFrame" class="profile-frame" src="../profile/try.html?v=profile4" title="Profile Studio"></iframe>
</section>
</div>
<div id="floating-panel" class="floating-panel glass-panel">
<div class="selection-info">
<span id="selection-status"></span>
</div>
<div class="button-group">
<button type="button" id="btnFloatDelete" class="btn btn-danger">
<span class="btn-icon">&#128465;</span>
Delete
</button>
<button type="button" id="btnFloatDeselect" class="btn">
<span class="btn-icon">&#10006;</span>
Deselect All
</button>
</div>
</div>
<div id="utilityScrim" class="drawer-scrim"></div>
<aside id="utilityDrawer" class="utility-drawer glass-panel">
<div class="utility-drawer-header">
<h3>Search & Bin</h3>
<button type="button" id="btnCloseDrawer" class="modal-close">&#10005;</button>
</div>
<section class="utility-section">
<label for="drawerSearch">Search Skin</label>
<div class="drawer-search-row">
<input type="text" id="drawerSearch" placeholder="Type skin name...">
<button type="button" id="btnClearSearch" class="btn">Clear</button>
</div>
<div id="drawerSearchStatus" class="hint-text">Search is hidden here. Matches are highlighted in blue.</div>
</section>
<section class="utility-section">
<div class="utility-row">
<h4>Bin (<span id="binCount">0</span>)</h4>
<div class="utility-actions">
<button type="button" id="btnPlaceBinSelected" class="btn">Place Selected</button>
<button type="button" id="btnRestoreBinAll" class="btn">Restore All</button>
<button type="button" id="btnEmptyBin" class="btn btn-danger">Empty Bin</button>
</div>
</div>
<div id="binList" class="bin-list"></div>
</section>
</aside>
<div id="image-processor-modal" class="modal-overlay">
<div class="modal-content glass-panel">
<div class="modal-header">
<h3 id="cropModalTitle">Crop Images</h3>
<button type="button" id="btnCloseCrop" class="modal-close">&#10005;</button>
</div>
<div class="processor-preview">
<img id="image-processor-preview" alt="Image preview for cropping">
</div>
<div class="modal-body">
<div class="input-row">
<div class="input-group">
<label for="cropW">Crop Width</label>
<input type="number" id="cropW" min="1" max="2000">
</div>
<div class="input-group">
<label for="cropH">Crop Height</label>
<input type="number" id="cropH" min="1" max="2000">
</div>
<button type="button" id="btnApplyCrop" class="btn">Set Size</button>
</div>
<p id="cropModalHint" class="modal-hint">Adjust crop, then click "Confirm & Map" to apply to all images</p>
<div id="insertMetaPanel" class="insert-meta-panel hidden">
<div class="input-group">
<label for="insertCropName">Image Name</label>
<input type="text" id="insertCropName" placeholder="Skin name">
</div>
<div class="insert-position-grid">
<div class="input-group">
<label for="insertCropPosition">Insert Position</label>
<input type="number" id="insertCropPosition" min="1" value="1">
</div>
<div class="input-group">
<label for="insertCropGroupSelect">Group</label>
<select id="insertCropGroupSelect"></select>
</div>
</div>
<div class="input-group hidden" id="insertCropNewGroupWrap">
<label for="insertCropNewGroup">New Group Name</label>
<input type="text" id="insertCropNewGroup" placeholder="Type new group name">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" id="btnCancelCrop" class="btn">Cancel</button>
<button type="button" id="btnConfirmCrop" class="btn btn-primary">Confirm & Map</button>
</div>
</div>
</div>
<div id="stitchPreviewModal" class="modal-overlay">
<div class="modal-content glass-panel">
<div class="modal-header">
<h3>Stitched Preview</h3>
<button type="button" id="btnCloseStitchPreview" class="modal-close">&#10005;</button>
</div>
<div class="processor-preview stitch-preview">
<img id="stitchPreviewImage" alt="Stitched result preview">
</div>
<div class="modal-footer">
<button type="button" id="btnCloseStitchPreviewSecondary" class="btn">Close</button>
<button type="button" id="btnDownloadStitchPreview" class="btn btn-primary">Download Stitched Image</button>
</div>
</div>
</div>
<div id="loading-overlay" class="modal-overlay">
<div class="loader-container">
<div class="spinner"></div>
<div class="loading-text">PROCESSING...</div>
<div id="loading-progress-label" class="loading-progress-label">Starting...</div>
<div class="loading-progress-track">
<div id="loading-progress-bar" class="loading-progress-bar"></div>
</div>
<div id="loading-progress-detail" class="loading-progress-detail"></div>
</div>
</div>
<div id="sidebarScrim" class="sidebar-scrim"></div>
<button type="button" id="btnToggleSidebarFab" class="btn btn-primary floating-action">
<span class="btn-icon">&#9776;</span>
Controls
</button>
<div id="mobileQuickBar" class="mobile-quickbar">
<button type="button" id="btnMobileControls" class="btn">&#9776;</button>
<button type="button" id="btnMobileZoomOut" class="btn">-</button>
<button type="button" id="btnMobileZoomIn" class="btn">+</button>
<button type="button" id="btnMobileFit" class="btn">&#9974;</button>
<button type="button" id="btnMobileBin" class="btn">&#128269;</button>
</div>
<script id="embedded-collage-data" type="application/json">
{
"skinWidth": 335,
"skinHeight": 300,
"cols": 5,
"files": {
"0": { "name": "- Layla_- Blue Specter.jpg", "group": "Premium" },
"1": { "name": "- Roger_- Fiend Haunter.jpg", "group": "Premium" },
"2": { "name": "- Rafaela_- Biomedic.jpg", "group": "Premium" },
"3": { "name": "- Nana_- Wind Fairy.jpg", "group": "Premium" },
"4": { "name": "- Paquito_- Manny Pacquiao.jpg", "group": "Premium" }
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"></script>
<script src="script.js"></script>
</body>
</html>