anycoder-e7dc136e / index.html
hunterboy420's picture
Upload folder using huggingface_hub
b6f529d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PhotoCritique - Professional Feedback Simulator</title>
<!-- Importing a font for modern typography -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
<!-- Importing FontAwesome for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #6366f1;
--primary-hover: #4f46e5;
--bg-dark: #0f172a;
--bg-card: #1e293b;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--accent: #22d3ee;
--danger: #ef4444;
--success: #10b981;
--radius: 16px;
--shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-dark);
color: var(--text-main);
min-height: 100vh;
display: flex;
flex-direction: column;
line-height: 1.6;
}
/* Header */
header {
background: rgba(30, 41, 59, 0.8);
backdrop-filter: blur(10px);
padding: 1.5rem 2rem;
position: sticky;
top: 0;
z-index: 100;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: 800;
background: linear-gradient(to right, var(--primary), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 0.5rem;
}
.attribution a {
color: var(--text-muted);
text-decoration: none;
font-size: 0.9rem;
transition: color 0.3s ease;
}
.attribution a:hover {
color: var(--primary);
}
/* Main Container */
main {
flex: 1;
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 2rem;
}
/* Upload Section */
.upload-section {
background: var(--bg-card);
border-radius: var(--radius);
padding: 3rem;
text-align: center;
border: 2px dashed rgba(255,255,255,0.1);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.upload-section.drag-over {
border-color: var(--primary);
background: rgba(99, 102, 241, 0.1);
}
.upload-icon {
font-size: 4rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
.upload-text h3 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.upload-text p {
color: var(--text-muted);
margin-bottom: 1.5rem;
}
.btn {
background: var(--primary);
color: white;
border: none;
padding: 0.8rem 2rem;
border-radius: 50px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, background 0.2s;
font-size: 1rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn:hover {
background: var(--primary-hover);
transform: translateY(-2px);
}
.btn:disabled {
background: var(--text-muted);
cursor: not-allowed;
transform: none;
}
#fileInput {
display: none;
}
/* Preview Area */
.preview-container {
display: none; /* Hidden by default */
background: var(--bg-card);
padding: 2rem;
border-radius: var(--radius);
box-shadow: var(--shadow);
flex-direction: column;
align-items: center;
gap: 2rem;
animation: fadeIn 0.5s ease;
}
.image-wrapper {
max-width: 100%;
max-height: 500px;
border-radius: 8px;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.1);
}
.image-wrapper img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.controls {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.btn-secondary {
background: transparent;
border: 1px solid var(--text-muted);
color: var(--text-muted);
}
.btn-secondary:hover {
border-color: var(--text-main);
color: var(--text-main);
background: rgba(255,255,255,0.05);
}
/* Results Section */
.results-section {
display: none;
margin-top: 2rem;
}
.section-title {
font-size: 1.5rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.reviews-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
/* Review Card */
.review-card {
background: var(--bg-card);
padding: 1.5rem;
border-radius: var(--radius);
border: 1px solid rgba(255,255,255,0.05);
display: flex;
flex-direction: column;
gap: 1rem;
animation: slideUp 0.5s ease forwards;
opacity: 0;
transform: translateY(20px);
}
.reviewer-header {
display: flex;
align-items: center;
gap: 1rem;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--primary);
}
.reviewer-info h4 {
font-size: 1rem;
margin-bottom: 0.2rem;
}
.reviewer-role {
font-size: 0.8rem;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
}
.rating-stars {
color: #fbbf24;
font-size: 0.9rem;
}
.review-body {
font-size: 0.95rem;
color: #cbd5e1;
font-style: italic;
background: rgba(0,0,0,0.2);
padding: 1rem;
border-radius: 8px;
border-left: 3px solid var(--primary);
}
.review-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
font-size: 0.75rem;
padding: 0.25rem 0.75rem;
border-radius: 20px;
background: rgba(255,255,255,0.1);
color: var(--text-muted);
}
/* Loading State */
.loading-overlay {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
gap: 1rem;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255,255,255,0.1);
border-left-color: var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
/* Responsive Adjustments */
@media (max-width: 768px) {
.upload-section { padding: 1.5rem; }
.logo span { display: none; }
.logo i { display: block; }
}
</style>
</head>
<body>
<header>
<div class="logo">
<i class="fa-solid fa-camera-retro"></i>
<span>PhotoCritique</span>
</div>
<div class="attribution">
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</div>
</header>
<main>
<!-- Upload Section -->
<section class="upload-section" id="dropZone">
<div class="upload-icon">
<i class="fa-solid fa-cloud-arrow-up"></i>
</div>
<div class="upload-text">
<h3>Upload your artwork</h3>
<p>Drag & drop a photo here, or click to browse</p>
</div>
<input type="file" id="fileInput" accept="image/*">
<button class="btn" onclick="document.getElementById('fileInput').click()">
Select Image
</button>
</section>
<!-- Preview & Action Section -->
<section class="preview-container" id="previewContainer">
<div class="image-wrapper">
<img id="imagePreview" src="" alt="Preview">
</div>
<div class="controls">
<button class="btn btn-secondary" id="resetBtn">
<i class="fa-solid fa-trash"></i> Clear
</button>
<button class="btn" id="analyzeBtn">
<i class="fa-solid fa-wand-magic-sparkles"></i> Get Critique
</button>
</div>
<!-- Loading Indicator -->
<div class="loading-overlay" id="loadingIndicator">
<div class="spinner"></div>
<p>Gathering reviews from top critics...</p>
</div>
</section>
<!-- Results Section -->
<section class="results-section" id="resultsSection">
<div class="section-title">
<i class="fa-solid fa-comments"></i> Critique Results
</div>
<div class="reviews-grid" id="reviewsGrid">
<!-- Reviews will be injected here via JS -->
</div>
</section>
</main>
<script>
// DOM Elements
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const previewContainer = document.getElementById('previewContainer');
const imagePreview = document.getElementById('imagePreview');
const resetBtn = document.getElementById('resetBtn');
const analyzeBtn = document.getElementById('analyzeBtn');
const loadingIndicator = document.getElementById('loadingIndicator');
const resultsSection = document.getElementById('resultsSection');
const reviewsGrid = document.getElementById('reviewsGrid');
// State
let currentFile = null;
// --- Event Listeners ---
// Drag & Drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
if (e.dataTransfer.files.length) {
handleFile(e.dataTransfer.files[0]);
}
});
// File Input Change
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleFile(e.target.files[0]);
}
});
// Reset
resetBtn.addEventListener('click', resetApp);
// Analyze
analyzeBtn.addEventListener('click', generateCritiques);
// --- Functions ---
function handleFile(file) {
if (!file.type.startsWith('image/')) {
alert('Please upload a valid image file.');
return;
}
currentFile = file;
const reader = new FileReader();
reader.onload = (e) => {
imagePreview.src = e.target.result;
dropZone.style.display = 'none';
previewContainer.style.display = 'flex';
resultsSection.style.display = 'none';
};
reader.readAsDataURL(file);
}
function resetApp() {
currentFile = null;
fileInput.value = '';
dropZone.style.display = 'block';
previewContainer.style.display = 'none';
resultsSection.style.display = 'none';
reviewsGrid.innerHTML = '';
}
function generateCritiques() {
// UI Updates
analyzeBtn.disabled = true;
resetBtn.disabled = true;
loadingIndicator.style.display = 'flex';
resultsSection.style.display = 'none';
// Simulate API delay
setTimeout(() => {
const reviews = createMockReviews();
displayReviews(reviews);
// Restore UI
loadingIndicator.style.display = 'none';
analyzeBtn.disabled = false;
resetBtn.disabled = false;
resultsSection.style.display = 'block';
// Scroll to results
resultsSection.scrollIntoView({ behavior: 'smooth' });
}, 2000);
}
// --- Mock Data Generators ---
const personas = [
{
name: "Elena V.",
role: "Art Curator",
avatar: "https://picsum.photos/seed/elena/100/100",
style: "sophisticated"
},
{
name: "Marcus J.",
role: "Photography Prof",
avatar: "https://picsum.photos/seed/marcus/100/100",
style: "technical"
},
{
name: "Sarah L.",
role: "Creative Director",
avatar: "https://picsum.photos/seed/sarah/100/100",
style: "emotive"
}
];
const openers = {
sophisticated: [
"The composition here is quite striking.",
"There is a palpable sense of narrative in this piece.",
"I find the color palette rather intriguing.",
"This work challenges the viewer's perspective effectively."
],
technical: [
"The lighting setup suggests a professional approach.",
"I notice the depth of field is used strategically here.",
"The exposure balance is handled with precision.",
"Technically speaking, the framing adheres to the rule of thirds nicely."
],
emotive: [
"This image evokes a strong sense of nostalgia.",
"I can really feel the emotion captured in this moment.",
"There is a raw honesty present in this shot.",
"The mood conveyed is incredibly atmospheric."
]
};
const critiques = {
sophisticated: [
"However, I believe the negative space could be utilized more effectively to guide the eye. The juxtaposition of elements is clever, but perhaps slightly cluttered in the lower quadrant.",
"While the subject is compelling, the background elements distract slightly from the focal point. A subtle shift in aperture might have elevated this further.",
"The texture work is exquisite. I do wonder if a slightly warmer temperature would enhance the emotional resonance without compromising the realism."
],
technical: [
"Focus seems to be sharp on the primary subject, which is excellent. That said, the highlights are slightly blown out in the upper right corner; recovering those details in post would improve dynamic range.",
"The leading lines draw the eye well, but the horizon line appears slightly tilted. Correcting this rotation would stabilize the composition significantly.",
"ISO performance looks clean, indicating good lighting conditions. I would suggest experimenting with a wider shutter speed to introduce some motion blur for artistic effect."
],
emotive: [
"It connects with me on a personal level, though I feel the expression could be emphasized more through contrast. The story being told is clear, yet leaves room for interpretation.",
"There is a beautiful vulnerability here. To make it pop, I might suggest increasing the clarity slightly to bring out the textures in the shadows.",
"The atmosphere is undeniable. If the colors were slightly desaturated, I think it would add to the somber, reflective tone of the piece."
]
};
const tagsPool = [
"Creative", "Well-Lit", "Moody", "Dynamic", "Balanced",
"Raw", "Modern", "Classic", "Vibrant", "Detailed"
];
function createMockReviews() {
return personas.map((persona, index) => {
// Randomly select text parts based on persona style
const opener = openers[persona.style][Math.floor(Math.random() * openers[persona.style].length)];
const critique = critiques[persona.style][Math.floor(Math.random() * critiques[persona.style].length)];
// Generate random rating (3 to 5 stars)
const rating = Math.floor(Math.random() * 3) + 3;
// Generate 3 random tags
const shuffledTags = tagsPool.sort(() => 0.5 - Math.random());
const selectedTags = shuffledTags.slice(0, 3);
return {
...persona,
rating,
text: `${opener} ${critique}`,
tags: selectedTags,
delay: index * 200 // stagger animation
};
});
}
function displayReviews(reviews) {
reviewsGrid.innerHTML = '';
reviews.forEach(review => {
const card = document.createElement('article');
card.className = 'review-card';
card.style.animationDelay = `${review.delay}ms`;
// Create Stars HTML
let starsHtml = '';
for(let i=0; i<5; i++) {
if(i < review.rating) starsHtml += '<i class="fa-solid fa-star"></i>';
else starsHtml += '<i class="fa-regular fa-star"></i>';
}
// Create Tags HTML
const tagsHtml = review.tags.map(tag => `<span class="tag">#${tag}</span>`).join('');
card.innerHTML = `
<div class="reviewer-header">
<img src="${review.avatar}" alt="${review.name}" class="avatar">
<div class="reviewer-info">
<h4>${review.name}</h4>
<span class="reviewer-role">${review.role}</span>
</div>
</div>
<div class="rating-stars">${starsHtml}</div>
<div class="review-body">
"${review.text}"
</div>
<div class="review-tags">
${tagsHtml}
</div>
`;
reviewsGrid.appendChild(card);
});
}
</script>
</body>
</html>