anycoder-78d61c5d / index.html
eubottura's picture
Upload folder using huggingface_hub
80e2aaa verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PhotoShot Pro | Production Coordinator</title>
<!-- Phosphor Icons for modern UI iconography -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<style>
:root {
/* Color Palette - Modern & Professional */
--primary: #2563eb;
--primary-dark: #1e40af;
--secondary: #64748b;
--bg-body: #f1f5f9;
--bg-surface: #ffffff;
--bg-panel: #ffffff;
--text-main: #0f172a;
--text-muted: #64748b;
--border: #e2e8f0;
--accent-success: #10b981;
--accent-warning: #f59e0b;
--accent-danger: #ef4444;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--radius: 12px;
--font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
background-color: var(--bg-body);
color: var(--text-main);
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
header {
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
}
.brand {
display: flex;
align-items: center;
gap: 0.75rem;
font-weight: 700;
font-size: 1.25rem;
color: var(--primary);
}
.brand i {
font-size: 1.5rem;
}
.anycoder-link {
font-size: 0.875rem;
color: var(--text-muted);
text-decoration: none;
background: #f8fafc;
padding: 0.5rem 1rem;
border-radius: var(--radius);
border: 1px solid var(--border);
transition: all 0.2s;
}
.anycoder-link:hover {
background: var(--primary);
color: white;
border-color: var(--primary);
}
/* Main Layout */
main {
flex: 1;
display: grid;
grid-template-columns: 350px 1fr;
gap: 2rem;
padding: 2rem;
max-width: 1600px;
margin: 0 auto;
width: 100%;
}
@media (max-width: 900px) {
main {
grid-template-columns: 1fr;
}
}
/* Panels */
.panel {
background: var(--bg-panel);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
height: fit-content;
}
.panel-header {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
font-size: 1.1rem;
color: var(--text-main);
padding-bottom: 1rem;
border-bottom: 1px solid var(--border);
}
/* Form Elements */
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
label {
font-size: 0.875rem;
font-weight: 500;
color: var(--text-muted);
}
textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius);
font-family: inherit;
resize: vertical;
min-height: 120px;
font-size: 0.95rem;
transition: border-color 0.2s;
}
textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.btn-primary {
background: var(--primary);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: var(--radius);
font-weight: 600;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
transition: background 0.2s;
}
.btn-primary:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: transparent;
color: var(--text-muted);
border: 1px solid var(--border);
padding: 0.75rem 1.5rem;
border-radius: var(--radius);
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-secondary:hover {
background: #f8fafc;
color: var(--text-main);
}
/* Results Area */
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.shot-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
/* Shot Card */
.shot-card {
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.25rem;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
overflow: hidden;
}
.shot-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.shot-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: var(--secondary);
}
.shot-card.featured::before {
background: var(--primary);
}
.shot-card.macro::before {
background: var(--accent-warning);
}
.shot-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.shot-type {
font-weight: 700;
font-size: 1rem;
color: var(--text-main);
}
.shot-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 99px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.badge-global {
background: #eff6ff;
color: var(--primary);
}
.badge-single {
background: #f0fdf4;
color: var(--accent-success);
}
.shot-details {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.detail-row {
display: flex;
align-items: flex-start;
gap: 0.75rem;
font-size: 0.875rem;
}
.detail-row i {
color: var(--text-muted);
margin-top: 2px;
flex-shrink: 0;
}
.color-tag {
display: inline-block;
background: var(--bg-body);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
margin-right: 0.25rem;
margin-bottom: 0.25rem;
border: 1px solid var(--border);
}
.overlay-text {
background: var(--text-main);
color: white;
padding: 0.5rem;
border-radius: 6px;
font-family: monospace;
font-size: 0.8rem;
margin-top: 0.5rem;
}
.overlay-text.empty {
background: transparent;
color: var(--text-muted);
font-style: italic;
border: 1px dashed var(--border);
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: var(--text-muted);
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Summary Bar */
.summary-bar {
background: var(--bg-surface);
padding: 1rem 1.5rem;
border-radius: var(--radius);
margin-bottom: 1.5rem;
border: 1px solid var(--border);
display: flex;
gap: 2rem;
flex-wrap: wrap;
}
.summary-item {
display: flex;
flex-direction: column;
}
.summary-label {
font-size: 0.75rem;
text-transform: uppercase;
color: var(--text-muted);
font-weight: 600;
}
.summary-value {
font-size: 1.1rem;
font-weight: 700;
color: var(--text-main);
}
/* Toast Notification */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: var(--text-main);
color: white;
padding: 1rem 1.5rem;
border-radius: var(--radius);
box-shadow: var(--shadow-md);
transform: translateY(150%);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 1000;
display: flex;
align-items: center;
gap: 0.75rem;
}
.toast.show {
transform: translateY(0);
}
</style>
</head>
<body>
<header>
<div class="brand">
<i class="ph ph-camera"></i>
PhotoShot Pro
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder
</a>
</header>
<main>
<!-- Left Panel: Input Controls -->
<section class="panel">
<div class="panel-header">
<i class="ph ph-sliders-horizontal"></i>
Production Input
</div>
<div class="form-group">
<label for="productInput">Product Name & Details</label>
<textarea id="productInput" placeholder="e.g. Men's High-Waisted Slim Fit Chino / Navy, Beige, Black - Pay 2 Take 3 (Includes Free Belt)"></textarea>
<small style="color: var(--text-muted); font-size: 0.75rem;">Include gender, features, colors (separated by / or ,), and promotions.</small>
</div>
<div style="display: flex; gap: 1rem;">
<button id="generateBtn" class="btn-primary" style="flex: 1;">
<i class="ph ph-magic-wand"></i> Generate List
</button>
<button id="clearBtn" class="btn-secondary">
<i class="ph ph-trash"></i>
</button>
</div>
<div style="margin-top: auto; border-top: 1px solid var(--border); padding-top: 1rem;">
<label>Logic Preview</label>
<div style="font-size: 0.8rem; color: var(--text-muted); display: flex; flex-direction: column; gap: 0.5rem;">
<div style="display: flex; justify-content: space-between;">
<span>Gender Detection:</span>
<strong id="previewGender">-</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Colors Found:</span>
<strong id="previewColors">-</strong>
</div>
<div style="display: flex; justify-content: space-between;">
<span>Promo Offer:</span>
<strong id="previewPromo">-</strong>
</div>
</div>
</div>
</section>
<!-- Right Panel: Output Results -->
<section class="panel" style="background: transparent; border: none; box-shadow: none; padding: 0;">
<div id="resultsArea">
<!-- Empty State -->
<div class="empty-state">
<i class="ph ph-clipboard-text"></i>
<h3>Ready to Coordinate</h3>
<p>Enter a product name on the left to generate a photography shot list based on your production logic.</p>
</div>
</div>
</section>
</main>
<div id="toast" class="toast">
<i class="ph ph-check-circle" style="font-size: 1.25rem;"></i>
<span>Shot list generated successfully!</span>
</div>
<script>
// --- DOM Elements ---
const productInput = document.getElementById('productInput');
const generateBtn = document.getElementById('generateBtn');
const clearBtn = document.getElementById('clearBtn');
const resultsArea = document.getElementById('resultsArea');
const toast = document.getElementById('toast');
const previewGender = document.getElementById('previewGender');
const previewColors = document.getElementById('previewColors');
const previewPromo = document.getElementById('previewPromo');
// --- Logic Configuration ---
const SHOT_DEFINITIONS = [
{ id: 'mirror', name: 'Review Mirror', type: 'global', gender: true },
{ id: 'bed', name: 'Review Bed (Pile)', type: 'global', gender: false },
{ id: 'main', name: 'Main Product Photo', type: 'global', gender: false, overlay: true },
{ id: 'iso', name: 'Isolated Product (PNG)', type: 'global', gender: false },
{ id: 'macro', name: 'Detail Shot 2 (Macro)', type: 'single', gender: false, macroException: true },
{ id: 'street', name: 'Model Shot 1 (Street)', type: 'single', gender: true },
{ id: 'urban', name: 'Model Shot 2 (Urban)', type: 'single', gender: true },
{ id: 'hand', name: 'Detail Shot 1 (Hand)', type: 'single', gender: false }
];
// --- Parsing Logic ---
function parseProductDetails(input) {
const text = input.trim();
if (!text) return null;
// 1. Gender Detection
let gender = 'Unspecified';
if (text.toLowerCase().includes("men's")) gender = 'Male';
else if (text.toLowerCase().includes("women's")) gender = 'Female';
// 2. Color Parsing (Split by "/" or ",")
// We look for the color section usually after the product name, but regex is safer for specific delimiters
// Removing everything inside parens first to avoid confusing "Pay X Take Y" logic with colors
const cleanTextForColors = text.replace(/\([^)]*\)/g, '');
// Split by / or comma, trim whitespace, filter empty
let colors = cleanTextForColors.split(/\/|,/).map(c => c.trim()).filter(c => c.length > 0);
// If no delimiters found but we have text, we might assume the whole thing is one color or no color specified.
// For this tool, we assume explicit delimiters are used for multi-color.
// However, if the split returns 1 item that is the whole product name, we might be parsing wrong.
// Let's try to isolate colors: usually the last part of the string before brackets.
// For simplicity based on prompt: "Extract available colors by splitting..."
// Filter out non-color words (heuristic)
const stopWords = ['pants', 'shirt', 'jacket', 'dress', 'shoes', 'fit', 'style', 'collection', 'pay', 'take', 'men\'s', 'women\'s'];
colors = colors.filter(c => !stopWords.includes(c.toLowerCase()));
if (colors.length === 0) colors = ['Default'];
// 3. Promo Detection ("Pay X Take Y")
const promoRegex = /(pay \d+ take \d+)/i;
const promoMatch = text.match(promoRegex);
const promoText = promoMatch ? promoMatch[0] : null;
// 4. Feature Adaptation (Structural Details)
// Looking for specific keywords mentioned in prompt + common fashion terms
const featureKeywords = [
'high-waisted', 'vertical front seams', 'pleated', 'slim fit',
'relaxed fit', 'skinny', 'straight leg', 'tapered', 'cargo',
'pockets', 'zipper', 'buttons', 'elastic waist'
];
// Normalize text for checking
const lowerText = text.toLowerCase();
const foundFeatures = featureKeywords.filter(feat => lowerText.includes(feat.toLowerCase()));
const featureString = foundFeatures.length > 0 ? foundFeatures.join(', ') : null;
// 5. Bonus/Gift Check (for Macro Exception)
const hasBonusOrGift = /bonus|gift|free|includes/i.test(text);
return {
raw: text,
gender,
colors,
promoText,
featureString,
hasBonusOrGift
};
}
// --- Rendering Logic ---
function renderShotList(data) {
let colorIndex = 0;
const cardsHTML = SHOT_DEFINITIONS.map((shot, index) => {
let assignedColors = [];
let overlayText = '';
let description = data.raw;
let badgeClass = shot.type === 'global' ? 'badge-global' : 'badge-single';
let badgeText = shot.type === 'global' ? 'All Colors' : 'Variant Focus';
let cardClass = '';
// Color Assignment
if (shot.type === 'global') {
assignedColors = [...data.colors];
cardClass = 'featured';
} else {
// Individual: Rotate colors
const color = data.colors[colorIndex % data.colors.length];
assignedColors = [color];
colorIndex++;
}
// Overlay Logic
if (shot.overlay) {
overlayText = data.promoText || '';
}
// Macro Exception
if (shot.macroException) {
cardClass = 'macro';
if (data.hasBonusOrGift && data.promoText) {
overlayText = ''; // Strip promo
// Logic: "list only the product name and color"
// We construct a clean description
const cleanName = data.raw.replace(data.promoText, '').replace(/\s\s+/g, ' ').trim();
description = cleanName;
}
}
// Set Exclusion for Individual (Prompt: "exclude multi-piece promotional logic... focus solely on color variant")
if (shot.type === 'single' && data.promoText) {
// Remove promo from description for individual shots
description = description.replace(data.promoText, '').replace(/\s\s+/g, ' ').trim();
}
// Append features if present
if (data.featureString) {
description += ` (${data.featureString})`;
}
// Render HTML
return `
<div class="shot-card ${cardClass}">
<div class="shot-header">
<span class="shot-type">${shot.name}</span>
<span class="shot-badge ${badgeClass}">${badgeText}</span>
</div>
<div class="shot-details">
${shot.gender ? `
<div class="detail-row">
<i class="ph ph-gender-intersex"></i>
<span><strong>Model:</strong> ${data.gender}</span>
</div>` : ''}
<div class="detail-row">
<i class="ph ph-palette"></i>
<div>
<strong>Color${assignedColors.length > 1 ? 's' : ''}:</strong><br>
<div style="margin-top:4px;">
${assignedColors.map(c => `<span class="color-tag">${c}</span>`).join('')}
</div>
</div>
</div>
<div class="detail-row">
<i class="ph ph-info"></i>
<span>${description}</span>
</div>
${shot.overlay || shot.macroException ? `
<div class="detail-row">
<i class="ph ph-text-aa"></i>
<div style="width: 100%;">
<strong>Text Overlay:</strong>
<div class="overlay-text ${!overlayText ? 'empty' : ''}">
${overlayText ? overlayText : 'None'}
</div>
</div>
</div>` : ''}
</div>
</div>
`;
}).join('');
// Summary Bar
const summaryHTML = `
<div class="summary-bar">
<div class="summary-item">
<span class="summary-label">Total Shots</span>
<span class="summary-value">${SHOT_DEFINITIONS.length}</span>
</div>
<div class="summary-item">
<span class="summary-label">Target Gender</span>
<span class="summary-value">${data.gender}</span>
</div>
<div class="summary-item">
<span class="summary-label">Colors Detected</span>
<span class="summary-value">${data.colors.length}</span>
</div>
<div class="summary-item">
<span class="summary-label">Promotion</span>
<span class="summary-value" style="color: ${data.promoText ? 'var(--accent-success)' : 'var(--text-muted)'}">
${data.promoText ? 'Active' : 'None'}
</span>
</div>
</div>
<div class="shot-grid">
${cardsHTML}
</div>
`;
resultsArea.innerHTML = summaryHTML;
}
// --- Event Listeners ---
function updatePreview() {
const data = parseProductDetails(productInput.value);
if (data) {
previewGender.textContent = data.gender;
previewColors.textContent = data.colors.join(', ');
previewPromo.textContent = data.promoText || 'None';
} else {
previewGender.textContent = '-';
previewColors.textContent = '-';
previewPromo.textContent = '-';
}
}
function showToast() {
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
generateBtn.addEventListener('click', () => {
const data = parseProductDetails(productInput.value);
if (data) {
renderShotList(data);
showToast();
} else {
resultsArea.innerHTML = `
<div class="empty-state">
<i class="ph ph-warning-circle" style="color: var(--accent-danger)"></i>
<h3>Invalid Input</h3>
<p>Please enter a product name to generate the shot list.</p>
</div>
`;
}
});
clearBtn.addEventListener('click', () => {
productInput.value = '';
updatePreview();
resultsArea.innerHTML = `
<div class="empty-state">
<i class="ph ph-clipboard-text"></i>
<h3>Ready to Coordinate</h3>
<p>Enter a product name on the left to generate a photography shot list based on your production logic.</p>
</div>
`;
});
productInput.addEventListener('input', updatePreview);
// --- Initialization ---
updatePreview();
</script>
</body>
</html>