Only show T-Shirt Compositor and garment controls when product is a T-Shirt

This commit is contained in:
airent106 committed 2026-10-29 12:14:13 -06:00
1 parent 6c6867b49c
commit ff0fe0990e
3 files changed
+124 -25

No files matched your search

+104 -14
View File
@@ -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") {
const data = await apiFetch(`/api/ideas/${id}`);
state.activeIdeaId = id;
state.activeIdea = data.idea;
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("idea-drawer").classList.remove("hidden");
@@ -796,13 +864,15 @@ async function openIdeaDrawer(id, initialTab = "details") {
// Pre-populate compositor text from idea title
const topInput = document.getElementById("comp-top-text");
const bottomInput = document.getElementById("comp-bottom-text");
if (topInput && !topInput.value) {
if (topInput) {
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";
}
renderCompositorCanvas();
if (isCurrentProductTShirt()) {
renderCompositorCanvas();
}
if (initialTab === "whack" && !state.drawerWhackCard) {
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 || "")}">
✓ Use Title
</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>
${
isCurrentProductTShirt()
? `<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>
`
@@ -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)}">
🎨 Send to Mockup Studio
</button>
<button type="button" class="btn btn-ghost btn-xs btn-att-to-canvas" data-att-url="${escapeHtml(att.imageUrl)}">
👕 Tee Canvas
</button>
${
isCurrentProductTShirt()
? `<button type="button" class="btn btn-ghost btn-xs btn-att-to-canvas" data-att-url="${escapeHtml(att.imageUrl)}">
👕 Tee Canvas
</button>`
: ""
}
${
!isCover
? `<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 (!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;
}
@@ -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)}">
✨ Refine Mockup
</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>
${
isCurrentProductTShirt()
? `<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
? `<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-template-select")?.addEventListener("change", () => {
syncTShirtGeneratorVisibility();
if (state.activeIdea) {
renderDrawerMockupGallery(state.activeIdea);
renderDrawerAttachments(state.activeIdea);
}
});
document.getElementById("nb-generator-select")?.addEventListener("change", async (e) => {
const chosenModel = e.target.value;
state.settings.nanoBananaModel = chosenModel;
+7 -7
View File
@@ -573,9 +573,9 @@
</div>
</div>
<!-- Garment / Surface Color Swatches -->
<div class="field-group">
<label>Garment / Surface Color: <strong id="nb-selected-color-label">Vintage Cream (#F5E8D3)</strong></label>
<!-- Garment / Surface Color Swatches (Only shown when product is a T-Shirt) -->
<div id="nb-garment-color-group" class="field-group hidden">
<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">
<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>
@@ -589,7 +589,7 @@
<div class="field-group">
<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>
<!-- Iterative Image-to-Image Edit Banner (Active when a mockup is selected for refinement) -->
@@ -602,7 +602,7 @@
type="text"
id="nb-edit-instruction-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>
@@ -614,8 +614,8 @@
</div>
</div>
<!-- Interactive T-Shirt & Product Canvas Compositor -->
<div class="studio-card">
<!-- Interactive T-Shirt & Emblem Compositor (Only shown when product is a T-Shirt) -->
<div id="tshirt-compositor-card" class="studio-card hidden">
<div class="studio-card-header">
<h3>👕 Interactive T-Shirt & Emblem Compositor</h3>
<span class="muted-label">Instant apparel layout & print proof</span>
+13 -4
View File
@@ -321,18 +321,27 @@ export async function generateNameSuggestions({
userBackground = DEFAULT_USER_BACKGROUND,
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 = [
"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)}`,
`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:",
JSON.stringify({
suggestions: [
{
name: "Suggested Idea Title or Brand Name",
tagline: "Short punchy subtitle, est. line, or one-line hook",
style: "Angle badge (e.g. Vintage Club, Punchy Brand, Witty, Minimalist, Tee Slogan, Codename)"
tagline: "Short punchy subtitle or one-line hook",
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)"
}
]
})