--- START OF FILE Paste January 29, 2026 - 9:19PM ---

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>VisualLock AI Pro – Prompt Builder</title>

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">

<!-- You might want to add Font Awesome for icons here if you plan to use them -->

<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> -->

<style>

/* Capture One Dark Theme Color Palette - REFINED */

:root {

--bg-body: #f7f5f4; /* Your preferred darkest grey for overall page */

--bg-panel: #f7f5f4; /* Your preferred slightly lighter dark grey for panels */

--bg-input: #ffffff; /* A slightly lighter, yet still dark, grey for input fields */

--accent: #333333; /* A professional, calming blue accent (Photoshop-esque) */

--accent-rgb: 94, 157, 219; /* RGB for translucent accent */

--accent-hover: #4a8cd0; /* Darker blue for hover */

--text-main: #000000; /* Slightly softer white for main text */

--text-muted: #000000; /* Medium grey for muted text - ensure good contrast */

--border: #1e1e1e; /* Darker grey for borders - clear separation */

--radius: 4px; /* Sharper corners */

--: 0 1px 8px rgba(0, 0, 0, 0.5); /* Slightly stronger, deeper */

--header-bg: #ffffff; /* Header background matches panels */

--header-border: #1e1e1e; /* Header border matches panel borders */

--panel-spacing: 16px; /* Consistent spacing - slightly increased for airiness */

--header-height: 60px;

}

* {

box-sizing: border-box;

-webkit-tap-highlight-color: transparent; /* Remove tap highlight on mobile */

}

body {

margin: 0;

font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

background: var(--bg-body);

color: var(--text-main);

line-height: 1.5;

min-height: 100vh;

display: flex;

justify-content: center;

align-items: flex-start; /* Align to top */

padding: 20px; /* Padding around the entire app */

font-size: 14px;

}

/* Scrollbar Styling */

::-webkit-scrollbar { width: 8px; height: 8px; }

::-webkit-scrollbar-track { background: var(--bg-body); }

::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }

/* Main App Wrapper */

.main-app-wrapper {

background: var(--bg-panel);

border-radius: var(--radius);

overflow: hidden;

max-width: 1600px; /* Increased max-width */

width: 100%;

box-: var(--);

display: flex;

flex-direction: column;

min-height: calc(100vh - 40px);

}

/* Header */

header {

background: var(--header-bg);

border-bottom: 1px solid var(--header-border);

padding: 10px 20px;

position: sticky;

top: 0;

z-index: 100;

display: flex;

justify-content: space-between;

align-items: center;

flex-wrap: wrap;

gap: 10px;

height: var(--header-height);

}

h1 {

margin: 0;

font-size: 18px;

letter-spacing: 0;

display: flex;

align-items: center;

color: var(--text-main); /* Ensure title stands out */

font-weight: 600; /* Slightly bolder */

}

h1 .ai {

color: var(--accent); /* Accent for AI */

margin: 0 3px;

font-weight: 700;

}

h1 .pro {

background: var(--accent); /* Maintain accent for PRO tag */

color: white;

font-weight: 700;

font-size: 9px;

padding: 1px 5px;

border-radius: 3px;

margin-left: 6px;

text-transform: uppercase;

letter-spacing: 0.8px;

}

.header-actions {

display: flex;

gap: 8px;

align-items: center;

}

/* App Container */

.app-container {

display: flex;

flex-wrap: nowrap;

width: 100%;

padding: var(--panel-spacing);

gap: var(--panel-spacing);

align-items: flex-start;

flex-grow: 1;

}

.left-column {

flex: 1 1 450px;

min-width: 380px;

display: flex;

flex-direction: column;

gap: var(--panel-spacing);

}

.right-column {

flex: 0 0 450px;

background: var(--bg-panel);

border: 1px solid var(--border);

border-radius: var(--radius);

padding: var(--panel-spacing);

position: sticky;

top: calc(var(--header-height) + var(--panel-spacing));

max-height: calc(100vh - var(--header-height) - (var(--panel-spacing) * 2));

overflow-y: auto;

display: flex;

flex-direction: column;

gap: var(--panel-spacing);

box-: var(--);

}

/* Forms & Inputs */

.settingsBox {

background: var(--bg-panel);

border: 1px solid var(--border);

border-radius: var(--radius);

padding: var(--panel-spacing);

box-: var(--);

flex-grow: 1;

}

.settingsBox.tabbed-panel {

padding-top: 0;

border-top-left-radius: 0;

border-top-right-radius: 0;

}

.settingsBox h3 {

margin: 0 0 10px 0;

color: var(--text-main);

font-size: 15px;

font-weight: 600;

border-bottom: 1px solid var(--border);

padding-bottom: 8px;

display: flex;

align-items: center;

}

label {

display: flex;

align-items: center;

margin-top: 14px; /* Slightly more space above labels */

margin-bottom: 6px; /* Slightly more space below labels */

font-size: 13px; /* Slightly larger labels for readability */

font-weight: 500; /* Medium weight */

color: var(--text-main); /* Labels are important, so make them visible */

text-transform: uppercase;

letter-spacing: 0.5px;

position: relative;

}

label:first-child { margin-top: 0; }

.section-number-circle {

display: none; /* Hide circles for C1 look */

}

label::before { /* Replace circle with a subtle indicator */

content: '';

display: inline-block;

width: 4px;

height: 12px;

background-color: var(--accent);

margin-right: 8px;

border-radius: 2px;

flex-shrink: 0;

}

.fieldDesc {

font-size: 12px; /* Keep reasonable size */

color: var(--text-muted); /* Good contrast with #ffffff */

margin-bottom: 8px; /* More separation from input */

font-style: normal;

}

input:not([type="file"]), select, textarea {

width: 100%;

padding: 10px 12px; /* Slightly more padding for comfort */

background: var(--bg-input);

color: var(--text-main);

border: 1px solid var(--border);

border-radius: var(--radius);

font-size: 14px; /* Slightly larger input text */

transition: border-color 0.2s, box- 0.2s;

font-family: inherit;

}

input:not([type="file"]):focus, select:focus, textarea:focus {

outline: none;

border-color: var(--accent);

box-: 0 0 0 1px var(--accent); /* Thin accent border on focus */

}

select {

-webkit-appearance: none;

-moz-appearance: none;

appearance: none;

background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23aaaaaa%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13%205.1L146.2%20202.7%2018.5%2074.5a17.6%2017.6%200%200%200-25.3%2023.3l137.9%20137.8c6.8%206.7%2017.7%206.7%2024.5%200l137.9-137.8c10.1-10.1%202.9-26.3-12.9-26.3z%22%2F%3E%3C%2Fsvg%3E');

background-repeat: no-repeat;

background-position: right 10px center; /* Adjusted for larger padding */

background-size: 10px;

padding-right: 30px; /* Make space for the arrow */

}

.input-row {

display: grid;

grid-template-columns: 1fr 1fr;

gap: 12px; /* Slightly more gap */

}

/* File Upload Styling */

input[type="file"] {

padding: 10px 12px;

}

input[type="file"]::file-selector-button {

background: var(--border);

color: var(--text-main);

border: none;

padding: 6px 10px;

border-radius: var(--radius);

cursor: pointer;

margin-right: 10px;

font-size: 13px;

transition: background 0.2s, color 0.2s;

}

input[type="file"]::file-selector-button:hover {

background: var(--text-muted);

color: var(--text-main);

}

/* Output Section */

#photoPreviews {

display: flex;

flex-wrap: wrap;

gap: 10px; /* Slightly more gap */

min-height: 90px; /* Increased min-height */

background: var(--bg-input);

border-radius: var(--radius);

border: 1px dashed var(--border);

padding: 10px;

align-items: center;

justify-content: center;

color: var(--text-muted);

font-size: 12px;

position: relative;

}

#photoPreviews:empty::before {

content: "No images selected";

position: absolute;

left: 50%;

top: 50%;

transform: translate(-50%, -50%);

white-space: nowrap;

}

.previewImgContainer {

position: relative;

width: 75px; /* Larger preview images */

height: 75px;

border-radius: var(--radius);

border: 1px solid var(--border);

overflow: hidden;

display: flex;

align-items: center;

justify-content: center;

}

img.previewImg {

width: 100%;

height: 100%;

object-fit: cover;

}

.removePreview {

position: absolute;

top: 4px; /* Adjusted position */

right: 4px; /* Adjusted position */

background: rgba(0,0,0,0.7); /* Slightly darker background */

color: white;

border-radius: 50%;

width: 20px; /* Slightly larger remove button */

height: 20px;

display: flex;

align-items: center;

justify-content: center;

font-size: 13px;

cursor: pointer;

line-height: 1;

transition: background 0.2s;

}

.removePreview:hover {

background: var(--accent);

}

#promptOutput {

background: var(--bg-input);

border: 1px solid var(--border);

padding: 14px; /* Increased padding */

border-radius: var(--radius);

min-height: 160px; /* Increased min-height */

font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;

font-size: 13px; /* Slightly larger font */

line-height: 1.6; /* Increased line-height for readability */

color: var(--text-main);

white-space: pre-wrap;

box-: inset 0 2px 4px rgba(0,0,0,0.3); /* Darker inset */

word-break: break-word;

overflow-y: auto;

}

/* Buttons */

.action-btn {

width: 100%;

padding: 12px; /* More padding */

border: none;

border-radius: var(--radius);

font-weight: 600;

font-size: 14px; /* Slightly larger button text */

cursor: pointer;

transition: transform 0.1s, background 0.2s, box- 0.2s;

}

#generatePrompt {

background: var(--accent); /* Solid accent color */

color: white;

margin-bottom: 10px; /* Slightly more margin */

box-: 0 2px 8px rgba(var(--accent-rgb), 0.4); /* Stronger */

}

#generatePrompt:hover {

transform: translateY(-1px);

background: var(--accent-hover);

box-: 0 4px 12px rgba(var(--accent-rgb), 0.5);

}

#generatePrompt:disabled {

background: var(--bg-input);

color: var(--text-muted);

border: 1px solid var(--border);

box-: none;

transform: none;

cursor: not-allowed;

}

.secondary {

background: var(--bg-input); /* Secondary button matches input background */

border: 1px solid var(--border);

color: var(--text-muted);

}

.secondary:hover {

border-color: var(--accent);

color: var(--accent);

background: rgba(var(--accent-rgb), 0.1); /* Subtle hover background */

}

.secondary:disabled { opacity: 0.6; cursor: not-allowed; background: var(--bg-input); color: var(--text-muted); }

.separator { height: 1px; background: var(--border); margin: 10px 0; } /* Slightly more margin */

/* AI Output Area */

#aiModelsContainer {

display: flex;

flex-direction: column;

gap: 12px; /* Slightly more gap */

}

#aiModelsContainer div {

background: var(--bg-input);

padding: 12px; /* Increased padding */

border-radius: var(--radius);

border-left: 3px solid var(--accent); /* Keep accent for emphasis */

font-size: 12px;

color: var(--text-main);

word-break: break-word;

overflow-y: auto;

}

#aiModelsContainer strong {

color: var(--accent); /* Accent for AI model names */

}

/* Toast */

.toast {

position: fixed; bottom: 20px; right: 20px;

background: var(--accent); color: white;

font-weight: 600;

padding: 12px 22px; /* Slightly more padding */

border-radius: 4px;

box-: 0 4px 10px rgba(var(--accent-rgb), 0.6); /* Stronger */

opacity: 0; transform: translateY(20px);

transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);

z-index: 2000;

font-size: 14px;

}

.toast.show { opacity: 1; transform: translateY(0); }

/* Info link styling - Tooltips */

.info-link {

margin-left: 8px; /* Slightly more margin */

font-size: 0.8em;

color: var(--accent);

cursor: pointer;

text-decoration: none;

font-weight: normal;

position: relative;

display: inline-flex;

align-items: center;

justify-content: center;

width: 18px; /* Slightly larger hit area */

height: 18px;

border-radius: 50%;

background: rgba(var(--accent-rgb), 0.15); /* Lighter tint of accent */

color: var(--accent);

font-weight: bold;

transition: background 0.2s;

}

.info-link:hover {

background: var(--accent);

color: white;

}

.info-tooltip {

position: absolute;

top: 100%;

left: 50%;

transform: translateX(-50%);

background: var(--bg-input); /* Consistent input background */

border: 1px solid var(--border);

padding: 10px 14px; /* Increased padding */

border-radius: var(--radius);

font-size: 12px;

color: var(--text-main);

white-space: normal;

min-width: 220px; /* Increased min-width */

max-width: 320px; /* Increased max-width */

z-index: 1001;

opacity: 0;

pointer-events: none;

transition: opacity 0.2s ease, transform 0.2s ease;

box-: var(--);

margin-top: 6px; /* Small gap below '?' */

}

.info-link:hover .info-tooltip {

opacity: 1;

pointer-events: auto;

transform: translateX(-50%) translateY(0);

}

/* Input group for mood and randomize button */

.mood-input-group {

display: flex;

gap: 10px; /* Slightly more gap */

align-items: flex-end;

}

.mood-input-group > div {

flex-grow: 1;

}

.mood-input-group button {

padding: 10px 14px; /* Increased padding */

font-size: 13px;

border-radius: var(--radius);

white-space: nowrap;

height: 38px; /* Match input height + padding */

background: var(--accent);

color: white;

border: none;

cursor: pointer;

transition: background 0.2s;

}

.mood-input-group button:hover {

background: var(--accent-hover);

}

/* Additional Instructions Specific Styling */

.additional-instructions-group {

display: flex;

flex-direction: column;

gap: 10px; /* Slightly more gap */

}

.additional-instructions-options {

display: flex;

flex-wrap: wrap;

gap: 8px; /* Slightly more gap */

}

.additional-instructions-options button {

background: var(--bg-input);

border: 1px solid var(--border);

color: var(--text-muted);

padding: 8px 12px; /* Increased padding */

border-radius: var(--radius);

font-size: 12px;

cursor: pointer;

transition: all 0.2s;

white-space: nowrap;

}

.additional-instructions-options button:hover {

border-color: var(--accent);

color: var(--accent);

background: rgba(var(--accent-rgb), 0.1);

}

.additional-instructions-options button.selected {

background: var(--accent);

color: white;

border-color: var(--accent);

}

/* Target AIs Select */

#targetAIs {

min-height: 90px; /* Ensure visibility for multiple select */

height: auto;

padding: 10px 12px;

}

#targetAIs option {

padding: 6px 0;

}

#targetAIs option:checked {

background-color: var(--accent);

color: white;

}

/* Ensure header buttons are styled correctly */

.header-actions button {

background: var(--bg-input);

border: 1px solid var(--border);

color: var(--text-muted);

padding: 8px 12px;

border-radius: var(--radius);

font-size: 13px;

height: auto;

white-space: nowrap;

}

.header-actions button:hover {

border-color: var(--accent);

color: var(--accent);

background: rgba(var(--accent-rgb), 0.1);

}

/* Tabbed Interface for Left Column */

.tab-bar {

display: flex;

border-bottom: 1px solid var(--border);

margin-bottom: 0;

overflow-x: auto;

padding-bottom: 5px;

background: var(--bg-panel);

border-top-left-radius: var(--radius);

border-top-right-radius: var(--radius);

padding-left: var(--panel-spacing);

padding-right: var(--panel-spacing);

}

.tab-button {

flex-shrink: 0;

padding: 12px 18px; /* Increased padding */

border: 1px solid transparent;

border-bottom: none;

background: transparent;

color: var(--text-muted);

font-size: 14px;

font-weight: 500;

cursor: pointer;

transition: all 0.2s ease;

border-radius: var(--radius) var(--radius) 0 0;

margin-bottom: -1px;

display: flex; /* For icon alignment */

align-items: center;

gap: 6px; /* Space between icon and text */

}

.tab-button:hover {

color: var(--text-main);

background: var(--bg-input);

}

.tab-button.active {

color: var(--accent); /* Accent for active tab */

border-color: var(--border);

border-bottom-color: var(--bg-panel);

background: var(--bg-panel);

font-weight: 600; /* Bolder for active */

}

.tab-content {

display: none;

flex-direction: column;

gap: var(--panel-spacing);

padding: var(--panel-spacing);

border: 1px solid var(--border);

border-top: none;

border-bottom-left-radius: var(--radius);

border-bottom-right-radius: var(--radius);

background: var(--bg-panel);

}

.tab-content.active {

display: flex;

}

/* Accordion Styling for Advanced Sections */

.accordion-item {

border-bottom: 1px solid var(--border);

padding-bottom: 8px; /* Increased padding */

}

.accordion-item:last-of-type {

border-bottom: none;

padding-bottom: 0;

}

.accordion-header {

display: flex;

align-items: center;

cursor: pointer;

padding: 12px 0; /* Increased padding */

font-size: 16px; /* Slightly larger for clarity */

font-weight: 600;

color: var(--text-main);

transition: color 0.2s;

}

.accordion-header:hover {

color: var(--accent);

}

.accordion-header.expanded {

color: var(--accent); /* Accent when expanded */

}

.accordion-header .arrow {

margin-left: auto;

transition: transform 0.2s ease;

font-size: 1.2em;

}

.accordion-header.expanded .arrow {

transform: rotate(90deg);

}

.accordion-content {

max-height: 0;

overflow: hidden;

transition: max-height 0.3s ease-out;

padding-top: 0;

display: flex;

flex-direction: column;

gap: 12px; /* Increased spacing for elements within accordion */

}

.accordion-content.expanded {

padding-top: 12px; /* Add padding when expanded */

}

.accordion-content .input-row {

margin-top: 0;

}

.accordion-content label { margin-top: 0; }

.accordion-content label:first-of-type { margin-top: 14px; } /* First label in content gets top margin */

.accordion-header::before { /* Consistent accent line */

content: '';

display: inline-block;

width: 4px;

height: 16px;

background-color: var(--accent);

margin-right: 8px;

border-radius: 2px;

flex-shrink: 0;

}

.suggest-button {

background: #50664d; /* Muted, professional green/grey */

color: white;

border: none;

padding: 10px 14px;

border-radius: var(--radius);

font-size: 13px;

font-weight: 600;

cursor: pointer;

transition: background 0.2s;

margin-top: 12px; /* Increased margin */

box-: 0 2px 5px rgba(0, 0, 0, 0.3);

}

.suggest-button:hover {

background: #6a8067;

}

/* Dark Rustic Preset Button - Adjusted to new dark theme earthy tone */

#darkRusticPresetBtn {

background: #50664d !important; /* Muted, professional green/grey */

color: white !important;

border: none !important;

box-: 0 2px 5px rgba(0, 0, 0, 0.3);

}

#darkRusticPresetBtn:hover {

background: #6a8067 !important;

}

/* Mobile Responsive */

@media (max-width: 900px) {

body { padding: 0; }

.main-app-wrapper {

margin: 0;

border-radius: 0;

min-height: 100vh;

}

.app-container {

flex-direction: column;

padding: 10px;

gap: 10px;

}

.left-column { min-width: auto; width: 100%; }

.right-column {

width: 100%;

flex: 1 1 auto;

position: static;

max-height: none;

}

header {

flex-direction: column;

gap: 8px;

padding: 10px;

height: auto;

}

h1 { margin-bottom: 5px; }

.header-actions {

width: 100%;

justify-content: center;

}

.header-actions button {

flex-grow: 1;

max-width: 120px;

}

.tab-bar { justify-content: flex-start; }

.tab-button { padding: 10px 12px; font-size: 13px; gap: 4px; } /* Adjust tab button size for mobile */

.tab-content {

border-left: none;

border-right: none;

border-radius: 0;

}

.settingsBox {

border-radius: 0;

border-left: none;

border-right: none;

}

}

/* New Styling for Photography Type Selector */

.photography-type-selector {

margin-bottom: var(--panel-spacing);

padding-bottom: var(--panel-spacing);

border-bottom: 1px solid var(--border);

}

.photography-type-selector label {

margin-top: 0; /* Override default label margin for this specific label */

}

.photography-type-selector select {

width: auto; /* Allow select to shrink to content */

max-width: 100%;

}

</style>

</head>

<body>

<div class="main-app-wrapper">

<header>

<h1>VisualLock <span class="ai">AI</span><span class="pro">PRO</span></h1>

<div class="header-actions">

<button id="savePresetBtn" class="secondary">💾 Save Preset</button>

<input type="file" id="loadPresetInput" accept=".json" style="display: none;">

<button id="loadPresetBtn" class="secondary">📂 Load Preset</button>

<button id="supportBtn" class="secondary">Donate</button>

</div>

</header>

<div class="app-container">

<!-- LEFT COLUMN: INPUTS -->

<div class="left-column">

<!-- Photography Type Selector -->

<div class="settingsBox photography-type-selector">

<label for="photographyType">Photography Type</label>

<select id="photographyType">

<option value="product">Product Photography</option>

<option value="portrait">Portrait Photography</option>

</select>

</div>

<!-- Consolidated Settings Box for Tabs -->

<div class="settingsBox tabbed-panel">

<div class="tab-bar">

<!-- Added unicode icons for a Photoshop-like feel. For Font Awesome, use <i class="fas fa-star"></i> -->

<button class="tab-button active" data-tab="basicSettings">⭐ Basic</button>

<button class="tab-button" data-tab="advancedSettings">⚙️ Advanced</button>

<button class="tab-button" data-tab="stylingSettings">🎨 Styling</button>

<button class="tab-button" data-tab="outputSettings">📄 Output</button>

</div>

<!-- Product Photography Settings -->

<div id="productSettings" class="tab-content-group active">

<div id="basicSettings" class="tab-content active">

<label>Subject & Reference</label>

<div class="fieldDesc">Upload up to 3 reference images.</div>

<input type="file" id="referencePhotos" accept="image/*" multiple>

<div id="photoPreviews"></div>

<label>Product Description</label>

<input type="text" id="description" placeholder="e.g. Tactical handgun, matte black finish">

<div class="input-row">

<div>

<label>Height</label>

<input type="text" id="productHeight" placeholder="e.g. 1 ft 4 in">

</div>

<div>

<label>Width</label>

<input type="text" id="productWidth" placeholder="e.g. 0 ft 6 in">

</div>

</div>

<label>Environment</label>

<select id="environment">

<option value="studio neutral">Studio Neutral</option>

<option value="indoor lifestyle">Indoor Lifestyle</option>

<option value="outdoor natural">Outdoor Natural</option>

<option value="controlled tactical">Controlled Tactical</option>

<option value="futuristic urban">Futuristic Urban</option>

<option value="rustic countryside">Rustic Countryside</option>

</select>

<label>Lighting Base</label>

<select id="lighting">

<option value="soft studio lighting">Soft Studio</option>

<option value="directional window lighting">Window Light</option>

<option value="overcast outdoor lighting">Overcast</option>

<option value="controlled dramatic lighting">Dramatic</option>

<option value="spotlight">Spotlight</option>

<option value="high-key">High-Key</option>

<option value="low-key">Low-Key</option>

<option value="golden hour">Golden Hour</option>

</select>

<div class="input-row">

<div>

<label>Angle</label>

<select id="cameraPerspective">

<option value="product-level">Product-Level</option>

<option value="eye-level">Eye-Level</option>

<option value="slight low-angle">Low Angle</option>

<option value="high-angle">High Angle</option>

<option value="flat lay (overhead)">Flat Lay</option>

</select>

</div>

<div>

<label>Aspect Ratio</label>

<select id="aspectRatio">

<option value="1:1">1:1 (Square)</option>

<option value="4:3">4:3 (Standard)</option>

<option value="3:2">3:2 (Classic)</option>

<option value="16:9">16:9 (Wide)</option>

<option value="9:16">9:16 (Vertical)</option>

</select>

</div>

</div>

<label>Background Detail</label>

<input type="text" id="background" placeholder="e.g. matte black gradient, concrete texture">

</div>

<div id="advancedSettings" class="tab-content">

<!-- Lighting Refinement (from popup) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="lightingDetails">💡 Refine Lighting <span class="arrow">▶</span></div>

<div class="accordion-content" id="lightingDetailsAccordion">

<div class="input-row">

<div><label>Source</label><input type="text" id="lightSource" placeholder="e.g. strobe"></div>

<div><label>Direction</label><input type="text" id="lightDirection" placeholder="e.g. side"></div>

</div>

<label>Intent / Mood</label><input type="text" id="lightIntent" placeholder="e.g. dramatic">

<div class="input-row">

<div><div><label>Count</label><input type="text" id="lightCount" placeholder="e.g. 3-point"></div></div>

<div><div><label>Modifiers</label><input type="text" id="lightModifiers" placeholder="e.g. softbox"></div></div>

</div>

<label>Reflections</label><input type="text" id="reflectionsGlares" placeholder="e.g. specular highlights">

</div>

</div>

<!-- Advanced Camera (from popup) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="cameraDetails">📷 Advanced Camera <span class="arrow">▶</span></div>

<div class="accordion-content" id="cameraDetailsAccordion">

<div class="input-row">

<div><label>Aperture <a href="#" class="info-link" data-info="aperture">? <span class="info-tooltip">Aperture (f-stop): Controls how much light enters the camera and the depth of field (how much of the image is in focus). Lower f-numbers (like f/2.8) mean more light and a shallower depth of field (blurry background).</span></a></label><input type="text" id="aperture" placeholder="2.8"></div>

<div><label>Shutter <a href="#" class="info-link" data-info="shutterSpeed">? <span class="info-tooltip">Shutter Speed: Determines how long the camera's sensor is exposed to light. Faster speeds (like 1/1000s) freeze motion; slower speeds (like 1/30s) can create motion blur.</span></a></label><input type="text" id="shutterSpeed" placeholder="1/125"></div>

</div>

<div class="input-row">

<div><label>ISO <a href="#" class="info-link" data-info="iso">? <span class="info-tooltip">ISO: Measures the camera sensor's sensitivity to light. Higher ISO allows for shooting in darker conditions but can introduce 'noise' (graininess) into the image.</span></a></label><input type="text" id="iso" placeholder="100"></div>

<div><label>WB (K) <a href="#" class="info-link" data-info="whiteBalance">? <span class="info-tooltip">White Balance (WB K): Adjusts the color temperature of your photos to make whites appear truly white, compensating for different light sources (e.g., warm indoor light vs. cool outdoor light). Measured in Kelvin (K).</span></a></label><input type="text" id="whiteBalance" placeholder="5600"></div>

</div>

<div class="input-row">

<div><label>Focal Length <a href="#" class="info-link" data-info="focalLength">? <span class="info-tooltip">Focal Length (mm): Refers to the lens's ability to magnify the scene. Shorter focal lengths (e.g., 24mm) offer a wide-angle view, while longer ones (e.g., 200mm) provide a telephoto (zoomed-in) view.</span></a></label><input type="text" id="focalLength" placeholder="85mm"></div>

<div><label>Lens Type</label><input type="text" id="lensType" placeholder="Macro"></div>

</div>

<label>Focus Method</label><input type="text" id="focusMethod" placeholder="e.g. focus stacking">

</div>

</div>

<!-- Props (from popup) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="propsDetails">📦 Add Props <span class="arrow">▶</span></div>

<div class="accordion-content" id="propsDetailsAccordion">

<button id="suggestPropComboBtn" class="suggest-button">Suggest Combo</button>

<div class="input-row">

<div><label>Prop 1</label><input type="text" id="prop1"></div>

<div><label>Dims</label><input type="text" id="prop1Dim"></div>

</div>

<div class="input-row">

<div><label>Prop 2</label><input type="text" id="prop2"></div>

<div><label>Dims</label><input type="text" id="prop2Dim"></div>

</div>

<div class="input-row">

<div><label>Prop 3</label><input type="text" id="prop3"></div>

<div><label>Dims</label><input type="text" id="prop3Dim"></div>

</div>

<div class="input-row">

<div><label>Prop 4</label><input type="text" id="prop4"></div>

<div><label>Dims</label><input type="text" id="prop4Dim"></div>

</div>

</div>

</div>

</div>

<div id="stylingSettings" class="tab-content">

<!-- Surface & Mood (existing) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="surfaceMood">✨ Surface & Mood <span class="arrow">▶</span></div>

<div class="accordion-content expanded" id="surfaceMoodAccordion">

<div class="input-row">

<div>

<label>Material</label>

<input type="text" id="surfaceMaterial" placeholder="e.g. polished marble">

</div>

<div>

<label>Color</label>

<input type="text" id="surfaceColor" placeholder="e.g. pure white">

</div>

</div>

<label>Atmosphere</label>

<div class="mood-input-group">

<div>

<input type="text" id="overallMood" placeholder="e.g. luxurious, industrial, serene">

</div>

<button id="randomizeMoodBtn">Randomize Mood</button>

</div>

</div>

</div>

<!-- Composition (from popup) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="compositionDetails">📐 Composition <span class="arrow">▶</span></div>

<div class="accordion-content" id="compositionDetailsAccordion">

<label>Rule</label><input type="text" id="compRule" placeholder="e.g. rule of thirds">

<div class="input-row">

<div><label>Framing</label><input type="text" id="framing" placeholder="e.g. medium shot"></div>

<div><label>Neg. Space</label><input type="text" id="negativeSpace" placeholder="e.g. ample"></div>

</div>

<label>Depth of Field</label><input type="text" id="dofDesc" placeholder="e.g. creamy bokeh">

</div>

</div>

<!-- Post Processing (from popup) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="postProcessingDetails">🎨 Post-Processing <span class="arrow">▶</span></div>

<div class="accordion-content" id="postProcessingDetailsAccordion">

<div class="input-row">

<div><label>Grading</label><input type="text" id="colorGrading" placeholder="e.g. vibrant"></div>

<div><label>Contrast</label><input type="text" id="contrast" placeholder="e.g. high"></div>

</div>

<div class="input-row">

<div><label>Sharpness</label><input type="text" id="sharpness" placeholder="e.g. sharp"></div>

<div><label>Grain</label><input type="text" id="grainNoise" placeholder="e.g. minimal"></div>

</div>

<div class="input-row">

<div><label>Retouch</label><input type="text" id="retouching" placeholder="e.g. flawless"></div>

<div><label>Vignette</label><input type="text" id="vignetting" placeholder="e.g. subtle"></div>

</div>

</div>

</div>

</div>

<div id="outputSettings" class="tab-content">

<div class="accordion-item">

<div class="accordion-header" data-accordion="additionalInstructions">📄 Additional Instructions <span class="arrow">▶</span></div>

<div class="accordion-content expanded" id="additionalInstructionsAccordion">

<div class="additional-instructions-group">

<div class="additional-instructions-options">

<button type="button" data-value="Clean, dry, polished black metal surface, no smudges, no oil, matte finish">Clean Matte Finish</button>

<button type="button" data-value="Pristine gun frame, perfectly clean, no residue, smooth, even surface">Pristine Surface</button>

<button type="button" data-value="Black firearm finish, free of imperfections, dry, no fingerprints, studio quality">Studio Quality Finish</button>

</div>

<textarea id="additional" rows="3" placeholder="Any specific details or exclusions..."></textarea>

</div>

</div>

</div>

<!-- Preset Button - Moved here for logical grouping with "instructions" for output -->

<button id="darkRusticPresetBtn" class="action-btn secondary">🍂 Dark Rustic Preset</button>

</div>

</div> <!-- End of productSettings -->

<!-- Portrait Photography Settings -->

<div id="portraitSettings" class="tab-content-group" style="display: none;">

<div id="basicSettingsPortrait" class="tab-content active">

<label>Subject Description</label>

<input type="text" id="portraitSubjectDescription" placeholder="e.g. A young woman with long wavy hair">

<label>Age & Gender</label>

<div class="input-row">

<div><input type="text" id="portraitAge" placeholder="e.g. 20s"></div>

<div><input type="text" id="portraitGender" placeholder="e.g. Female"></div>

</div>

<label>Ethnicity / Background</label>

<input type="text" id="portraitEthnicity" placeholder="e.g. Caucasian, Asian, African-American">

<label>Expression & Pose</label>

<input type="text" id="portraitExpressionPose" placeholder="e.g. smiling gently, looking directly at camera, hands clasped">

<label>Attire / Wardrobe</label>

<input type="text" id="portraitAttire" placeholder="e.g. elegant evening gown, casual streetwear">

<label>Location / Background</label>

<select id="portraitEnvironment">

<option value="studio seamless background">Studio Seamless Background</option>

<option value="urban cityscape">Urban Cityscape</option>

<option value="natural outdoor park">Natural Outdoor Park</option>

<option value="cozy indoor cafe">Cozy Indoor Cafe</option>

<option value="historic building interior">Historic Building Interior</option>

<option value="dramatic natural landscape">Dramatic Natural Landscape</option>

</select>

<label>Lighting Base</label>

<select id="portraitLighting">

<option value="natural soft window light">Natural Soft Window Light</option>

<option value="studio butterfly lighting">Studio Butterfly Lighting</option>

<option value="rim light, dramatic">Rim Light, Dramatic</option>

<option value="golden hour backlight">Golden Hour Backlight</option>

<option value="high-key, bright">High-Key, Bright</option>

<option value="low-key, moody">Low-Key, Moody</option>

</select>

<div class="input-row">

<div>

<label>Shot Type</label>

<select id="portraitShotType">

<option value="headshot">Headshot</option>

<option value="bust shot">Bust Shot</option>

<option value="half-body shot">Half-Body Shot</option>

<option value="full-body shot">Full-Body Shot</option>

<option value="environmental portrait">Environmental Portrait</option>

</select>

</div>

<div>

<label>Aspect Ratio</label>

<select id="portraitAspectRatio">

<option value="1:1">1:1 (Square)</option>

<option value="4:3">4:3 (Standard)</option>

<option value="3:2">3:2 (Classic)</option>

<option value="16:9">16:9 (Wide)</option>

<option value="9:16">9:16 (Vertical)</option>

</select>

</div>

</div>

</div>

<div id="advancedSettingsPortrait" class="tab-content">

<!-- Lighting Refinement (adapted for portrait) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="lightingDetailsPortrait">💡 Refine Lighting <span class="arrow">▶</span></div>

<div class="accordion-content" id="lightingDetailsAccordionPortrait">

<div class="input-row">

<div><label>Source</label><input type="text" id="lightSourcePortrait" placeholder="e.g. large softbox"></div>

<div><label>Direction</label><input type="text" id="lightDirectionPortrait" placeholder="e.g. 45 degrees left"></div>

</div>

<label>Intent / Mood</label><input type="text" id="lightIntentPortrait" placeholder="e.g. ethereal, dramatic">

<div class="input-row">

<div><div><label>Count</label><input type="text" id="lightCountPortrait" placeholder="e.g. 2-light setup"></div></div>

<div><div><label>Modifiers</label><input type="text" id="lightModifiersPortrait" placeholder="e.g. beauty dish, reflector"></div></div>

</div>

<label>Catchlights</label><input type="text" id="catchlightsPortrait" placeholder="e.g. natural, sparkling">

</div>

</div>

<!-- Advanced Camera (shared or slightly adapted) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="cameraDetailsPortrait">📷 Advanced Camera <span class="arrow">▶</span></div>

<div class="accordion-content" id="cameraDetailsAccordionPortrait">

<div class="input-row">

<div><label>Aperture <a href="#" class="info-link" data-info="aperture">? <span class="info-tooltip">Aperture (f-stop): Controls how much light enters the camera and the depth of field (how much of the image is in focus). Lower f-numbers (like f/2.8) mean more light and a shallower depth of field (blurry background).</span></a></label><input type="text" id="aperturePortrait" placeholder="1.8"></div>

<div><label>Shutter <a href="#" class="info-link" data-info="shutterSpeed">? <span class="info-tooltip">Shutter Speed: Determines how long the camera's sensor is exposed to light. Faster speeds (like 1/1000s) freeze motion; slower speeds (like 1/30s) can create motion blur.</span></a></label><input type="text" id="shutterSpeedPortrait" placeholder="1/160"></div>

</div>

<div class="input-row">

<div><label>ISO <a href="#" class="info-link" data-info="iso">? <span class="info-tooltip">ISO: Measures the camera sensor's sensitivity to light. Higher ISO allows for shooting in darker conditions but can introduce 'noise' (graininess) into the image.</span></a></label><input type="text" id="isoPortrait" placeholder="200"></div>

<div><label>WB (K) <a href="#" class="info-link" data-info="whiteBalance">? <span class="info-tooltip">White Balance (WB K): Adjusts the color temperature of your photos to make whites appear truly white, compensating for different light sources (e.g., warm indoor light vs. cool outdoor light). Measured in Kelvin (K).</span></a></label><input type="text" id="whiteBalancePortrait" placeholder="5200"></div>

</div>

<div class="input-row">

<div><label>Focal Length <a href="#" class="info-link" data-info="focalLength">? <span class="info-tooltip">Focal Length (mm): Refers to the lens's ability to magnify the scene. Shorter focal lengths (e.g., 24mm) offer a wide-angle view, while longer ones (e.g., 200mm) provide a telephoto (zoomed-in) view.</span></a></label><input type="text" id="focalLengthPortrait" placeholder="50mm"></div>

<div><label>Lens Type</label><input type="text" id="lensTypePortrait" placeholder="Prime"></div>

</div>

<label>Focus Point</label><input type="text" id="focusMethodPortrait" placeholder="e.g. on the eyes">

</div>

</div>

<!-- Hair & Makeup / Accessories (new for portrait) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="hairMakeupDetails">💄 Hair & Makeup / Accessories <span class="arrow">▶</span></div>

<div class="accordion-content" id="hairMakeupDetailsAccordion">

<label>Hair Style</label><input type="text" id="hairStyle" placeholder="e.g. flowing curls, slicked back">

<label>Makeup Style</label><input type="text" id="makeupStyle" placeholder="e.g. natural, smoky eyes, bold lips">

<label>Accessories</label><input type="text" id="accessories" placeholder="e.g. delicate necklace, statement earrings, hat">

</div>

</div>

</div>

<div id="stylingSettingsPortrait" class="tab-content">

<!-- Skin Texture & Mood (adapted for portrait) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="skinMoodPortrait">✨ Skin & Mood <span class="arrow">▶</span></div>

<div class="accordion-content expanded" id="skinMoodAccordionPortrait">

<label>Skin Texture</label>

<input type="text" id="skinTexturePortrait" placeholder="e.g. smooth, radiant, natural, glowing">

<label>Overall Mood</label>

<div class="mood-input-group">

<div>

<input type="text" id="overallMoodPortrait" placeholder="e.g. ethereal, powerful, intimate, joyful">

</div>

<button id="randomizeMoodBtnPortrait">Randomize Mood</button>

</div>

</div>

</div>

<!-- Composition (adapted for portrait) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="compositionDetailsPortrait">📐 Composition <span class="arrow">▶</span></div>

<div class="accordion-content" id="compositionDetailsAccordionPortrait">

<label>Rule</label><input type="text" id="compRulePortrait" placeholder="e.g. rule of thirds, leading lines">

<div class="input-row">

<div><label>Framing</label><input type="text" id="framingPortrait" placeholder="e.g. close-up, wide shot"></div>

<div><label>Neg. Space</label><input type="text" id="negativeSpacePortrait" placeholder="e.g. dramatic, balanced"></div>

</div>

<label>Depth of Field</label><input type="text" id="dofDescPortrait" placeholder="e.g. creamy bokeh, sharp background">

</div>

</div>

<!-- Post Processing (adapted for portrait) -->

<div class="accordion-item">

<div class="accordion-header" data-accordion="postProcessingDetailsPortrait">🎨 Post-Processing <span class="arrow">▶</span></div>

<div class="accordion-content" id="postProcessingDetailsAccordionPortrait">

<div class="input-row">

<div><label>Grading</label><input type="text" id="colorGradingPortrait" placeholder="e.g. warm, cinematic, monochrome"></div>

<div><label>Contrast</label><input type="text" id="contrastPortrait" placeholder="e.g. soft, strong"></div>

</div>

<div class="input-row">

<div><label>Sharpness</label><input type="text" id="sharpnessPortrait" placeholder="e.g. crisp, soft focus"></div>

<div><label>Grain</label><input type="text" id="grainNoisePortrait" placeholder="e.g. subtle film grain, clean"></div>

</div>

<div class="input-row">

<div><label>Retouch</label><input type="text" id="retouchingPortrait" placeholder="e.g. subtle skin softening, blemish removal"></div>

<div><label>Vignette</label><input type="text" id="vignettingPortrait" placeholder="e.g. strong, subtle, none"></div>

</div>

</div>

</div>

</div>

<div id="outputSettingsPortrait" class="tab-content">

<div class="accordion-item">

<div class="accordion-header" data-accordion="additionalInstructionsPortrait">📄 Additional Instructions <span class="arrow">▶</span></div>

<div class="accordion-content expanded" id="additionalInstructionsAccordionPortrait">

<div class="additional-instructions-group">

<div class="additional-instructions-options" id="additionalInstructionsOptionsPortrait">

<button type="button" data-value="Natural skin texture, no excessive smoothing or plastic look">Natural Skin</button>

<button type="button" data-value="Eyes sharp and in focus, catchlights present">Sharp Eyes</button>

<button type="button" data-value="Flattering angles, genuine expression">Flattering Portrait</button>

<button type="button" data-value="Avoid distorted features, maintain realistic proportions">Realistic Features</button>

</div>

<textarea id="additionalPortrait" rows="3" placeholder="Any specific details or exclusions..."></textarea>

</div>

</div>

</div>

</div>

</div> <!-- End of portraitSettings -->

</div> <!-- End of settingsBox -->

</div>

<!-- RIGHT COLUMN: OUTPUT (STICKY) -->

<div class="right-column">

<label>Generated Prompt</label>

<div id="promptOutput">Prompt will appear here...</div>

<button id="generatePrompt" class="action-btn">✨ Build Prompt</button>

<button id="copyPrompt" class="action-btn secondary" disabled>Copy to Clipboard</button>

<div style="border-top: 1px solid var(--border); margin-top: 20px; padding-top: 15px;">

<label>AI Optimization Target</label>

<select id="targetAIs" multiple>

<option value="midjourney">MidJourney v6</option>

<option value="dalle">DALL·E 3 / Bing</option>

<option value="gpt">Stable Diffusion / XL</option>

</select>

<div id="aiModelsContainer"></div>

</div>

</div>

</div>

</div>

<div id="toast" class="toast"></div>

<script>

// ---------------------------

// Toast

// ---------------------------

function toast(msg){

const t = document.getElementById("toast");

t.textContent = msg;

t.classList.add("show");

setTimeout(()=>t.classList.remove("show"),2000);

}

// ---------------------------

// Params & Templates - EXPANDED LockAIParams

// ---------------------------

class LockAIParams {

constructor(){

this.photographyType = "product"; // New property

this.subjectType = "product"; // Retained for compatibility with product description

this.description = "";

this.referenceImages = [];

this.environment = "studio neutral";

// EXPANDED LIGHTING

this.lighting = {

style:"soft studio lighting",

source:null,

direction:null,

intent:null,

count: null,

modifiers: null,

reflectionsGlares: null,

catchlights: null // New for portrait

};

// EXPANDED CAMERA

this.camera = {

perspective:"product-level", // For product

shotType:null, // For portrait

aperture:null,

shutterSpeed:null,

ISO:null,

whiteBalance:null,

focalLength: null,

lensType: null,

focusMethod: null, // For product

focusPoint: null // For portrait

};

this.aspectRatio = "1:1";

this.background = null;

this.props = [];

this.additional = "";

this.productHeight = "";

this.productWidth = "";

this.propDims = [];

// NEW PROPERTIES

this.surfaceMaterial = ""; // For product

this.surfaceColor = ""; // For product

this.skinTexture = ""; // For portrait

this.overallMood = "";

// NEW COMPOSITION

this.composition = {

rule: null,

framing: null,

negativeSpace: null,

dofDesc: null

};

// NEW POST-PROCESSING

this.postProcessing = {

colorGrading: null,

contrast: null,

sharpness: null,

grainNoise: null,

retouching: null,

vignetting: null

};

// PORTRAIT-SPECIFIC FIELDS

this.portrait = {

subjectDescription: "",

age: "",

gender: "",

ethnicity: "",

expressionPose: "",

attire: "",

accessories: "",

hairStyle: "",

makeupStyle: ""

};

}

}

// UPDATED promptTemplates for more natural language

const promptTemplates = {

product: `A professional, photorealistic image of the uploaded {{description}}, shown from a {{cameraPerspective}} angle.

Dimensions are approximately {{productHeight}} tall and {{productWidth}} wide.

Crucially, do not alter the original positioning or orientation of the main subject; maintain its exact geometry, proportions, materials, finishes, textures, and markings.

The scene is set in a {{environment}} with {{lightingStyle}} lighting{{lightingDetails}}.

The background features: {{background}}.

Camera settings: {{cameraDetails}}.

Aspect ratio: {{aspectRatio}}.

{{surfaceDetails}}

{{compositionDetails}}

{{props}}{{propDims}}

{{postProcessingDetails}}

{{additional}}

This image should be strictly photorealistic and suitable for commercial use.

`,

portrait: `A photorealistic {{shotType}} portrait of {{portraitSubjectDescription}} ({{portraitAge}}, {{portraitGender}}, {{portraitEthnicity}}).

The subject is in a {{portraitExpressionPose}} pose, wearing {{portraitAttire}}{{accessories}}.

The location is a {{portraitEnvironment}} with {{lightingStyle}} lighting{{lightingDetails}}.

Hair style: {{hairStyle}}. Makeup style: {{makeupStyle}}.

Camera settings: {{cameraDetails}}.

Aspect ratio: {{aspectRatio}}.

{{skinDetails}}

{{compositionDetails}}

{{postProcessingDetails}}

{{additional}}

This image should be strictly photorealistic and suitable for commercial use.

`

};

// UPDATED buildPrompt function to include new parameters and improved cleanup

function buildPrompt(params, template, index=1){

const replacements = {

index,

photographyType: params.photographyType,

description: params.description || params.subjectType, // for product

productHeight: params.productHeight || "unspecified",

productWidth: params.productWidth || "unspecified",

environment: params.environment, // for product

// Portrait specific fields

portraitSubjectDescription: params.portrait.subjectDescription || "a person",

portraitAge: params.portrait.age || "unspecified age",

portraitGender: params.portrait.gender || "unspecified gender",

portraitEthnicity: params.portrait.ethnicity || "unspecified ethnicity",

portraitExpressionPose: params.portrait.expressionPose || "neutral expression",

portraitAttire: params.portrait.attire || "casual clothing",

portraitEnvironment: params.portrait.environment || "a studio seamless background",

shotType: params.camera.shotType || "portrait",

accessories: params.portrait.accessories ? ` with ${params.portrait.accessories}` : "",

hairStyle: params.portrait.hairStyle || "natural hair",

makeupStyle: params.portrait.makeupStyle || "natural makeup",

// Lighting Details

lightingStyle: params.lighting.style,

lightingDetails: (() => {

const parts = [];

if (params.lighting.source) parts.push(`a ${params.lighting.source} light source`);

if (params.lighting.direction) parts.push(`light coming from the ${params.lighting.direction}`);

if (params.lighting.intent) parts.push(`to achieve a ${params.lighting.intent} mood`);

if (params.lighting.count) parts.push(`${params.lighting.count} setup`);

if (params.lighting.modifiers) parts.push(`using ${params.lighting.modifiers}`);

if (params.photographyType === "product" && params.lighting.reflectionsGlares) parts.push(`with ${params.lighting.reflectionsGlares}`);

if (params.photographyType === "portrait" && params.lighting.catchlights) parts.push(`with ${params.lighting.catchlights} catchlights`);

return parts.length > 0 ? ` (${parts.join(", ")})` : "";

})(),

// Camera Details

cameraPerspective: params.camera.perspective, // For product

cameraDetails: (() => {

const parts = [];

if (params.camera.aperture) parts.push(`aperture f/${params.camera.aperture}`);

if (params.camera.shutterSpeed) parts.push(`shutter speed ${params.camera.shutterSpeed}s`);

if (params.camera.ISO) parts.push(`ISO ${params.camera.ISO}`);

if (params.camera.whiteBalance) parts.push(`white balance ${params.camera.whiteBalance}K`);

if (params.camera.focalLength) parts.push(`focal length ${params.camera.focalLength}`);

if (params.camera.lensType) parts.push(`a ${params.camera.lensType} lens`);

if (params.photographyType === "product" && params.camera.focusMethod) parts.push(`focus method: ${params.camera.focusMethod}`);

if (params.photographyType === "portrait" && params.camera.focusPoint) parts.push(`focus point: ${params.camera.focusPoint}`);

return parts.length > 0 ? `${parts.join(", ")}` : "standard camera settings";

})(),

aspectRatio: params.aspectRatio,

background: params.background || "a neutral studio background",

// NEW Surface Details (Product) / Skin Details (Portrait)

surfaceDetails: (() => {

const parts = [];

if (params.surfaceMaterial) parts.push(`The surface is ${params.surfaceMaterial}`);

if (params.surfaceColor) parts.push(`in a ${params.surfaceColor} color`);

if (params.overallMood && params.photographyType === "product") parts.push(`creating a ${params.overallMood} atmosphere`);

return parts.length > 0 ? `${parts.join(" and ")}.` : "";

})(),

skinDetails: (() => {

const parts = [];

if (params.skinTexture) parts.push(`Skin texture is ${params.skinTexture}`);

if (params.overallMood && params.photographyType === "portrait") parts.push(`creating a ${params.overallMood} mood`);

return parts.length > 0 ? `${parts.join(" and ")}.` : "";

})(),

// NEW Composition Details

compositionDetails: (() => {

const parts = [];

if (params.composition.rule) parts.push(`utilizing the ${params.composition.rule} rule`);

if (params.composition.framing) parts.push(`with a ${params.composition.framing} shot`);

if (params.composition.negativeSpace) parts.push(`featuring ${params.composition.negativeSpace} negative space`);

if (params.composition.dofDesc) parts.push(`and ${params.composition.dofDesc}`);

return parts.length > 0 ? `Composition details: ${parts.join(", ")}.` : "";

})(),

props: params.props.length > 0 ? `Props included are: ${params.props.join(", ")}` : "No specific props are added",

propDims: params.propDims.length > 0 ? ` (with dimensions: ${params.propDims.join(", ")})` : "",

// NEW Post-Processing Details

postProcessingDetails: (() => {

const parts = [];

if (params.postProcessing.colorGrading) parts.push(`${params.postProcessing.colorGrading} color grading`);

if (params.postProcessing.contrast) parts.push(`${params.postProcessing.contrast} contrast`);

if (params.postProcessing.sharpness) parts.push(`${params.postProcessing.sharpness} sharpness`);

if (params.postProcessing.grainNoise) parts.push(`${params.postProcessing.grainNoise} grain/noise`);

if (params.postProcessing.retouching) parts.push(`${params.postProcessing.retouching} retouching`);

if (params.postProcessing.vignetting) parts.push(`${params.postProcessing.vignetting} vignetting`);

return parts.length > 0 ? `Post-processing includes: ${parts.join(", ")}.` : "";

})(),

additional: params.additional ? `Additional notes: ${params.additional}.` : ""

};

let prompt = template;

Object.entries(replacements).forEach(([k,v])=>{

prompt = prompt.replace(new RegExp(`{{${k}}}`,"g"), v);

});

// --- Advanced cleanup for natural language prompts ---

// Remove lines that are left empty or contain only default/empty phrases

prompt = prompt.split('\n').filter(line => {

const trimmedLine = line.trim();

if (!trimmedLine) return false; // Remove entirely empty lines

if (trimmedLine === "Dimensions are approximately unspecified tall and unspecified wide.") return false;

if (trimmedLine === "The scene is set in a studio neutral with soft studio lighting lighting.") return false; // If environment/lighting are defaults and no details

if (trimmedLine === "The background features: a neutral studio background.") return false; // If background is default

if (trimmedLine === "Camera settings: standard camera settings.") return false; // If camera details are default

if (trimmedLine === "No specific props are added (with dimensions: ).") return false; // If props are empty

if (trimmedLine === "No specific props are added.") return false; // If props are empty

if (trimmedLine === "Composition details:.") return false; // If composition is empty

if (trimmedLine === "Post-processing includes:.") return false; // If post-processing is empty

if (trimmedLine === "Additional notes:.") return false; // If additional is empty

if (trimmedLine === "The surface is.") return false; // If surface details are empty (product)

if (trimmedLine === "Skin texture is.") return false; // If skin details are empty (portrait)

if (trimmedLine === "Hair style: natural hair. Makeup style: natural makeup.") return false; // If portrait specifics are default

if (trimmedLine.match(/A photorealistic (.*?) portrait of a person \(unspecified age, unspecified gender, unspecified ethnicity\)\./)) return false; // Default portrait line

if (trimmedLine.match(/The subject is in a neutral expression pose, wearing casual clothing\./)) return false; // Default portrait subject details

// More robust check for lighting details - if it only contains the style and then an empty parentheses group

if (trimmedLine.match(/with (.*?) lighting \(\)/)) {

const lightingStyleMatch = trimmedLine.match(/with (.*?) lighting/);

if (lightingStyleMatch && lightingStyleMatch[1] === params.lighting.style) {

const currentLightingDetails = replacements.lightingDetails.trim();

if (currentLightingDetails === "()") return false; // If only "()" is left from lightingDetails

}

}

return true;

}).join('\n');

// Remove any remaining multiple spaces or leading/trailing punctuation from the cleanup

prompt = prompt.replace(/\s\s+/g, " "); // Replace multiple spaces with single space

prompt = prompt.replace(/,\s*\./g, "."); // Remove commas before periods

prompt = prompt.replace(/:\s*\./g, "."); // Remove colons before periods

prompt = prompt.replace(/\.\s*\./g, "."); // Remove double periods

prompt = prompt.replace(/( and )+\./g, "."); // Remove "and ." pattern

prompt = prompt.replace(/,\s*$/gm, ""); // Remove trailing commas at end of lines

prompt = prompt.replace(/\s*,\s*\./g, "."); // Catch cases like "item, ." -> "item."

prompt = prompt.replace(/\s\.\s*$/gm, "."); // Ensure a single period at the end of a sentence if it was modified

return prompt.trim();

}

function generateLockAIPrompts(params, targetAIs=[]){

const template = promptTemplates[params.photographyType];

if(!template) throw new Error(`No template for photography type ${params.photographyType}`);

const basePrompts = params.referenceImages.map((_,i)=>buildPrompt(params,template,i+1));

const optimizedPrompts = {};

targetAIs.forEach(ai=>{

optimizedPrompts[ai] = basePrompts.map(p=>{

if(ai==="midjourney") {

const arValue = params.aspectRatio.replace(":", "_");

// MidJourney specific modifications, simplified as per the new natural language prompt structure

let mjPrompt = p;

mjPrompt = mjPrompt.replace(/Environment: (.*?), Lighting: (.*?)\./g, 'Environment: $1, Lighting: $2');

mjPrompt = mjPrompt.replace(/Camera settings: (.*?)\./g, 'Camera: $1.');

mjPrompt = mjPrompt.replace(/Props included are: (.*?)( \(with dimensions: .*?\))?\./g, 'Props: $1$2.');

mjPrompt = mjPrompt.replace(/Post-processing includes: (.*?)\./g, 'Post-processing: $1.');

// Add --ar parameter only if not 1:1, as 1:1 is MJ default

return `${mjPrompt} --v 6 ${params.aspectRatio !== "1:1" ? `--ar ${arValue}` : ''}`.trim();

}

if(ai==="dalle" || ai==="gpt") {

// DALL-E/GPT are more flexible with natural language, so the base prompt often works well

}

return p;

});

});

return { basePrompts, optimizedPrompts };

}

// ---------------------------

// UI Elements

// ---------------------------

const photographyTypeSelector = document.getElementById("photographyType");

const productSettingsGroup = document.getElementById("productSettings");

const portraitSettingsGroup = document.getElementById("portraitSettings");

const referencePhotos = document.getElementById("referencePhotos");

const photoPreviews = document.getElementById("photoPreviews");

const generatePromptBtn = document.getElementById("generatePrompt");

const copyPromptBtn = document.getElementById("copyPrompt");

const promptOutput = document.getElementById("promptOutput");

const targetAIs = document.getElementById("targetAIs");

const aiContainer = document.getElementById("aiModelsContainer");

const randomizeMoodBtnProduct = document.getElementById("randomizeMoodBtn");

const overallMoodInputProduct = document.getElementById("overallMood");

const randomizeMoodBtnPortrait = document.getElementById("randomizeMoodBtnPortrait");

const overallMoodInputPortrait = document.getElementById("overallMoodPortrait");

const suggestPropComboBtn = document.getElementById("suggestPropComboBtn");

const additionalInstructionsTextareaProduct = document.getElementById("additional");

const additionalInstructionsOptionsProduct = document.querySelector(".additional-instructions-options"); // For product

const additionalInstructionsTextareaPortrait = document.getElementById("additionalPortrait");

const additionalInstructionsOptionsPortrait = document.getElementById("additionalInstructionsOptionsPortrait"); // For portrait

const darkRusticPresetBtn = document.getElementById("darkRusticPresetBtn");

const savePresetBtn = document.getElementById("savePresetBtn");

const loadPresetBtn = document.getElementById("loadPresetBtn");

const loadPresetInput = document.getElementById("loadPresetInput");

// ---------------------------

// Photography Type Switcher Logic

// ---------------------------

function switchPhotographyType() {

const selectedType = photographyTypeSelector.value;

if (selectedType === "product") {

productSettingsGroup.style.display = 'flex';

portraitSettingsGroup.style.display = 'none';

// Re-activate product tabs if portrait was active

document.getElementById('basicSettings').classList.add('active');

document.getElementById('basicSettingsPortrait').classList.remove('active');

document.querySelector('.tab-bar .tab-button[data-tab="basicSettings"]').classList.add('active');

// The Dark Rustic Preset button is part of product output settings

darkRusticPresetBtn.style.display = 'block';

} else if (selectedType === "portrait") {

productSettingsGroup.style.display = 'none';

portraitSettingsGroup.style.display = 'flex';

// Re-activate portrait tabs if product was active

document.getElementById('basicSettingsPortrait').classList.add('active');

document.getElementById('basicSettings').classList.remove('active');

document.querySelector('.tab-bar .tab-button[data-tab="basicSettings"]').classList.add('active');

// Hide Dark Rustic Preset button for portrait mode

darkRusticPresetBtn.style.display = 'none';

}

updateGeneratedPrompts();

initializeAccordionHeights(); // Re-initialize accordion heights for the visible group

}

photographyTypeSelector.addEventListener("change", switchPhotographyType);

// ---------------------------

// Image Upload & Preview

// ---------------------------

let uploadedImages = []; // Store image data URLs

referencePhotos.addEventListener("change", (e)=>{

const files = Array.from(e.target.files);

// Filter out files that are not images or if over limit

const newImageFiles = files.filter(file => file.type.startsWith('image/') && uploadedImages.length < 3);

if (newImageFiles.length === 0 && files.length > 0) {

if (uploadedImages.length >= 3) {

toast("Maximum 3 reference images reached.");

} else {

toast("Please select image files (JPG, PNG, GIF, etc.).");

}

referencePhotos.value = ""; // Clear file input

return;

}

let loadedCount = 0;

newImageFiles.forEach(file => {

const reader = new FileReader();

reader.onload = e => {

uploadedImages.push(e.target.result);

renderPreviews();

loadedCount++;

if (loadedCount === newImageFiles.length) {

updateGeneratedPrompts();

}

};

reader.readAsDataURL(file);

});

referencePhotos.value = ""; // Clear file input

});

function renderPreviews() {

photoPreviews.innerHTML = "";

if (uploadedImages.length === 0) {

// No specific content needed, CSS :empty::before handles it.

} else {

uploadedImages.forEach((src, index) => {

const container = document.createElement("div");

container.className = "previewImgContainer";

const img = document.createElement("img");

img.src = src;

img.className = "previewImg";

const removeBtn = document.createElement("span");

removeBtn.className = "removePreview";

removeBtn.textContent = "×";

removeBtn.addEventListener("click", () => {

uploadedImages.splice(index, 1);

renderPreviews();

updateGeneratedPrompts(); // Update prompt after removing image

});

container.appendChild(img);

container.appendChild(removeBtn);

photoPreviews.appendChild(container);

});

}

}

// ---------------------------

// Refactored Prompt Generation Function

// This function is now called by both the button and the select change event

// ---------------------------

function updateGeneratedPrompts() {

const params = new LockAIParams();

params.photographyType = photographyTypeSelector.value;

const descriptionIsEmpty = (params.photographyType === "product" && !document.getElementById("description").value.trim()) ||

(params.photographyType === "portrait" && !document.getElementById("portraitSubjectDescription").value.trim());

if (uploadedImages.length === 0 && descriptionIsEmpty) {

promptOutput.textContent = "Enter a description or upload an image to generate prompts.";

aiContainer.innerHTML = "";

copyPromptBtn.disabled = true;

generatePromptBtn.textContent = "Build Prompt";

generatePromptBtn.disabled = false;

return;

}

generatePromptBtn.textContent="Building Prompt…";

generatePromptBtn.disabled=true;

copyPromptBtn.disabled=true;

setTimeout(()=>{

params.referenceImages = uploadedImages.map(src => "image"); // Just placeholder for count

if (params.photographyType === "product") {

params.description = document.getElementById("description").value.trim() || "product";

params.environment = document.getElementById("environment").value;

// Lighting

params.lighting.style = document.getElementById("lighting").value;

params.lighting.source = document.getElementById("lightSource").value.trim() || null;

params.lighting.direction = document.getElementById("lightDirection").value.trim() || null;

params.lighting.intent = document.getElementById("lightIntent").value.trim() || null;

params.lighting.count = document.getElementById("lightCount").value.trim() || null;

params.lighting.modifiers = document.getElementById("lightModifiers").value.trim() || null;

params.lighting.reflectionsGlares = document.getElementById("reflectionsGlares").value.trim() || null;

// Camera

params.camera.perspective = document.getElementById("cameraPerspective").value;

params.camera.aperture = document.getElementById("aperture").value.trim() || null;

params.camera.shutterSpeed = document.getElementById("shutterSpeed").value.trim() || null;

params.camera.ISO = document.getElementById("iso").value.trim() || null;

params.camera.whiteBalance = document.getElementById("whiteBalance").value.trim() || null;

params.camera.focalLength = document.getElementById("focalLength").value.trim() || null;

params.camera.lensType = document.getElementById("lensType").value.trim() || null;

params.camera.focusMethod = document.getElementById("focusMethod").value.trim() || null;

params.aspectRatio = document.getElementById("aspectRatio").value;

params.background = document.getElementById("background").value.trim() || null;

// Product dimensions

params.productHeight = document.getElementById("productHeight").value.trim() || "";

params.productWidth = document.getElementById("productWidth").value.trim() || "";

params.props = [document.getElementById("prop1").value.trim(),

document.getElementById("prop2").value.trim(),

document.getElementById("prop3").value.trim(),

document.getElementById("prop4").value.trim()].filter(Boolean);

params.propDims = [document.getElementById("prop1Dim").value.trim(),

document.getElementById("prop2Dim").value.trim(),

document.getElementById("prop3Dim").value.trim(),

document.getElementById("prop4Dim").value.trim()].filter(Boolean);

params.additional = additionalInstructionsTextareaProduct.value.trim();

// Styling & Surface

params.surfaceMaterial = document.getElementById("surfaceMaterial").value.trim() || "";

params.surfaceColor = document.getElementById("surfaceColor").value.trim() || "";

params.overallMood = document.getElementById("overallMood").value.trim() || "";

// Composition

params.composition.rule = document.getElementById("compRule").value.trim() || null;

params.composition.framing = document.getElementById("framing").value.trim() || null;

params.composition.negativeSpace = document.getElementById("negativeSpace").value.trim() || null;

params.composition.dofDesc = document.getElementById("dofDesc").value.trim() || null;

// Post-Processing

params.postProcessing.colorGrading = document.getElementById("colorGrading").value.trim() || null;

params.postProcessing.contrast = document.getElementById("contrast").value.trim() || null;

params.postProcessing.sharpness = document.getElementById("sharpness").value.trim() || null;

params.postProcessing.grainNoise = document.getElementById("grainNoise").value.trim() || null;

params.postProcessing.retouching = document.getElementById("retouching").value.trim() || null;

params.postProcessing.vignetting = document.getElementById("vignetting").value.trim() || null;

} else if (params.photographyType === "portrait") {

params.portrait.subjectDescription = document.getElementById("portraitSubjectDescription").value.trim() || "a person";

params.portrait.age = document.getElementById("portraitAge").value.trim() || null;

params.portrait.gender = document.getElementById("portraitGender").value.trim() || null;

params.portrait.ethnicity = document.getElementById("portraitEthnicity").value.trim() || null;

params.portrait.expressionPose = document.getElementById("portraitExpressionPose").value.trim() || null;

params.portrait.attire = document.getElementById("portraitAttire").value.trim() || null;

params.portrait.accessories = document.getElementById("accessories").value.trim() || null;

params.portrait.hairStyle = document.getElementById("hairStyle").value.trim() || null;

params.portrait.makeupStyle = document.getElementById("makeupStyle").value.trim() || null;

params.portrait.environment = document.getElementById("portraitEnvironment").value;

params.lighting.style = document.getElementById("portraitLighting").value;

// Lighting Refinement Portrait

params.lighting.source = document.getElementById("lightSourcePortrait").value.trim() || null;

params.lighting.direction = document.getElementById("lightDirectionPortrait").value.trim() || null;

params.lighting.intent = document.getElementById("lightIntentPortrait").value.trim() || null;

params.lighting.count = document.getElementById("lightCountPortrait").value.trim() || null;

params.lighting.modifiers = document.getElementById("lightModifiersPortrait").value.trim() || null;

params.lighting.catchlights = document.getElementById("catchlightsPortrait").value.trim() || null;

params.camera.shotType = document.getElementById("portraitShotType").value;

params.aspectRatio = document.getElementById("portraitAspectRatio").value;

// Camera Advanced Portrait

params.camera.aperture = document.getElementById("aperturePortrait").value.trim() || null;

params.camera.shutterSpeed = document.getElementById("shutterSpeedPortrait").value.trim() || null;

params.camera.ISO = document.getElementById("isoPortrait").value.trim() || null;

params.camera.whiteBalance = document.getElementById("whiteBalancePortrait").value.trim() || null;

params.camera.focalLength = document.getElementById("focalLengthPortrait").value.trim() || null;

params.camera.lensType = document.getElementById("lensTypePortrait").value.trim() || null;

params.camera.focusPoint = document.getElementById("focusMethodPortrait").value.trim() || null; // Using focusMethodPortrait for focusPoint

// Skin & Mood Portrait

params.skinTexture = document.getElementById("skinTexturePortrait").value.trim() || "";

params.overallMood = document.getElementById("overallMoodPortrait").value.trim() || "";

// Composition Portrait

params.composition.rule = document.getElementById("compRulePortrait").value.trim() || null;

params.composition.framing = document.getElementById("framingPortrait").value.trim() || null;

params.composition.negativeSpace = document.getElementById("negativeSpacePortrait").value.trim() || null;

params.composition.dofDesc = document.getElementById("dofDescPortrait").value.trim() || null;

// Post-Processing Portrait

params.postProcessing.colorGrading = document.getElementById("colorGradingPortrait").value.trim() || null;

params.postProcessing.contrast = document.getElementById("contrastPortrait").value.trim() || null;

params.postProcessing.sharpness = document.getElementById("sharpnessPortrait").value.trim() || null;

params.postProcessing.grainNoise = document.getElementById("grainNoisePortrait").value.trim() || null;

params.postProcessing.retouching = document.getElementById("retouchingPortrait").value.trim() || null;

params.postProcessing.vignetting = document.getElementById("vignettingPortrait").value.trim() || null;

params.additional = additionalInstructionsTextareaPortrait.value.trim();

}

const selectedAIs = Array.from(targetAIs.selectedOptions).map(o=>o.value);

const { basePrompts, optimizedPrompts } = generateLockAIPrompts(params, selectedAIs);

promptOutput.textContent = basePrompts.join("\n\n");

aiContainer.innerHTML = "";

Object.entries(optimizedPrompts).forEach(([ai, prompts])=>{

const div = document.createElement("div");

div.innerHTML = `<strong>${ai.toUpperCase()} Optimized:</strong><div style="white-space:pre-wrap; margin-top:5px;">${prompts.join("\n\n")}</div>`;

aiContainer.appendChild(div);

});

copyPromptBtn.disabled=false;

generatePromptBtn.textContent="Build Prompt";

generatePromptBtn.disabled=false;

},200); // Small delay for "Building Prompt..." message visibility

}

// ---------------------------

// Generate Prompt Button Event Listener

// ---------------------------

generatePromptBtn.addEventListener("click", updateGeneratedPrompts);

// ---------------------------

// Auto-Update Prompts on AI Target Change Event Listener

// ---------------------------

targetAIs.addEventListener("change", updateGeneratedPrompts);

// ---------------------------

// Dynamic Prompt Updates for all relevant input fields

// ---------------------------

document.querySelectorAll('.left-column input, .left-column select, .left-column textarea').forEach(element => {

// Exclude file inputs as they have their own change listener

if (element.type !== 'file') {

element.addEventListener('input', updateGeneratedPrompts);

element.addEventListener('change', updateGeneratedPrompts); // For select elements

}

});

// ---------------------------

// Copy

// ---------------------------

copyPromptBtn.addEventListener("click", ()=>{

navigator.clipboard.writeText(promptOutput.textContent);

toast("Prompt copied!");

});

// ---------------------------

// Support Button

// ---------------------------

document.getElementById("supportBtn").addEventListener("click",()=>{

window.open("https://paypal.me/visuallockai","_blank");

});

// ---------------------------

// NEW: Randomize Mood (Product)

// ---------------------------

const moods = [

"luxurious", "industrial", "serene", "energetic", "calm", "dramatic", "minimalist",

"futuristic", "vintage", "rustic", "cozy", "sleek", "vibrant", "moody", "clean", "grungy",

"elegant", "bold", "playful", "sophisticated"

];

randomizeMoodBtnProduct.addEventListener("click", () => {

const randomIndex = Math.floor(Math.random() * moods.length);

overallMoodInputProduct.value = moods[randomIndex];

updateGeneratedPrompts();

});

// ---------------------------

// NEW: Randomize Mood (Portrait)

// ---------------------------

const portraitMoods = [

"ethereal", "powerful", "intimate", "joyful", "melancholy", "confident", "mysterious",

"dreamy", "candid", "glamorous", "raw", "vulnerable", "playful", "serious"

];

randomizeMoodBtnPortrait.addEventListener("click", () => {

const randomIndex = Math.floor(Math.random() * portraitMoods.length);

overallMoodInputPortrait.value = portraitMoods[randomIndex];

updateGeneratedPrompts();

});

// ---------------------------

// NEW: Prop Combos

// ---------------------------

const propCombos = [

["vintage camera", "leather journal"],

["coffee cup", "open book"],

["succulent plant", "geometric vase"],

["wooden block", "dried flower"],

["minimalist watch", "small sculpture"],

["spectacles", "quill pen"],

["sci-fi gadget", "glowing orb"],

["aged map", "compass"],

["velvet cloth", "gold jewelry"],

["ceramic bowl", "fresh fruit"]

];

suggestPropComboBtn.addEventListener("click", () => {

const randomIndex = Math.floor(Math.random() * propCombos.length);

const selectedCombo = propCombos[randomIndex];

document.getElementById("prop1").value = selectedCombo[0];

document.getElementById("prop2").value = selectedCombo[1];

document.getElementById("prop3").value = "";

document.getElementById("prop4").value = "";

document.getElementById("prop1Dim").value = "";

document.getElementById("prop2Dim").value = "";

document.getElementById("prop3Dim").value = "";

document.getElementById("prop4Dim").value = "";

updateGeneratedPrompts();

});

// ---------------------------

// NEW: Additional Instructions Buttons (Product)

// ---------------------------

additionalInstructionsOptionsProduct.querySelectorAll('button').forEach(button => {

button.addEventListener('click', () => {

// Toggle selected class

button.classList.toggle('selected');

// Get all selected values

const selectedValues = Array.from(additionalInstructionsOptionsProduct.querySelectorAll('button.selected'))

.map(btn => btn.dataset.value);

// Update the textarea with selected values, joined by a comma and space

additionalInstructionsTextareaProduct.value = selectedValues.join(', ');

updateGeneratedPrompts();

});

});

// ---------------------------

// NEW: Additional Instructions Buttons (Portrait)

// ---------------------------

additionalInstructionsOptionsPortrait.querySelectorAll('button').forEach(button => {

button.addEventListener('click', () => {

// Toggle selected class

button.classList.toggle('selected');

// Get all selected values

const selectedValues = Array.from(additionalInstructionsOptionsPortrait.querySelectorAll('button.selected'))

.map(btn => btn.dataset.value);

// Update the textarea with selected values, joined by a comma and space

additionalInstructionsTextareaPortrait.value = selectedValues.join(', ');

updateGeneratedPrompts();

});

});

// ---------------------------

// NEW: Dark Rustic Preset Functionality

// ---------------------------

darkRusticPresetBtn.addEventListener("click", () => {

// Ensure product photography is selected first

photographyTypeSelector.value = "product";

switchPhotographyType(); // Apply display changes

// Basic fields

document.getElementById("description").value = "Tactical handgun, matte black finish";

document.getElementById("productHeight").value = "0 ft 8 in";

document.getElementById("productWidth").value = "0 ft 4 in";

document.getElementById("environment").value = "controlled tactical";

document.getElementById("lighting").value = "controlled dramatic lighting";

// Lighting accordion fields

document.getElementById("lightSource").value = "strobe";

document.getElementById("lightDirection").value = "side";

document.getElementById("lightIntent").value = "moody, atmospheric";

document.getElementById("lightCount").value = "1-point";

document.getElementById("lightModifiers").value = "snoot, grid";

document.getElementById("reflectionsGlares").value = "subtle specular highlights, minimal glare";

document.getElementById("cameraPerspective").value = "product-level";

document.getElementById("aspectRatio").value = "4:3";

document.getElementById("background").value = "Dark, rustic wooden table with leather accents, Still life on distressed wood, with a dark, moody background and soft, directional lighting, Vintage Western-style backdrop with old leather, wooden surfaces, and subtle smoke, Dark, atmospheric photography setting with natural elements like wood, leather, and foliage";

// Camera accordion fields

document.getElementById("aperture").value = "2.8";

document.getElementById("shutterSpeed").value = "1/100";

document.getElementById("iso").value = "200";

document.getElementById("whiteBalance").value = "3200";

document.getElementById("focalLength").value = "100mm";

document.getElementById("lensType").value = "macro";

document.getElementById("focusMethod").value = "single point focus, sharpest details on product";

document.getElementById("surfaceMaterial").value = "distressed wood, aged leather";

document.getElementById("surfaceColor").value = "dark brown, deep red";

document.getElementById("overallMood").value = "rustic, moody, vintage, atmospheric";

// Props

document.getElementById("prop1").value = "vintage bullets";

document.getElementById("prop2").value = "leather holster";

document.getElementById("prop3").value = "";

document.getElementById("prop4").value = "";

document.getElementById("prop1Dim").value = "";

document.getElementById("prop2Dim").value = "";

document.getElementById("prop3Dim").value = "";

document.getElementById("prop4Dim").value = "";

// Composition accordion fields

document.getElementById("compRule").value = "rule of thirds, leading lines";

document.getElementById("framing").value = "close-up, medium shot";

document.getElementById("negativeSpace").value = "minimal, controlled negative space";

document.getElementById("dofDesc").value = "creamy bokeh, shallow depth of field";

// Post-Processing accordion fields

document.getElementById("colorGrading").value = "dark, desaturated, warm tones, cinematic grade";

document.getElementById("contrast").value = "high";

document.getElementById("sharpness").value = "sharp, focused on product";

document.getElementById("grainNoise").value = "subtle film grain";

document.getElementById("retouching").value = "minimal, natural imperfections preserved";

document.getElementById("vignetting").value = "strong, dark corners";

// Clear and set additional instructions

additionalInstructionsTextareaProduct.value = "Clean, dry, polished black metal surface, no smudges, no oil, matte finish, do not alter the rotation of the subject, sharp focus, high detail.";

// Deselect all additional instruction buttons, then re-select relevant ones

additionalInstructionsOptionsProduct.querySelectorAll('button').forEach(btn => btn.classList.remove('selected'));

// Simulate clicks on the relevant buttons for the preset

const presetInstructions = [

"Clean, dry, polished black metal surface, no smudges, no oil, matte finish",

"do not alter the rotation of the subject"

];

additionalInstructionsOptionsProduct.querySelectorAll('button').forEach(btn => {

if (btn.dataset.value === "Clean, dry, polished black metal surface, no smudges, no oil, matte finish" ||

btn.dataset.value === "do not alter the rotation of the subject") { // Assuming this is an instruction, or add it as a new button

btn.classList.add('selected');

}

});

// Clear any uploaded images for the preset

uploadedImages = [];

renderPreviews();

updateGeneratedPrompts();

toast("Dark Rustic Preset Applied!");

});

// ---------------------------

// NEW: Save Preset Functionality

// ---------------------------

savePresetBtn.addEventListener("click", () => {

const presetData = {

photographyType: photographyTypeSelector.value,

// Product fields

description: document.getElementById("description").value,

productHeight: document.getElementById("productHeight").value,

productWidth: document.getElementById("productWidth").value,

environment: document.getElementById("environment").value,

lighting: {

style: document.getElementById("lighting").value,

source: document.getElementById("lightSource").value,

direction: document.getElementById("lightDirection").value,

intent: document.getElementById("lightIntent").value,

count: document.getElementById("lightCount").value,

modifiers: document.getElementById("lightModifiers").value,

reflectionsGlares: document.getElementById("reflectionsGlares").value,

catchlights: "" // Ensure this is saved for portrait

},

camera: {

perspective: document.getElementById("cameraPerspective").value,

aperture: document.getElementById("aperture").value,

shutterSpeed: document.getElementById("shutterSpeed").value,

ISO: document.getElementById("iso").value,

whiteBalance: document.getElementById("whiteBalance").value,

focalLength: document.getElementById("focalLength").value,

lensType: document.getElementById("lensType").value,

focusMethod: document.getElementById("focusMethod").value,

shotType: "", // Ensure this is saved for portrait

focusPoint: "" // Ensure this is saved for portrait

},

aspectRatio: document.getElementById("aspectRatio").value,

background: document.getElementById("background").value,

surfaceMaterial: document.getElementById("surfaceMaterial").value,

surfaceColor: document.getElementById("surfaceColor").value,

overallMood: document.getElementById("overallMood").value,

props: [

document.getElementById("prop1").value,

document.getElementById("prop2").value,

document.getElementById("prop3").value,

document.getElementById("prop4").value

],

propDims: [

document.getElementById("prop1Dim").value,

document.getElementById("prop2Dim").value,

document.getElementById("prop3Dim").value,

document.getElementById("prop4Dim").value

],

additional: additionalInstructionsTextareaProduct.value,

composition: {

rule: document.getElementById("compRule").value,

framing: document.getElementById("framing").value,

negativeSpace: document.getElementById("negativeSpace").value,

dofDesc: document.getElementById("dofDesc").value

},

postProcessing: {

colorGrading: document.getElementById("colorGrading").value,

contrast: document.getElementById("contrast").value,

sharpness: document.getElementById("sharpness").value,

grainNoise: document.getElementById("grainNoise").value,

retouching: document.getElementById("retouching").value,

vignetting: document.getElementById("vignetting").value

},

selectedAdditionalInstructionsProduct: Array.from(additionalInstructionsOptionsProduct.querySelectorAll('button.selected')).map(btn => btn.dataset.value),

// Portrait fields

portrait: {

subjectDescription: document.getElementById("portraitSubjectDescription").value,

age: document.getElementById("portraitAge").value,

gender: document.getElementById("portraitGender").value,

ethnicity: document.getElementById("portraitEthnicity").value,

expressionPose: document.getElementById("portraitExpressionPose").value,

attire: document.getElementById("portraitAttire").value,

accessories: document.getElementById("accessories").value,

hairStyle: document.getElementById("hairStyle").value,

makeupStyle: document.getElementById("makeupStyle").value

},

portraitEnvironment: document.getElementById("portraitEnvironment").value,

portraitLighting: {

style: document.getElementById("portraitLighting").value,

source: document.getElementById("lightSourcePortrait").value,

direction: document.getElementById("lightDirectionPortrait").value,

intent: document.getElementById("lightIntentPortrait").value,

count: document.getElementById("lightCountPortrait").value,

modifiers: document.getElementById("lightModifiersPortrait").value,

catchlights: document.getElementById("catchlightsPortrait").value,

reflectionsGlares: "" // Ensure this is saved for product

},

portraitCamera: {

shotType: document.getElementById("portraitShotType").value,

aperture: document.getElementById("aperturePortrait").value,

shutterSpeed: document.getElementById("shutterSpeedPortrait").value,

ISO: document.getElementById("isoPortrait").value,

whiteBalance: document.getElementById("whiteBalancePortrait").value,

focalLength: document.getElementById("focalLengthPortrait").value,

lensType: document.getElementById("lensTypePortrait").value,

focusPoint: document.getElementById("focusMethodPortrait").value,

perspective: "" // Ensure this is saved for product

},

portraitAspectRatio: document.getElementById("portraitAspectRatio").value,

skinTexture: document.getElementById("skinTexturePortrait").value,

overallMoodPortrait: document.getElementById("overallMoodPortrait").value,

portraitComposition: {

rule: document.getElementById("compRulePortrait").value,

framing: document.getElementById("framingPortrait").value,

negativeSpace: document.getElementById("negativeSpacePortrait").value,

dofDesc: document.getElementById("dofDescPortrait").value

},

portraitPostProcessing: {

colorGrading: document.getElementById("colorGradingPortrait").value,

contrast: document.getElementById("contrastPortrait").value,

sharpness: document.getElementById("sharpnessPortrait").value,

grainNoise: document.getElementById("grainNoisePortrait").value,

retouching: document.getElementById("retouchingPortrait").value,

vignetting: document.getElementById("vignettingPortrait").value

},

selectedAdditionalInstructionsPortrait: Array.from(additionalInstructionsOptionsPortrait.querySelectorAll('button.selected')).map(btn => btn.dataset.value)

};

const filename = `VisualLock_Preset_${new Date().toISOString().slice(0,10)}.json`;

const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(presetData, null, 2));

const downloadAnchorNode = document.createElement('a');

downloadAnchorNode.setAttribute("href", dataStr);

downloadAnchorNode.setAttribute("download", filename);

document.body.appendChild(downloadAnchorNode);

downloadAnchorNode.click();

downloadAnchorNode.remove();

toast("Preset saved!");

});

// ---------------------------

// NEW: Load Preset Functionality

// ---------------------------

loadPresetBtn.addEventListener("click", () => {

loadPresetInput.click(); // Trigger the hidden file input

});

loadPresetInput.addEventListener("change", (event) => {

const file = event.target.files[0];

if (!file) {

return;

}

const reader = new FileReader();

reader.onload = (e) => {

try {

const loadedPreset = JSON.parse(e.target.result);

applyPreset(loadedPreset);

toast("Preset loaded successfully!");

} catch (error) {

console.error("Error parsing preset file:", error);

toast("Error loading preset: Invalid file format.");

}

};

reader.readAsText(file);

// Clear the file input so the same file can be loaded again if needed

loadPresetInput.value = '';

});

function applyPreset(preset) {

// Set photography type first

photographyTypeSelector.value = preset.photographyType || "product";

switchPhotographyType(); // Update UI based on type

// Product fields

document.getElementById("description").value = preset.description || "";

document.getElementById("productHeight").value = preset.productHeight || "";

document.getElementById("productWidth").value = preset.productWidth || "";

document.getElementById("environment").value = preset.environment || "studio neutral";

document.getElementById("lighting").value = preset.lighting?.style || "soft studio lighting";

document.getElementById("cameraPerspective").value = preset.camera?.perspective || "product-level";

document.getElementById("aspectRatio").value = preset.aspectRatio || "1:1";

document.getElementById("background").value = preset.background || "";

document.getElementById("surfaceMaterial").value = preset.surfaceMaterial || "";

document.getElementById("surfaceColor").value = preset.surfaceColor || "";

document.getElementById("overallMood").value = preset.overallMood || "";

additionalInstructionsTextareaProduct.value = preset.additional || "";

// Lighting accordion fields (Product)

document.getElementById("lightSource").value = preset.lighting?.source || "";

document.getElementById("lightDirection").value = preset.lighting?.direction || "";

document.getElementById("lightIntent").value = preset.lighting?.intent || "";

document.getElementById("lightCount").value = preset.lighting?.count || "";

document.getElementById("lightModifiers").value = preset.lighting?.modifiers || "";

document.getElementById("reflectionsGlares").value = preset.lighting?.reflectionsGlares || "";

// Camera accordion fields (Product)

document.getElementById("aperture").value = preset.camera?.aperture || "";

document.getElementById("shutterSpeed").value = preset.camera?.shutterSpeed || "";

document.getElementById("iso").value = preset.camera?.ISO || "";

document.getElementById("whiteBalance").value = preset.camera?.whiteBalance || "";

document.getElementById("focalLength").value = preset.camera?.focalLength || "";

document.getElementById("lensType").value = preset.camera?.lensType || "";

document.getElementById("focusMethod").value = preset.camera?.focusMethod || "";

// Props accordion fields (Product)

document.getElementById("prop1").value = preset.props?.[0] || "";

document.getElementById("prop2").value = preset.props?.[1] || "";

document.getElementById("prop3").value = preset.props?.[2] || "";

document.getElementById("prop4").value = preset.props?.[3] || "";

document.getElementById("prop1Dim").value = preset.propDims?.[0] || "";

document.getElementById("prop2Dim").value = preset.propDims?.[1] || "";

document.getElementById("prop3Dim").value = preset.propDims?.[2] || "";

document.getElementById("prop4Dim").value = preset.propDims?.[3] || "";

// Composition accordion fields (Product)

document.getElementById("compRule").value = preset.composition?.rule || "";

document.getElementById("framing").value = preset.composition?.framing || "";

document.getElementById("negativeSpace").value = preset.composition?.negativeSpace || "";

document.getElementById("dofDesc").value = preset.composition?.dofDesc || "";

// Post-Processing accordion fields (Product)

document.getElementById("colorGrading").value = preset.postProcessing?.colorGrading || "";

document.getElementById("contrast").value = preset.postProcessing?.contrast || "";

document.getElementById("sharpness").value = preset.postProcessing?.sharpness || "";

document.getElementById("grainNoise").value = preset.postProcessing?.grainNoise || "";

document.getElementById("retouching").value = preset.postProcessing?.retouching || "";

document.getElementById("vignetting").value = preset.postProcessing?.vignetting || "";

// Update the state of additional instructions buttons (Product)

additionalInstructionsOptionsProduct.querySelectorAll('button').forEach(btn => {

const isSelected = preset.selectedAdditionalInstructionsProduct && preset.selectedAdditionalInstructionsProduct.includes(btn.dataset.value);

if (isSelected) {

btn.classList.add('selected');

} else {

btn.classList.remove('selected');

}

});

// Portrait fields

document.getElementById("portraitSubjectDescription").value = preset.portrait?.subjectDescription || "";

document.getElementById("portraitAge").value = preset.portrait?.age || "";

document.getElementById("portraitGender").value = preset.portrait?.gender || "";

document.getElementById("portraitEthnicity").value = preset.portrait?.ethnicity || "";

document.getElementById("portraitExpressionPose").value = preset.portrait?.expressionPose || "";

document.getElementById("portraitAttire").value = preset.portrait?.attire || "";

document.getElementById("accessories").value = preset.portrait?.accessories || "";

document.getElementById("hairStyle").value = preset.portrait?.hairStyle || "";

document.getElementById("makeupStyle").value = preset.portrait?.makeupStyle || "";

document.getElementById("portraitEnvironment").value = preset.portraitEnvironment || "studio seamless background";

document.getElementById("portraitLighting").value = preset.portraitLighting?.style || "natural soft window light";

document.getElementById("portraitShotType").value = preset.portraitCamera?.shotType || "headshot";

document.getElementById("portraitAspectRatio").value = preset.portraitAspectRatio || "1:1";

document.getElementById("skinTexturePortrait").value = preset.skinTexture || "";

document.getElementById("overallMoodPortrait").value = preset.overallMoodPortrait || "";

additionalInstructionsTextareaPortrait.value = preset.additionalPortrait || ""; // Assuming a separate additional field for portrait

// Lighting Refinement Portrait

document.getElementById("lightSourcePortrait").value = preset.portraitLighting?.source || "";

document.getElementById("lightDirectionPortrait").value = preset.portraitLighting?.direction || "";

document.getElementById("lightIntentPortrait").value = preset.portraitLighting?.intent || "";

document.getElementById("lightCountPortrait").value = preset.portraitLighting?.count || "";

document.getElementById("lightModifiersPortrait").value = preset.portraitLighting?.modifiers || "";

document.getElementById("catchlightsPortrait").value = preset.portraitLighting?.catchlights || "";

// Camera Advanced Portrait

document.getElementById("aperturePortrait").value = preset.portraitCamera?.aperture || "";

document.getElementById("shutterSpeedPortrait").value = preset.portraitCamera?.shutterSpeed || "";

document.getElementById("isoPortrait").value = preset.portraitCamera?.ISO || "";

document.getElementById("whiteBalancePortrait").value = preset.portraitCamera?.whiteBalance || "";

document.getElementById("focalLengthPortrait").value = preset.portraitCamera?.focalLength || "";

document.getElementById("lensTypePortrait").value = preset.portraitCamera?.lensType || "";

document.getElementById("focusMethodPortrait").value = preset.portraitCamera?.focusPoint || "";

// Composition Portrait

document.getElementById("compRulePortrait").value = preset.portraitComposition?.rule || "";

document.getElementById("framingPortrait").value = preset.portraitComposition?.framing || "";

document.getElementById("negativeSpacePortrait").value = preset.portraitComposition?.negativeSpace || "";

document.getElementById("dofDescPortrait").value = preset.portraitComposition?.dofDesc || "";

// Post-Processing Portrait

document.getElementById("colorGradingPortrait").value = preset.portraitPostProcessing?.colorGrading || "";

document.getElementById("contrastPortrait").value = preset.portraitPostProcessing?.contrast || "";

document.getElementById("sharpnessPortrait").value = preset.portraitPostProcessing?.sharpness || "";

document.getElementById("grainNoisePortrait").value = preset.portraitPostProcessing?.grainNoise || "";

document.getElementById("retouchingPortrait").value = preset.portraitPostProcessing?.retouching || "";

document.getElementById("vignettingPortrait").value = preset.portraitPostProcessing?.vignetting || "";

// Update the state of additional instructions buttons (Portrait)

additionalInstructionsOptionsPortrait.querySelectorAll('button').forEach(btn => {

const isSelected = preset.selectedAdditionalInstructionsPortrait && preset.selectedAdditionalInstructionsPortrait.includes(btn.dataset.value);

if (isSelected) {

btn.classList.add('selected');

} else {

btn.classList.remove('selected');

}

});

// Clear and re-render reference images if any were loaded (from local storage for example, not direct file upload)

// For simplicity, we'll just clear current and assume user will re-upload if needed.

uploadedImages = [];

renderPreviews();

// After applying the preset, update the generated prompts

updateGeneratedPrompts();

}

// ---------------------------

// NEW: Tabbed Interface Logic

// ---------------------------

document.querySelectorAll('.tab-button').forEach(button => {

button.addEventListener('click', () => {

const selectedType = photographyTypeSelector.value;

let currentTabContentGroup = (selectedType === "product") ? productSettingsGroup : portraitSettingsGroup;

// Deactivate all tab buttons and hide all tab contents within the active group

document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));

currentTabContentGroup.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));

// Activate clicked button and show corresponding content

button.classList.add('active');

// Determine which specific tab content div to show based on selectedType and tab data-tab

const targetTabId = button.dataset.tab + (selectedType === "portrait" ? "Portrait" : "");

document.getElementById(targetTabId).classList.add('active');

// Force a prompt update in case switching tabs affects input visibility for reading values

updateGeneratedPrompts();

});

});

// ---------------------------

// NEW: Accordion Logic

// ---------------------------

function toggleAccordion(header) {

const content = document.getElementById(header.dataset.accordion + ((photographyTypeSelector.value === "portrait") ? "PortraitAccordion" : "Accordion"));

const isExpanded = header.classList.toggle('expanded');

content.classList.toggle('expanded');

if (isExpanded) {

content.style.maxHeight = content.scrollHeight + "px";

} else {

content.style.maxHeight = "0";

}

updateGeneratedPrompts(); // Update prompt after opening/closing

}

document.querySelectorAll('.accordion-header').forEach(header => {

header.addEventListener('click', () => toggleAccordion(header));

});

// Function to set initial heights for expanded accordions

function initializeAccordionHeights() {

const selectedType = photographyTypeSelector.value;

let currentTabContentGroup = (selectedType === "product") ? productSettingsGroup : portraitSettingsGroup;

currentTabContentGroup.querySelectorAll('.accordion-content.expanded').forEach(content => {

content.style.maxHeight = content.scrollHeight + "px";

});

}

// Recalculate accordion heights on window resize

window.addEventListener('resize', () => {

initializeAccordionHeights(); // Call the unified function

});

// Initial setup on load

document.addEventListener("DOMContentLoaded", () => {

renderPreviews();

switchPhotographyType(); // Ensure correct initial display

initializeAccordionHeights(); // Set initial heights for accordions

});

</script>

</body>

</html>

Contact
APXpmg

Peter grillone. Rancho vista Lane
Eureka Springs AR
USA
Tel. four seven nine . four zero five . 7770

If you are a client in need of assistance, please contact our customer support. They can help you with product and service related questions.

© 2026 All Rights Reserved, LUSANTIS

PeterG

Quiet strength applied to visual craft
Directed by Peter.G