Only show T-Shirt Compositor and garment controls when product is a T-Shirt
This commit is contained in:
1 parent
6c6867b49c
commit
ff0fe0990e
3 files changed
+124
-25
No files matched your search
+104
-14
@@ -780,13 +780,81 @@ function renderTriageStage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SLIDE-OVER RICH WORKBENCH & NANO BANANA STUDIO ---
|
// --- SLIDE-OVER RICH WORKBENCH & IMAGE MOCKUP STUDIO ---
|
||||||
|
function detectDefaultProductTemplate(idea) {
|
||||||
|
if (!idea) return "app-ui";
|
||||||
|
const textBlob = [
|
||||||
|
idea.title || "",
|
||||||
|
idea.elevator_pitch || "",
|
||||||
|
...(idea.tags || [])
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
if (/\b(left[- ]chest|pocket\s+tee|pocket\s+emblem)\b/i.test(textBlob)) {
|
||||||
|
return "tshirt-left-chest";
|
||||||
|
}
|
||||||
|
if (/\b(t-?shirt|tee\b|shirt|apparel|crewneck|garment|hoodie|merch|wearable)\b/i.test(textBlob)) {
|
||||||
|
return "tshirt-front";
|
||||||
|
}
|
||||||
|
const latestMockupTemplate = idea.mockups?.[0]?.template;
|
||||||
|
if (latestMockupTemplate === "tshirt-front" || latestMockupTemplate === "tshirt-left-chest") {
|
||||||
|
return latestMockupTemplate;
|
||||||
|
}
|
||||||
|
if (/\b(sticker|decal|patch|badge)\b/i.test(textBlob) || latestMockupTemplate === "sticker") {
|
||||||
|
return "sticker";
|
||||||
|
}
|
||||||
|
if (/\b(poster|print|wall\s*art|framed)\b/i.test(textBlob) || latestMockupTemplate === "poster") {
|
||||||
|
return "poster";
|
||||||
|
}
|
||||||
|
if (idea.category === "creative") {
|
||||||
|
return "isolated-graphic";
|
||||||
|
}
|
||||||
|
return "app-ui";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCurrentProductTShirt() {
|
||||||
|
const templateSel = document.getElementById("nb-template-select");
|
||||||
|
const templateVal = templateSel ? templateSel.value : detectDefaultProductTemplate(state.activeIdea);
|
||||||
|
return templateVal === "tshirt-front" || templateVal === "tshirt-left-chest";
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncTShirtGeneratorVisibility() {
|
||||||
|
const isTShirt = isCurrentProductTShirt();
|
||||||
|
const tshirtCard = document.getElementById("tshirt-compositor-card");
|
||||||
|
const garmentColorGroup = document.getElementById("nb-garment-color-group");
|
||||||
|
|
||||||
|
if (tshirtCard) {
|
||||||
|
tshirtCard.classList.toggle("hidden", !isTShirt);
|
||||||
|
}
|
||||||
|
if (garmentColorGroup) {
|
||||||
|
garmentColorGroup.classList.toggle("hidden", !isTShirt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTShirt) {
|
||||||
|
const surfaceSel = document.getElementById("comp-surface-select");
|
||||||
|
if (surfaceSel) surfaceSel.value = "tshirt";
|
||||||
|
const templateVal = document.getElementById("nb-template-select")?.value;
|
||||||
|
const placementSel = document.getElementById("comp-placement-select");
|
||||||
|
if (placementSel && templateVal === "tshirt-left-chest") {
|
||||||
|
placementSel.value = "left-chest";
|
||||||
|
} else if (placementSel && templateVal === "tshirt-front" && placementSel.value === "left-chest") {
|
||||||
|
placementSel.value = "center-chest";
|
||||||
|
}
|
||||||
|
renderCompositorCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function openIdeaDrawer(id, initialTab = "details") {
|
async function openIdeaDrawer(id, initialTab = "details") {
|
||||||
const data = await apiFetch(`/api/ideas/${id}`);
|
const data = await apiFetch(`/api/ideas/${id}`);
|
||||||
state.activeIdeaId = id;
|
state.activeIdeaId = id;
|
||||||
state.activeIdea = data.idea;
|
state.activeIdea = data.idea;
|
||||||
state.nbEditParentMockup = null;
|
state.nbEditParentMockup = null;
|
||||||
|
|
||||||
|
const templateSel = document.getElementById("nb-template-select");
|
||||||
|
if (templateSel) {
|
||||||
|
templateSel.value = detectDefaultProductTemplate(state.activeIdea);
|
||||||
|
}
|
||||||
|
syncTShirtGeneratorVisibility();
|
||||||
|
|
||||||
document.getElementById("drawer-backdrop").classList.remove("hidden");
|
document.getElementById("drawer-backdrop").classList.remove("hidden");
|
||||||
document.getElementById("idea-drawer").classList.remove("hidden");
|
document.getElementById("idea-drawer").classList.remove("hidden");
|
||||||
|
|
||||||
@@ -796,13 +864,15 @@ async function openIdeaDrawer(id, initialTab = "details") {
|
|||||||
// Pre-populate compositor text from idea title
|
// Pre-populate compositor text from idea title
|
||||||
const topInput = document.getElementById("comp-top-text");
|
const topInput = document.getElementById("comp-top-text");
|
||||||
const bottomInput = document.getElementById("comp-bottom-text");
|
const bottomInput = document.getElementById("comp-bottom-text");
|
||||||
if (topInput && !topInput.value) {
|
if (topInput) {
|
||||||
topInput.value = state.activeIdea.title.slice(0, 26).toUpperCase();
|
topInput.value = state.activeIdea.title.slice(0, 26).toUpperCase();
|
||||||
}
|
}
|
||||||
if (bottomInput && !bottomInput.value) {
|
if (bottomInput) {
|
||||||
bottomInput.value = state.activeIdea.tags[0] ? `#${state.activeIdea.tags[0].toUpperCase()}` : "SPARKVAULT STUDIO";
|
bottomInput.value = state.activeIdea.tags[0] ? `#${state.activeIdea.tags[0].toUpperCase()}` : "SPARKVAULT STUDIO";
|
||||||
}
|
}
|
||||||
renderCompositorCanvas();
|
if (isCurrentProductTShirt()) {
|
||||||
|
renderCompositorCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
if (initialTab === "whack" && !state.drawerWhackCard) {
|
if (initialTab === "whack" && !state.drawerWhackCard) {
|
||||||
await drawCardForActiveIdea();
|
await drawCardForActiveIdea();
|
||||||
@@ -854,9 +924,13 @@ async function requestNameSuggestionsForActiveIdea() {
|
|||||||
<button type="button" class="btn btn-primary btn-xs btn-use-suggested-title" data-name="${escapeHtml(s.name)}" data-tagline="${escapeHtml(s.tagline || "")}">
|
<button type="button" class="btn btn-primary btn-xs btn-use-suggested-title" data-name="${escapeHtml(s.name)}" data-tagline="${escapeHtml(s.tagline || "")}">
|
||||||
✓ Use Title
|
✓ Use Title
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-ghost btn-xs btn-use-suggested-tee" data-name="${escapeHtml(s.name)}" data-tagline="${escapeHtml(s.tagline || "")}">
|
${
|
||||||
👕 Send to Tee Canvas
|
isCurrentProductTShirt()
|
||||||
</button>
|
? `<button type="button" class="btn btn-ghost btn-xs btn-use-suggested-tee" data-name="${escapeHtml(s.name)}" data-tagline="${escapeHtml(s.tagline || "")}">
|
||||||
|
👕 Send to Tee Canvas
|
||||||
|
</button>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@@ -1064,9 +1138,13 @@ function renderDrawerAttachments(idea) {
|
|||||||
<button type="button" class="btn btn-primary btn-xs btn-att-to-mockup" data-att-id="${escapeHtml(att.id)}">
|
<button type="button" class="btn btn-primary btn-xs btn-att-to-mockup" data-att-id="${escapeHtml(att.id)}">
|
||||||
🎨 Send to Mockup Studio
|
🎨 Send to Mockup Studio
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-ghost btn-xs btn-att-to-canvas" data-att-url="${escapeHtml(att.imageUrl)}">
|
${
|
||||||
👕 Tee Canvas
|
isCurrentProductTShirt()
|
||||||
</button>
|
? `<button type="button" class="btn btn-ghost btn-xs btn-att-to-canvas" data-att-url="${escapeHtml(att.imageUrl)}">
|
||||||
|
👕 Tee Canvas
|
||||||
|
</button>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
${
|
${
|
||||||
!isCover
|
!isCover
|
||||||
? `<button type="button" class="btn btn-ghost btn-xs btn-att-cover" data-att-id="${escapeHtml(att.id)}">⭐ Cover</button>`
|
? `<button type="button" class="btn btn-ghost btn-xs btn-att-cover" data-att-id="${escapeHtml(att.id)}">⭐ Cover</button>`
|
||||||
@@ -1177,7 +1255,7 @@ function renderDrawerMockupGallery(idea) {
|
|||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
if (!idea.mockups || idea.mockups.length === 0) {
|
if (!idea.mockups || idea.mockups.length === 0) {
|
||||||
container.innerHTML = `<p class="muted-label">No mockups saved for this idea yet. Generate one in the Image Mockup Studio or save a layout from the T-Shirt Compositor!</p>`;
|
container.innerHTML = `<p class="muted-label">No mockups saved for this idea yet. Generate one in the Image Mockup Studio!</p>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1196,9 +1274,13 @@ function renderDrawerMockupGallery(idea) {
|
|||||||
<button type="button" class="btn btn-primary btn-xs btn-mk-refine" data-mk-id="${escapeHtml(m.id)}">
|
<button type="button" class="btn btn-primary btn-xs btn-mk-refine" data-mk-id="${escapeHtml(m.id)}">
|
||||||
✨ Refine Mockup
|
✨ Refine Mockup
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-ghost btn-xs btn-mk-load-canvas" data-mk-url="${escapeHtml(m.imageUrl)}">
|
${
|
||||||
👕 Place on Tee Canvas
|
isCurrentProductTShirt()
|
||||||
</button>
|
? `<button type="button" class="btn btn-ghost btn-xs btn-mk-load-canvas" data-mk-url="${escapeHtml(m.imageUrl)}">
|
||||||
|
👕 Place on Tee Canvas
|
||||||
|
</button>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
${
|
${
|
||||||
!isCover
|
!isCover
|
||||||
? `<button type="button" class="btn btn-ghost btn-xs btn-mk-set-cover" data-mk-id="${escapeHtml(m.id)}">⭐ Set Cover</button>`
|
? `<button type="button" class="btn btn-ghost btn-xs btn-mk-set-cover" data-mk-id="${escapeHtml(m.id)}">⭐ Set Cover</button>`
|
||||||
@@ -2109,6 +2191,14 @@ function bindGlobalEvents() {
|
|||||||
document.getElementById("nb-edit-instruction-input").value = "";
|
document.getElementById("nb-edit-instruction-input").value = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("nb-template-select")?.addEventListener("change", () => {
|
||||||
|
syncTShirtGeneratorVisibility();
|
||||||
|
if (state.activeIdea) {
|
||||||
|
renderDrawerMockupGallery(state.activeIdea);
|
||||||
|
renderDrawerAttachments(state.activeIdea);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("nb-generator-select")?.addEventListener("change", async (e) => {
|
document.getElementById("nb-generator-select")?.addEventListener("change", async (e) => {
|
||||||
const chosenModel = e.target.value;
|
const chosenModel = e.target.value;
|
||||||
state.settings.nanoBananaModel = chosenModel;
|
state.settings.nanoBananaModel = chosenModel;
|
||||||
|
|||||||
+7
-7
@@ -573,9 +573,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Garment / Surface Color Swatches -->
|
<!-- Garment / Surface Color Swatches (Only shown when product is a T-Shirt) -->
|
||||||
<div class="field-group">
|
<div id="nb-garment-color-group" class="field-group hidden">
|
||||||
<label>Garment / Surface Color: <strong id="nb-selected-color-label">Vintage Cream (#F5E8D3)</strong></label>
|
<label>T-Shirt Garment Color: <strong id="nb-selected-color-label">Vintage Cream (#F5E8D3)</strong></label>
|
||||||
<div class="color-swatch-row" id="nb-color-swatches">
|
<div class="color-swatch-row" id="nb-color-swatches">
|
||||||
<button type="button" class="color-swatch active" data-color="#F5E8D3" data-name="Vintage Cream" style="background:#F5E8D3" title="Vintage Cream (#F5E8D3)"></button>
|
<button type="button" class="color-swatch active" data-color="#F5E8D3" data-name="Vintage Cream" style="background:#F5E8D3" title="Vintage Cream (#F5E8D3)"></button>
|
||||||
<button type="button" class="color-swatch" data-color="#1C1B1A" data-name="Vintage Black" style="background:#1C1B1A" title="Vintage Black (#1C1B1A)"></button>
|
<button type="button" class="color-swatch" data-color="#1C1B1A" data-name="Vintage Black" style="background:#1C1B1A" title="Vintage Black (#1C1B1A)"></button>
|
||||||
@@ -589,7 +589,7 @@
|
|||||||
|
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<label for="nb-prompt-input">Visual Description / Artwork Prompt</label>
|
<label for="nb-prompt-input">Visual Description / Artwork Prompt</label>
|
||||||
<textarea id="nb-prompt-input" rows="3" class="textarea-input" placeholder="Describe the graphic, emblem, ink colors, typography, or mockup scene..."></textarea>
|
<textarea id="nb-prompt-input" rows="3" class="textarea-input" placeholder="Describe the visual concept, product UI, illustration, or mockup scene..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Iterative Image-to-Image Edit Banner (Active when a mockup is selected for refinement) -->
|
<!-- Iterative Image-to-Image Edit Banner (Active when a mockup is selected for refinement) -->
|
||||||
@@ -602,7 +602,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
id="nb-edit-instruction-input"
|
id="nb-edit-instruction-input"
|
||||||
class="text-input"
|
class="text-input"
|
||||||
placeholder="Describe what to change (e.g. 'Move graphic to left-chest pocket on a forest green shirt')"
|
placeholder="Describe what to change (e.g. 'Switch to dark mode layout' or 'Use bolder vintage typography')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -614,8 +614,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Interactive T-Shirt & Product Canvas Compositor -->
|
<!-- Interactive T-Shirt & Emblem Compositor (Only shown when product is a T-Shirt) -->
|
||||||
<div class="studio-card">
|
<div id="tshirt-compositor-card" class="studio-card hidden">
|
||||||
<div class="studio-card-header">
|
<div class="studio-card-header">
|
||||||
<h3>👕 Interactive T-Shirt & Emblem Compositor</h3>
|
<h3>👕 Interactive T-Shirt & Emblem Compositor</h3>
|
||||||
<span class="muted-label">Instant apparel layout & print proof</span>
|
<span class="muted-label">Instant apparel layout & print proof</span>
|
||||||
|
|||||||
@@ -321,18 +321,27 @@ export async function generateNameSuggestions({
|
|||||||
userBackground = DEFAULT_USER_BACKGROUND,
|
userBackground = DEFAULT_USER_BACKGROUND,
|
||||||
geminiApiKey = ""
|
geminiApiKey = ""
|
||||||
}) {
|
}) {
|
||||||
|
const ideaTextBlob = [idea.title || "", idea.elevator_pitch || "", ...(idea.tags || [])].join(" ");
|
||||||
|
const isTShirt = /\b(t-?shirt|tee\b|shirt|apparel|crewneck|garment|hoodie|merch|wearable)\b/i.test(ideaTextBlob);
|
||||||
|
|
||||||
|
const angleGuidance = isTShirt
|
||||||
|
? "Generate 6 distinct, memorable name/title suggestions for this apparel/t-shirt idea across different naming angles (e.g., Vintage Club/Crest, Punchy Brand Name, Witty/Clever, Minimalist, Screenprint Headline + Sub-tagline, Outfitter Badge)."
|
||||||
|
: "Generate 6 distinct, memorable name/title suggestions for this idea across different naming angles (e.g., Punchy Brand Name, Maker/Project Codename, Witty/Clever, Minimalist Editorial, Descriptive Product Name, Bold Hook). Do NOT frame it as a t-shirt or apparel slogan.";
|
||||||
|
|
||||||
const prompt = [
|
const prompt = [
|
||||||
"You are a sharp creative director, brand namer, and apparel/product copywriter.",
|
"You are a sharp creative director, product namer, and brand strategist.",
|
||||||
`IDEA TO NAME:\n- Current Title: ${idea.title}\n- Elevator Pitch: ${idea.elevator_pitch || ""}\n- Category: ${idea.category}\n- Tags: ${(idea.tags || []).join(", ")}\n- Notes: ${(idea.notes_markdown || "").slice(0, 600)}`,
|
`IDEA TO NAME:\n- Current Title: ${idea.title}\n- Elevator Pitch: ${idea.elevator_pitch || ""}\n- Category: ${idea.category}\n- Tags: ${(idea.tags || []).join(", ")}\n- Notes: ${(idea.notes_markdown || "").slice(0, 600)}`,
|
||||||
`USER BACKGROUND & TASTES:\n${userBackground || DEFAULT_USER_BACKGROUND}`,
|
`USER BACKGROUND & TASTES:\n${userBackground || DEFAULT_USER_BACKGROUND}`,
|
||||||
"Generate 6 distinct, memorable name/title suggestions for this idea across different naming angles (e.g., Vintage Club/Crest, Punchy Brand Name, Witty/Clever, Minimalist, Screenprint Headline + Sub-tagline, Maker/Project Codename).",
|
angleGuidance,
|
||||||
"Do NOT run any shell commands or tools. Reply ONLY with a single raw JSON object (no markdown fences) matching this exact schema:",
|
"Do NOT run any shell commands or tools. Reply ONLY with a single raw JSON object (no markdown fences) matching this exact schema:",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
name: "Suggested Idea Title or Brand Name",
|
name: "Suggested Idea Title or Brand Name",
|
||||||
tagline: "Short punchy subtitle, est. line, or one-line hook",
|
tagline: "Short punchy subtitle or one-line hook",
|
||||||
style: "Angle badge (e.g. Vintage Club, Punchy Brand, Witty, Minimalist, Tee Slogan, Codename)"
|
style: isTShirt
|
||||||
|
? "Angle badge (e.g. Vintage Club, Punchy Brand, Witty, Minimalist, Tee Slogan, Outfitter)"
|
||||||
|
: "Angle badge (e.g. Punchy Brand, Codename, Witty, Minimalist, Product Name, Bold Hook)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user