Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Roast My Fit - Savage Style Reviews</title> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary: #ff0055; | |
| --secondary: #1a1a1a; | |
| --accent: #00ffcc; | |
| --text: #ffffff; | |
| --card-bg: #252525; | |
| --glass: rgba(255, 255, 255, 0.05); | |
| --border: rgba(255, 255, 255, 0.1); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| } | |
| body { | |
| background-color: #0f0f0f; | |
| color: var(--text); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-x: hidden; | |
| background-image: | |
| radial-gradient(circle at 10% 20%, rgba(255, 0, 85, 0.1) 0%, transparent 20%), | |
| radial-gradient(circle at 90% 80%, rgba(0, 255, 204, 0.05) 0%, transparent 20%); | |
| } | |
| /* Header */ | |
| header { | |
| padding: 1.5rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid var(--border); | |
| backdrop-filter: blur(10px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| background: rgba(15, 15, 15, 0.8); | |
| } | |
| .logo { | |
| font-size: 1.5rem; | |
| font-weight: 900; | |
| letter-spacing: -1px; | |
| background: linear-gradient(45deg, var(--primary), #ff5500); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-transform: uppercase; | |
| } | |
| .anycoder-link { | |
| font-size: 0.8rem; | |
| color: var(--accent); | |
| text-decoration: none; | |
| font-weight: 600; | |
| padding: 0.5rem 1rem; | |
| border: 1px solid var(--accent); | |
| border-radius: 20px; | |
| transition: all 0.3s ease; | |
| } | |
| .anycoder-link:hover { | |
| background: var(--accent); | |
| color: var(--secondary); | |
| box-shadow: 0 0 15px rgba(0, 255, 204, 0.4); | |
| } | |
| /* Main Container */ | |
| main { | |
| flex: 1; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| width: 100%; | |
| padding: 2rem 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2rem; | |
| } | |
| /* Hero Section */ | |
| .hero { | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| } | |
| .hero h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 0.5rem; | |
| line-height: 1.1; | |
| } | |
| .hero p { | |
| color: #888; | |
| font-size: 1rem; | |
| } | |
| /* Upload Card */ | |
| .upload-card { | |
| background: var(--card-bg); | |
| border-radius: 24px; | |
| padding: 2rem; | |
| border: 1px solid var(--border); | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.3); | |
| transition: transform 0.3s ease; | |
| } | |
| .drop-zone { | |
| border: 2px dashed var(--border); | |
| border-radius: 16px; | |
| padding: 3rem 1rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .drop-zone:hover, .drop-zone.dragover { | |
| border-color: var(--primary); | |
| background: rgba(255, 0, 85, 0.05); | |
| } | |
| .drop-zone i { | |
| font-size: 3rem; | |
| color: var(--primary); | |
| margin-bottom: 1rem; | |
| } | |
| .drop-zone p { | |
| font-weight: 500; | |
| color: #ccc; | |
| } | |
| .drop-zone input { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0; | |
| cursor: pointer; | |
| } | |
| /* Preview Area */ | |
| .preview-container { | |
| margin-top: 1.5rem; | |
| display: none; | |
| border-radius: 12px; | |
| overflow: hidden; | |
| position: relative; | |
| max-height: 400px; | |
| } | |
| .preview-container img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| display: block; | |
| } | |
| .remove-btn { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| background: rgba(0,0,0,0.7); | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 30px; | |
| height: 30px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| /* Optional Inputs */ | |
| .optional-inputs { | |
| margin-top: 1.5rem; | |
| display: grid; | |
| gap: 1rem; | |
| } | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .input-group label { | |
| font-size: 0.85rem; | |
| color: #aaa; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .input-group input, .input-group select { | |
| background: var(--secondary); | |
| border: 1px solid var(--border); | |
| padding: 0.8rem; | |
| border-radius: 8px; | |
| color: white; | |
| font-size: 1rem; | |
| outline: none; | |
| } | |
| .input-group input:focus { | |
| border-color: var(--primary); | |
| } | |
| /* Action Button */ | |
| .btn-roast { | |
| width: 100%; | |
| margin-top: 1.5rem; | |
| padding: 1rem; | |
| background: var(--primary); | |
| color: white; | |
| border: none; | |
| border-radius: 12px; | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| transition: all 0.2s; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn-roast:disabled { | |
| background: #444; | |
| cursor: not-allowed; | |
| opacity: 0.7; | |
| } | |
| .btn-roast:not(:disabled):hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(255, 0, 85, 0.3); | |
| } | |
| /* Loader */ | |
| .loader { | |
| display: none; | |
| text-align: center; | |
| margin-top: 2rem; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid rgba(255,255,255,0.1); | |
| border-left-color: var(--primary); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 1rem; | |
| } | |
| @keyframes spin { 100% { transform: rotate(360deg); } } | |
| .loading-text { | |
| font-style: italic; | |
| color: var(--accent); | |
| animation: pulse 1.5s infinite; | |
| } | |
| @keyframes pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } } | |
| /* Results Section */ | |
| #results-area { | |
| display: none; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| animation: slideUp 0.5s ease-out; | |
| } | |
| @keyframes slideUp { | |
| from { transform: translateY(20px); opacity: 0; } | |
| to { transform: translateY(0); opacity: 1; } | |
| } | |
| .review-card { | |
| background: linear-gradient(145deg, #1e1e1e, #252525); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| border: 1px solid var(--border); | |
| position: relative; | |
| } | |
| .reviewer-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| .avatar { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 50%; | |
| background: #333; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| border: 2px solid var(--primary); | |
| } | |
| .reviewer-info h3 { | |
| font-size: 1.1rem; | |
| margin-bottom: 2px; | |
| } | |
| .reviewer-info span { | |
| font-size: 0.8rem; | |
| color: #888; | |
| background: rgba(255,255,255,0.1); | |
| padding: 2px 8px; | |
| border-radius: 10px; | |
| } | |
| .rating-badge { | |
| margin-left: auto; | |
| font-size: 1.5rem; | |
| font-weight: 900; | |
| color: var(--primary); | |
| background: rgba(255, 0, 85, 0.1); | |
| padding: 0.5rem 1rem; | |
| border-radius: 12px; | |
| } | |
| .review-text { | |
| font-size: 1rem; | |
| line-height: 1.6; | |
| color: #ddd; | |
| padding-left: 1rem; | |
| border-left: 3px solid var(--accent); | |
| margin-bottom: 1rem; | |
| } | |
| .tags { | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| } | |
| .tag { | |
| font-size: 0.75rem; | |
| padding: 4px 10px; | |
| border-radius: 6px; | |
| background: #333; | |
| color: #aaa; | |
| } | |
| .tag.bad { color: #ff4d4d; border: 1px solid #ff4d4d33; } | |
| .tag.good { color: #00ffcc; border: 1px solid #00ffcc33; } | |
| /* Mega Review Styles */ | |
| .mega-review-alert { | |
| background: linear-gradient(45deg, #ff0055, #7700ff); | |
| padding: 1rem; | |
| border-radius: 12px; | |
| text-align: center; | |
| font-weight: 800; | |
| text-transform: uppercase; | |
| margin-bottom: 1rem; | |
| box-shadow: 0 0 20px rgba(255, 0, 85, 0.5); | |
| animation: shake 0.5s; | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 75% { transform: translateX(5px); } | |
| } | |
| @media (max-width: 600px) { | |
| .hero h1 { font-size: 1.8rem; } | |
| .reviewer-header { flex-direction: row; } | |
| .rating-badge { font-size: 1.2rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="logo"><i class="fa-solid fa-fire-flame-curved"></i> Roast My Fit</div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a> | |
| </header> | |
| <main> | |
| <section class="hero"> | |
| <h1>The Council Has Spoken</h1> | |
| <p>Upload your outfit. Get absolutely destroyed (or praised) by our panel of ruthless fashion critics. No filters. No mercy.</p> | |
| </section> | |
| <section class="upload-card"> | |
| <div class="drop-zone" id="drop-zone"> | |
| <i class="fa-solid fa-cloud-arrow-up"></i> | |
| <p>Drag & Drop your fit here or click to browse</p> | |
| <input type="file" id="file-input" accept="image/*,video/*"> | |
| </div> | |
| <div class="preview-container" id="preview-container"> | |
| <img id="preview-image" src="" alt="Preview"> | |
| <button class="remove-btn" id="remove-btn"><i class="fa-solid fa-times"></i></button> | |
| </div> | |
| <div class="optional-inputs"> | |
| <div class="input-group"> | |
| <label>Brand / Designer (Optional)</label> | |
| <input type="text" id="brand-input" placeholder="e.g. Zara, Thrifted, Gucci..."> | |
| </div> | |
| <div class="input-group"> | |
| <label>Occasion (Optional)</label> | |
| <select id="occasion-input"> | |
| <option value="">Select Occasion...</option> | |
| <option value="Streetwear">Streetwear</option> | |
| <option value="Date Night">Date Night</option> | |
| <option value="Office">Office / Work</option> | |
| <option value="Gym">Gym / Athleisure</option> | |
| <option value="Party">Party</option> | |
| </select> | |
| </div> | |
| </div> | |
| <button class="btn-roast" id="roast-btn" disabled>Judge Me</button> | |
| </section> | |
| <div class="loader" id="loader"> | |
| <div class="spinner"></div> | |
| <p class="loading-text" id="loading-text">Summoning the fashion police...</p> | |
| </div> | |
| <section id="results-area"></section> | |
| </main> | |
| <script> | |
| // --- Data: The Personalities --- | |
| const personas = [ | |
| { name: "Chloe", icon: "💅", vibe: "High Maintenance", style: "Brutally honest about cheap fabrics and bad fits." }, | |
| { name: "Sasha", icon: "💀", vibe: "Goth/Alt", style: "Hates anything basic. Loves black." }, | |
| { name: "Bella", icon: "☕", vibe: "Influencer", style: "Uses too many buzzwords. Obsessed with trends." }, | |
| { name: "Jasmine", icon: "💸", vibe: "Label Snob", style: "If it's not designer, she's not looking." }, | |
| { name: "Riley", icon: "🛹", vibe: "Skater", style: "Chill but roasts you for trying too hard." }, | |
| { name: "Mia", icon: "📚", vibe: "Preppy", style: "Judges your color coordination and ironing." }, | |
| { name: "Zoe", icon: "🎨", vibe: "Artsy", style: "Critiques your silhouette and texture usage." }, | |
| { name: "Emma", icon: "🙄", vibe: "Over It", style: "Bored by your existence. Very sarcastic." } | |
| ]; | |
| const roastTemplates = { | |
| bad: [ | |
| "Honey, no. Just no. The silhouette is giving 'potato sack chic' and not in the ironic Balenciaga way.", | |
| "I'm actually offended. Did you get dressed in the dark? The color coordination is non-existent.", | |
| "This screams 'I bought the mannequin outfit' but ordered it from Wish. The fit is tragic.", | |
| "It's giving 2014 tumblr but without the aesthetic appeal. Burn it.", | |
| "Why is it so... ill-fitting? It's hugging all the wrong places and loose where it should be tight." | |
| ], | |
| mid: [ | |
| "It's... fine. It's safe. It's the outfit equivalent of unseasoned chicken.", | |
| "I see what you were trying to do, but you didn't quite stick the landing. Needs better accessories.", | |
| "Not terrible, but I wouldn't post this on main. Maybe close friends story only.", | |
| "The pieces are okay individually, but together they're fighting for custody of your dignity." | |
| ], | |
| good: [ | |
| "Okay, wait. I actually don't hate this. The proportions are kind of eating.", | |
| "Finally, someone with taste. The texture combination is elite.", | |
| "Slay. No notes. You actually look like you know what you're doing.", | |
| "This is a serve. The fit is immaculate and the vibe is right." | |
| ] | |
| }; | |
| const specificComments = { | |
| brand: [ | |
| "Also, claiming that brand doesn't save the fit.", | |
| "That brand is so last season, try to keep up.", | |
| "At least you spent money on it, even if it looks cheap." | |
| ], | |
| occasion: [ | |
| "For that occasion? Bold choice. And by bold I mean wrong.", | |
| "You're going to stand out, but probably for the wrong reasons.", | |
| "It fits the vibe I guess, but it's so predictable." | |
| ] | |
| }; | |
| // --- DOM Elements --- | |
| const dropZone = document.getElementById('drop-zone'); | |
| const fileInput = document.getElementById('file-input'); | |
| const previewContainer = document.getElementById('preview-container'); | |
| const previewImage = document.getElementById('preview-image'); | |
| const removeBtn = document.getElementById('remove-btn'); | |
| const roastBtn = document.getElementById('roast-btn'); | |
| const loader = document.getElementById('loader'); | |
| const loadingText = document.getElementById('loading-text'); | |
| const resultsArea = document.getElementById('results-area'); | |
| const brandInput = document.getElementById('brand-input'); | |
| const occasionInput = document.getElementById('occasion-input'); | |
| // --- Event Listeners --- | |
| dropZone.addEventListener('click', () => fileInput.click()); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.add('dragover'); | |
| }); | |
| dropZone.addEventListener('dragleave', () => { | |
| dropZone.classList.remove('dragover'); | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('dragover'); | |
| if (e.dataTransfer.files.length) { | |
| handleFile(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (fileInput.files.length) { | |
| handleFile(fileInput.files[0]); | |
| } | |
| }); | |
| removeBtn.addEventListener('click', () => { | |
| fileInput.value = ''; | |
| previewContainer.style.display = 'none'; | |
| dropZone.style.display = 'block'; | |
| roastBtn.disabled = true; | |
| resultsArea.innerHTML = ''; | |
| resultsArea.style.display = 'none'; | |
| }); | |
| roastBtn.addEventListener('click', generateRoast); | |
| // --- Functions --- | |
| function handleFile(file) { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| previewImage.src = e.target.result; | |
| previewContainer.style.display = 'block'; | |
| dropZone.style.display = 'none'; | |
| roastBtn.disabled = false; | |
| // Reset results if new image | |
| resultsArea.innerHTML = ''; | |
| resultsArea.style.display = 'none'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function generateRandomRating() { | |
| return (Math.random() * 10).toFixed(1); | |
| } | |
| function getRoastText(score, brand, occasion) { | |
| let baseText = ""; | |
| let category = score < 4 ? 'bad' : (score < 7.5 ? 'mid' : 'good'); | |
| // Pick random template | |
| const templates = roastTemplates[category]; | |
| baseText = templates[Math.floor(Math.random() * templates.length)]; | |
| // Add context if provided | |
| if (brand && Math.random() > 0.5) { | |
| baseText += " " + specificComments.brand[Math.floor(Math.random() * specificComments.brand.length)]; | |
| } | |
| if (occasion && Math.random() > 0.5) { | |
| baseText += " " + specificComments.occasion[Math.floor(Math.random() * specificComments.occasion.length)]; | |
| } | |
| return baseText; | |
| } | |
| function createReviewCard(persona, score, text) { | |
| const card = document.createElement('div'); | |
| card.className = 'review-card'; | |
| let tagsHtml = ''; | |
| if (score < 5) tagsHtml += `<span class="tag bad">Tragic Fit</span>`; | |
| if (score > 8) tagsHtml += `<span class="tag good">Drip God</span>`; | |
| if (brandInput.value) tagsHtml += `<span class="tag">${brandInput.value}</span>`; | |
| card.innerHTML = ` | |
| <div class="reviewer-header"> | |
| <div class="avatar">${persona.icon}</div> | |
| <div class="reviewer-info"> | |
| <h3>${persona.name}</h3> | |
| <span>${persona.vibe}</span> | |
| </div> | |
| <div class="rating-badge">${score}/10</div> | |
| </div> | |
| <div class="review-text"> | |
| "${text}" | |
| </div> | |
| <div class="tags"> | |
| ${tagsHtml} | |
| </div> | |
| `; | |
| return card; | |
| } | |
| function generateRoast() { | |
| // UI State | |
| roastBtn.disabled = true; | |
| loader.style.display = 'block'; | |
| resultsArea.style.display = 'none'; | |
| resultsArea.innerHTML = ''; | |
| // Loading Texts | |
| const loadingPhrases = [ | |
| "Analyzing fabric quality...", | |
| "Consulting the group chat...", | |
| "Judging your life choices...", | |
| "Typing furiously...", | |
| "Sending screenshots to besties..." | |
| ]; | |
| let phraseIndex = 0; | |
| const interval = setInterval(() => { | |
| loadingText.innerText = loadingPhrases[phraseIndex % loadingPhrases.length]; | |
| phraseIndex++; | |
| }, 800); | |
| // Simulation Delay | |
| setTimeout(() => { | |
| clearInterval(interval); | |
| loader.style.display = 'none'; | |
| roastBtn.disabled = false; | |
| resultsArea.style.display = 'flex'; | |
| // Logic: 10% chance for "The Council" (All girls), otherwise 1 girl | |
| const isMegaRoast = Math.random() < 0.10; | |
| if (isMegaRoast) { | |
| const alert = document.createElement('div'); | |
| alert.className = 'mega-review-alert'; | |
| alert.innerText = "🚨 THE GROUP CHAT HAS LEAKED! EVERYONE IS ROASTING YOU! 🚨"; | |
| resultsArea.appendChild(alert); | |
| // Pick 5 random personas | |
| const shuffled = personas.sort(() => 0.5 - Math.random()); | |
| const selectedPersonas = shuffled.slice(0, 5); | |
| selectedPersonas.forEach(p => { | |
| const score = generateRandomRating(); | |
| const text = getRoastText(score, brandInput.value, occasionInput.value); | |
| resultsArea.appendChild(createReviewCard(p, score, text)); | |
| }); | |
| } else { | |
| // Single Reviewer | |
| const persona = personas[Math.floor(Math.random() * personas.length)]; | |
| const score = generateRandomRating(); | |
| const text = getRoastText(score, brandInput.value, occasionInput.value); | |
| resultsArea.appendChild(createReviewCard(persona, score, text)); | |
| } | |
| // Scroll to results | |
| resultsArea.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| }, 2500); | |
| } | |
| </script> | |
| </body> | |
| </html> |