emojistory-generator / index.html
ioniacob's picture
Big IDEAS come from emojis
169bcf4 verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generador de Historias con Emojis</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#8b5cf6',
'primary-dark': '#7c3aed',
secondary: '#f0f9ff',
dark: '#1e293b',
accent: '#d8b4fe'
},
fontFamily: {
'sans': ['Inter', 'sans-serif']
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
overflow-x: hidden;
background: linear-gradient(135deg, #f0f9ff 0%, #d8b4fe 100%);
min-height: 100vh;
}
.emoji-card {
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(139, 92, 246, 0.1);
}
.emoji-card:hover {
transform: translateY(-5px) scale(1.05);
box-shadow: 0 10px 15px rgba(139, 92, 246, 0.2);
z-index: 10;
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 1rem;
}
@media (min-width: 640px) {
.emoji-grid {
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
}
}
.emoji-bubble {
animation: float 5s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-20px) rotate(5deg); }
}
.fade-in {
animation: fadeIn 0.5s ease-in forwards;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.story-text {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
}
.selected-emoji {
position: relative;
display: inline-block;
transition: all 0.3s ease;
}
.selected-emoji::after {
content: '✕';
position: absolute;
top: -8px;
right: -8px;
background-color: #ef4444;
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
cursor: pointer;
}
.selected-emoji:hover::after {
opacity: 1;
}
</style>
</head>
<body class="antialiased">
<!-- Header with title and navigation -->
<header class="bg-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 py-3 flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center">
<i class="fa-solid fa-face-smile-beam text-3xl text-primary mr-3"></i>
<h1 class="text-2xl md:text-3xl font-bold text-dark">EmojiStory Generator</h1>
</div>
<div class="mt-2 md:mt-0 flex space-x-2">
<button id="how-to-use-btn" class="px-4 py-2 rounded-full bg-secondary text-primary hover:bg-primary hover:text-white transition">
<i class="fas fa-question-circle mr-2"></i>Instrucciones
</button>
<button id="api-config-btn" class="px-4 py-2 rounded-full bg-secondary text-primary hover:bg-primary hover:text-white transition">
<i class="fas fa-cog mr-2"></i>Configurar API
</button>
</div>
</div>
</header>
<!-- Main Content Area -->
<main class="container mx-auto px-4 py-8">
<!-- Selected Emoji Bar -->
<div id="selection-bar" class="bg-white rounded-xl shadow-lg p-4 mb-8 flex flex-wrap items-center min-h-[60px]">
<h2 class="text-lg font-semibold text-dark mr-4">Tus emojis:</h2>
<div id="selected-emojis-container" class="flex flex-wrap gap-2">
<!-- Selected emojis will appear here -->
</div>
<button id="clear-selection" class="ml-auto text-sm px-3 py-1 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition">
Limpiar selección
</button>
</div>
<!-- Search Area -->
<div class="text-center mb-10">
<div class="relative max-w-2xl mx-auto">
<i class="fa-solid fa-magnifying-glass absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
<input
id="search-input"
type="text"
placeholder="Buscar emojis por nombre o categoría..."
class="w-full py-4 pl-12 pr-40 rounded-full border-2 border-primary focus:border-primary-dark focus:ring-0 text-dark shadow-lg"
>
<button id="search-btn" class="absolute right-2 top-1/2 transform -translate-y-1/2 bg-primary hover:bg-primary-dark text-white px-6 py-2 rounded-full transition">
Buscar
</button>
</div>
</div>
<!-- Story Generation Area -->
<div class="text-center my-12">
<button id="generate-btn" class="bg-gradient-to-r from-primary to-primary-dark hover:from-primary-dark hover:to-primary text-white font-bold py-4 px-8 rounded-full text-xl transition-all transform hover:scale-105 shadow-xl">
<i class="fas fa-wand-magic-sparkles mr-2"></i>Crear Historia
</button>
</div>
<!-- Story Display -->
<div id="story-container" class="hidden mt-12">
<div class="story-text bg-white rounded-2xl p-8 mb-6">
<h2 class="text-2xl font-bold text-center text-dark mb-6">Tu historia generada</h2>
<div id="story-content" class="text-lg text-gray-700 mb-6 leading-relaxed">
<!-- Story content will be loaded here -->
</div>
<div class="flex flex-wrap justify-center gap-4">
<button id="copy-story" class="px-5 py-2.5 bg-primary hover:bg-primary-dark text-white rounded-full transition flex items-center">
<i class="fas fa-copy mr-2"></i> Copiar historia
</button>
<button id="share-twitter" class="px-5 py-2.5 bg-blue-500 hover:bg-blue-600 text-white rounded-full transition flex items-center">
<i class="fab fa-twitter mr-2"></i> Compartir en Twitter
</button>
</div>
</div>
</div>
<!-- Emoji Grid -->
<div id="emoji-grid" class="emoji-grid">
<!-- Emojis will be loaded here dynamically -->
</div>
</main>
<!-- Modal Dialogs -->
<div id="how-to-use-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-2xl p-8 max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-start mb-6">
<h3 class="text-2xl font-bold text-dark">Instrucciones de Uso</h3>
<button id="close-how-to-use" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<h4 class="font-semibold text-primary mb-2">1. Explorar Emojis</h4>
<p class="text-gray-700">Navega por los emojis organizados en categorías. Puedes ver más usando las flechas de navegación.</p>
</div>
<div>
<h4 class="font-semibold text-primary mb-2">2. Seleccionar Emojis</h4>
<p class="text-gray-700">Haz clic en cualquier emoji para añadirlo a tu selección en la barra superior. Verás aparecer una X sobre el emoji para poder eliminarlo.</p>
</div>
<div>
<h4 class="font-semibold text-primary mb-2">3. Buscar Emojis</h4>
<p class="text-gray-700">Usa el buscador central para filtrar emojis por nombre o categoría.</p>
</div>
<div>
<h4 class="font-semibold text-primary mb-2">4. Generar Historia</h4>
<p class="text-gray-700">Una vez que tengas seleccionados algunos emojis, pulsa "Crear Historia" para generar una historia única basada en tus emojis.</p>
</div>
<div>
<h4 class="font-semibold text-primary mb-2">5. Compartir</h4>
<p class="text-gray-700">Puedes copiar tu historia o compartirla directamente en Twitter.</p>
</div>
</div>
</div>
</div>
<div id="api-config-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-2xl p-8 max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-start mb-6">
<h3 class="text-2xl font-bold text-dark">Configuración de API</h3>
<button id="close-api-config" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-6">
<div>
<h4 class="font-semibold text-primary mb-2">Configuración de Hugging Face</h4>
<p class="text-gray-700 mb-4">Para generar historias, necesitas una API key de Hugging Face. Cómo obtenerla:</p>
<ol class="list-decimal pl-6 space-y-2 mb-4">
<li>Crear una cuenta en <a href="https://huggingface.co" target="_blank" class="text-primary hover:underline">huggingface.co</a></li>
<li>Ir a <a href="https://huggingface.co/settings/tokens" target="_blank" class="text-primary hover:underline">Settings → Access Tokens</a></li>
<li>Generar un nuevo token (seleccionar "Read" como permiso)</li>
<li>Copiar y pegar el token aquí:</li>
</ol>
<div class="flex space-x-3">
<input
type="text"
id="api-key-input"
placeholder="Pega tu API key de Hugging Face aquí"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg"
>
<button id="save-api-key" class="px-4 py-2 bg-primary hover:bg-primary-dark text-white rounded-lg">
Guardar
</button>
</div>
</div>
<div class="pt-4 border-t border-gray-200">
<h4 class="font-semibold text-primary mb-2">Alternativas de API</h4>
<p class="text-gray-700 mb-3">Si Hugging Face no está disponible, puedes usar estos servicios alternativos:</p>
<ul class="list-disc pl-6 space-y-2">
<li><a href="https://openrouter.ai" target="_blank" class="text-primary hover:underline">OpenRouter AI</a>: Servicio con múltiples modelos de IA gratuitos.</li>
<li><a href="https://deepai.org" target="_blank" class="text-primary hover:underline">DeepAI</a>: Plataforma de IA con API gratuita.</li>
<li><a href="https://beta.openai.com" target="_blank" class="text-primary hover:underline">OpenAI</a>: ChatGPT con API (requiere registro y créditos iniciales gratuitos).</li>
</ul>
</div>
</div>
</div>
</div>
<script>
// Emoji database - categorized
const emojisByCategory = {
"caras": ["😀", "😁", "😂", "🤣", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎", "😍", "😘", "🥰"],
"manos": ["👋", "🤚", "🖐", "✋", "🖖", "👌", "🤏", "✌️", "🤞", "🤟", "🤘", "🤙", "👈", "👉", "👆"],
"animales": ["🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐨", "🐯", "🦁", "🐮", "🐷", "🐸", "🐵"],
"comida": ["🍏", "🍎", "🍐", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🫐", "🍈", "🍒", "🍑", "🥭", "🍍"],
"naturaleza": ["🌵", "🎄", "🌲", "🌳", "🌴", "🌱", "🌿", "☘️", "🍀", "🎍", "🎋", "🍃", "🍂", "🍁", "🍄"],
"deportes": ["⚽", "🏀", "🏈", "⚾", "🎾", "🏐", "🏉", "🎱", "🏓", "🏸", "🥊", "🛹", "⛳", "🥏", "🏹"],
"objetos": ["⌚", "📱", "📲", "💻", "⌨️", "🖥", "🖨", "🖱", "🖲", "🕹", "📞", "☎️", "📟", "📠", "📺"],
"transporte": ["🚗", "🚕", "🚙", "🚌", "🚎", "🏎", "🚓", "🚑", "🚒", "🚐", "🛻", "🚚", "🚛", "🚜", "🏍"],
"símbolos": ["💖", "💝", "💘", "❤️", "💓", "💔", "💌", "💋", "💯", "💢", "💥", "💫", "💦", "💨", "🕳️"]
};
// UI elements
const searchInput = document.getElementById('search-input');
const searchBtn = document.getElementById('search-btn');
const emojiGrid = document.getElementById('emoji-grid');
const selectionBar = document.getElementById('selection-bar');
const selectedEmojisContainer = document.getElementById('selected-emojis-container');
const clearSelectionBtn = document.getElementById('clear-selection');
const generateBtn = document.getElementById('generate-btn');
const storyContainer = document.getElementById('story-container');
const storyContent = document.getElementById('story-content');
const copyStoryBtn = document.getElementById('copy-story');
const shareTwitterBtn = document.getElementById('share-twitter');
const howToUseModal = document.getElementById('how-to-use-modal');
const apiConfigModal = document.getElementById('api-config-modal');
const howToUseBtn = document.getElementById('how-to-use-btn');
const apiConfigBtn = document.getElementById('api-config-btn');
const closeHowToUse = document.getElementById('close-how-to-use');
const closeApiConfig = document.getElementById('close-api-config');
const apiKeyInput = document.getElementById('api-key-input');
const saveApiKeyBtn = document.getElementById('save-api-key');
// App state
let selectedEmojis = [];
let filteredEmojis = [];
const apiKey = localStorage.getItem('huggingface_api_key') || 'hf_wF';
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
loadEmojis();
loadSelectedFromUrl();
setupEventListeners();
if (!apiKey) {
showApiConfigModal();
}
});
// Load emojis from the database
function loadEmojis() {
emojiGrid.innerHTML = '';
for (const category in emojisByCategory) {
emojisByCategory[category].forEach(emoji => {
const emojiElement = createEmojiElement(emoji, category);
emojiGrid.appendChild(emojiElement);
});
}
}
// Create emoji element with animation and hover effects
function createEmojiElement(emoji, category) {
const div = document.createElement('div');
div.className = 'emoji-card bg-white rounded-xl p-4 flex flex-col items-center justify-center cursor-pointer transition-all hover:bg-accent';
div.innerHTML = `
<div class="text-4xl mb-1">${emoji}</div>
<span class="text-xs text-gray-500">${category}</span>
`;
div.addEventListener('click', () => {
toggleEmojiSelection(emoji);
div.classList.add('bg-accent');
});
return div;
}
// Toggle emoji selection
function toggleEmojiSelection(emoji) {
const index = selectedEmojis.indexOf(emoji);
if (index === -1) {
selectedEmojis.push(emoji);
} else {
selectedEmojis.splice(index, 1);
}
updateSelectedEmojisDisplay();
updateUrlParameters();
}
// Update the selected emojis display
function updateSelectedEmojisDisplay() {
selectedEmojisContainer.innerHTML = '';
if (selectedEmojis.length === 0) {
selectedEmojisContainer.innerHTML = '<p class="text-gray-500 italic">Selecciona algunos emojis para generar una historia</p>';
return;
}
selectedEmojis.forEach(emoji => {
const span = document.createElement('span');
span.className = 'selected-emoji text-3xl px-2 py-1 bg-white rounded-lg border-2 border-primary transition-all';
span.textContent = emoji;
span.addEventListener('click', (e) => {
e.stopPropagation();
toggleEmojiSelection(emoji);
});
selectedEmojisContainer.appendChild(span);
});
}
// Update URL with selected emojis as parameters
function updateUrlParameters() {
const url = new URL(window.location);
url.searchParams.set('emojis', selectedEmojis.join(''));
window.history.replaceState({}, '', url);
}
// Load selected emojis from URL parameters
function loadSelectedFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
const emojisParam = urlParams.get('emojis');
if (emojisParam) {
selectedEmojis = Array.from(emojisParam);
updateSelectedEmojisDisplay();
}
}
// Filter emojis by name or category
function filterEmojis(query) {
emojiGrid.innerHTML = '';
const filtered = [];
query = query.toLowerCase();
for (const category in emojisByCategory) {
emojisByCategory[category].forEach(emoji => {
if (category.toLowerCase().includes(query) || emoji.toLowerCase().includes(query)) {
filtered.push({emoji, category});
}
});
}
if (filtered.length === 0) {
emojiGrid.innerHTML = `
<div class="col-span-full text-center py-12">
<i class="fas fa-sad-tear text-5xl text-primary mb-4"></i>
<p class="text-xl text-gray-700">No se encontraron emojis para "${query}"</p>
<p class="text-gray-500 mt-2">Intenta buscar por nombre o categoría</p>
</div>
`;
} else {
filtered.forEach(item => {
const emojiElement = createEmojiElement(item.emoji, item.category);
emojiGrid.appendChild(emojiElement);
});
}
}
// Show API configuration modal
function showApiConfigModal() {
apiKeyInput.value = apiKey;
apiConfigModal.classList.remove('hidden');
}
// Setup all event listeners
function setupEventListeners() {
// Search functionality
searchBtn.addEventListener('click', () => {
filterEmojis(searchInput.value.trim());
});
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
filterEmojis(searchInput.value.trim());
}
});
// Clear selection
clearSelectionBtn.addEventListener('click', () => {
selectedEmojis = [];
updateSelectedEmojisDisplay();
updateUrlParameters();
});
// Generate story
generateBtn.addEventListener('click', async () => {
if (selectedEmojis.length === 0) {
alert("Por favor selecciona al menos un emoji primero");
return;
}
if (!apiKey) {
alert("Necesitas configurar tu API key primero");
showApiConfigModal();
return;
}
try {
generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Generando...';
generateBtn.disabled = true;
const story = await generateStory(selectedEmojis);
storyContent.innerHTML = `<p class="text-lg">${story}</p>`;
storyContainer.classList.remove('hidden');
window.scrollTo({
top: document.getElementById('story-container').offsetTop - 100,
behavior: 'smooth'
});
} catch (error) {
alert(`Error al generar la historia: ${error.message}`);
console.error(error);
} finally {
generateBtn.innerHTML = '<i class="fas fa-wand-magic-sparkles mr-2"></i>Crear Historia';
generateBtn.disabled = false;
}
});
// Copy story to clipboard
copyStoryBtn.addEventListener('click', () => {
const textarea = document.createElement('textarea');
textarea.value = storyContent.innerText;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
// Show feedback
const originalText = copyStoryBtn.innerHTML;
copyStoryBtn.innerHTML = '<i class="fas fa-check mr-2"></i>Copiada!';
setTimeout(() => {
copyStoryBtn.innerHTML = originalText;
}, 2000);
});
// Share to Twitter
shareTwitterBtn.addEventListener('click', () => {
const text = encodeURIComponent(`He creado una historia con emojis! Mira lo que generé:\n\n${storyContent.innerText}\n\n#EmojiStory`);
const url = `https://twitter.com/intent/tweet?text=${text}`;
window.open(url, '_blank');
});
// Modal controls
howToUseBtn.addEventListener('click', () => {
howToUseModal.classList.remove('hidden');
});
apiConfigBtn.addEventListener('click', () => {
showApiConfigModal();
});
closeHowToUse.addEventListener('click', () => {
howToUseModal.classList.add('hidden');
});
closeApiConfig.addEventListener('click', () => {
apiConfigModal.classList.add('hidden');
});
saveApiKeyBtn.addEventListener('click', () => {
const key = apiKeyInput.value.trim();
localStorage.setItem('huggingface_api_key', key);
apiConfigModal.classList.add('hidden');
alert("API key guardada con éxito!");
});
}
// Simulate story generation from an AI API
async function generateStory(emojis) {
// This is a simulation of an AI API call
// In a real implementation, this would be replaced with a fetch to Hugging Face or OpenRouter
// Simulate API call delay
await new Promise(resolve => setTimeout(resolve, 1500));
// Map of possible stories for different emoji combinations
const stories = {
"😀": "Había una vez un personaje que siempre estaba feliz. Su sonrisa iluminaba todo a su alrededor.",
"🐶": "Un perrito juguetón corría por el parque persiguiendo una pelota. Era el alma de la fiesta en el parque.",
"🍕": "La mejor pizza del mundo tenía extra queso y todos los ingredientes que podías imaginar.",
"😀🐶": "Un niño feliz jugaba con su perrito en el parque. Juntos hacían travesuras.",
"😀🍕": "Nada hacía más feliz a Juan que una deliciosa pizza recién horneada.",
"🐶🍕": "El perro miraba con ojos suplicantes cada vez que sus dueños comían pizza.",
"😀🐶🍕": "María sonreía mientras compartía su pizza con su leal perrito, que meneaba la cola con alegría."
};
// For demo purposes, return a story based on the combination or a default story
const key = emojis.join('');
if (stories[key]) {
return stories[key];
}
// Create a generic story for the selected emojis
const emojiList = emojis.join(' ');
return `Esta es una historia especial para tus emojis seleccionados: ${emojiList}. Había una vez un lugar mágico donde estos símbolos cobraban vida. Juntos creaban aventuras increíbles. ¿Te imaginas qué pasaría si ${emojis[0]} conociera a ${emojis[1]}? Seguramente sería una aventura que nunca olvidarías.`;
}
</script>
</body>
</html>