LUNA+
LUNA+
EstoyConLuna · España
🇪🇸 La primera red social española de IA humana

La IA está en todas partes.
Pero nadie habla
de cómo te está cambiando a ti.

EstoyConLuna es la comunidad española donde puedes ser honesto sobre tus miedos, tus dudas y tus oportunidades con la IA. Sin juicios. Sin tecnicismos. Con Luna siempre a tu lado.

🇪🇸
Hecha en España. Para personas de aquí. Sin Silicon Valley de por medio.
🔒
Sin venta de datos. Sin publicidad. Sin algoritmos que te manipulan. Jamás.
🌙
LUNA+ te conoce y aprende de ti — cada conversación evoluciona contigo.
🤝
Posts, fotos, comentarios y likes — con opción de anonimato total.
0 €Para siempre gratis
100%Español y privado
24/7Luna disponible

Crea tu cuenta

Gratis · Sin tarjeta · Sin spam · Siempre

o regístrate con email

🔒 Sin anuncios · Sin datos vendidos · Puedes irte cuando quieras

¡Bienvenido/a a EstoyConLuna!

Revisa tu email para confirmar. Luna te está esperando 🌙

🌙 La comunidad late ahora mismo

Personas reales.
Conexiones reales.
Luna siempre contigo.

Cada punto de luz es alguien que decidió no enfrentarse solo a la inteligencia artificial. Mueve el cursor y siente la red.

0 €
Siempre gratis
100%
Español y privado
24/7
Luna disponible
Todo lo que puedes hacer

Una red social construida diferente

No es otro clon de Instagram. Es un espacio pensado para que las personas se ayuden entre sí con la IA y sin miedo.

📝

Posts con consejo de IA

Publica texto, fotos o reflexiones. Cuando quieras, añade un tip de Luna al post — un consejo de IA personalizado para enriquecer lo que compartes.

🎭

Anonimato cuando lo eliges

Puedes publicar, comentar y dar likes con tu nombre real o en anónimo. Tú decides en cada interacción qué muestras y qué no.

🌙

Luna te conoce a ti

Luna recuerda tus conversaciones anteriores. Cada vez que hablas con ella, parte de donde lo dejasteis. Tu contexto es tuyo — nadie más lo ve.

💬

Comentarios y conexión real

Comenta, responde, conecta. Las conversaciones son el corazón de la red. Sin algoritmos que decidan qué ves — cronológico y honesto.

❤️

Likes con o sin identidad

Puedes dar me gusta mostrando quién eres o en modo anónimo. Las reacciones son tuyas — úsalas como quieras.

🎨

Tu perfil, tu identidad

Elige tu nickname, sube tu foto de perfil y decide qué información compartes. Tu perfil es tan público o privado como tú quieras.

+10 XP
+5 ✨ Destellos
🎮 Luna Academy

Aprende IA. Sube de nivel. Gana Destellos.

10 preguntas. Cada respuesta correcta te da XP y ✨ Destellos — la moneda de la comunidad.

0 Destellos
🌱 Principiante0 / 100 XP
Nivel 1
IA Básica
1 / 10
Cargando pregunta...
0Correctas
0Racha actual
0Preguntas hechas
💬 Comunidad en vivo

El feed de la comunidad

Posts reales de personas reales. Sin algoritmo. Sin filtros. Cronológico.

🌙 Cargando la comunidad...
Sin complicaciones

Cómo funciona

Cuatro pasos para entrar en la comunidad y empezar.

1

Crea tu cuenta

Con Google o email. Elige tu nickname y sube tu foto. En 2 minutos estás dentro.

2

Habla con Luna

Luna te hace las primeras preguntas, aprende de ti y se convierte en tu guía personal.

3

Explora y conecta

Lee posts de la comunidad, comenta, da likes y comparte tus experiencias con la IA.

4

Publica con un tip

Escribe tu post y pide a Luna que añada un consejo de IA. Tu experiencia + la IA = valor real.

Nuestro compromiso

Tu privacidad es sagrada aquí

En EstoyConLuna no somos el producto. Somos la herramienta. Sin publicidad, sin venta de datos, sin algoritmos diseñados para atraparte.

Lo que hablas con Luna es solo tuyo. Nadie más puede verlo, ni nosotros lo usamos para nada que no sea hacer tu experiencia mejor.

Unirme con esa garantía →
🔒

Cero venta de datos

Tus datos no se venden a nadie. Ni ahora ni nunca. Está en nuestra esencia.

❤️

Sin ánimo de lucro

Existimos para las personas, no para inversores ni anunciantes.

🎭

Anonimato real

Puedes participar sin revelar tu identidad. Tú decides en cada momento.

🚪

Salida siempre libre

Puedes borrar tu cuenta y todos tus datos en cualquier momento, sin preguntas.

Pruébala ahora

Habla con Luna

Sin registro. Prueba cómo es tener a Luna a tu lado antes de unirte.

Luna
Luna
En línea · Siempre disponible
Hola 👋 Soy Luna. ¿Qué está pasando en tu vida con la IA? Cuéntame sin filtros.
, // así que se obtienen en DOMContentLoaded para evitar que getElementById devuelva null // y rompa la ejecución del script (lo que impedía cargar el feed y LunaAcademy). let msgSidebar = null; let msgOverlay = null; let msgBadge = null; let msgConvPanel = null; let msgChatPanel = null; let msgList = null; let msgMessages = null; let msgInput = null; let msgEmpty = null; function openMsgSidebar() { if(msgSidebar) msgSidebar.classList.add('open'); if(msgOverlay) msgOverlay.classList.add('show'); } function closeMsgSidebar() { if(msgSidebar) msgSidebar.classList.remove('open'); if(msgOverlay) msgOverlay.classList.remove('show'); } function showConvList() { if(msgConvPanel) { msgConvPanel.style.display='flex'; } if(msgChatPanel) msgChatPanel.classList.remove('show'); activeChatUserId=null; } function showChat(userId, nick, av) { activeChatUserId = userId; if(msgConvPanel) msgConvPanel.style.display = 'none'; if(msgChatPanel) msgChatPanel.classList.add('show'); const nameEl = document.getElementById('msgChatName'); if(nameEl) nameEl.textContent = nick || userId; const avEl = document.getElementById('msgChatAv'); if (avEl) { if (av) { avEl.innerHTML = ``; } else { avEl.textContent = (nick||'?')[0].toUpperCase(); } } loadMessages(userId); markRead(userId); } document.getElementById('msgOpenBtn').addEventListener('click', () => { openMsgSidebar(); loadConversations(); }); // El resto de los elementos del sidebar están DESPUÉS del , se asignan en DOMContentLoaded document.addEventListener('DOMContentLoaded', () => { msgSidebar = document.getElementById('msgSidebar'); msgOverlay = document.getElementById('msgOverlay'); msgBadge = document.getElementById('msgBadge'); msgConvPanel = document.getElementById('msgConvPanel'); msgChatPanel = document.getElementById('msgChatPanel'); msgList = document.getElementById('msgList'); msgMessages = document.getElementById('msgMessages'); msgInput = document.getElementById('msgInput'); msgEmpty = document.getElementById('msgEmpty'); document.getElementById('msgCloseBtn')?.addEventListener('click', closeMsgSidebar); if(msgOverlay) msgOverlay.addEventListener('click', closeMsgSidebar); document.getElementById('msgBackBtn')?.addEventListener('click', showConvList); document.getElementById('msgSendBtn')?.addEventListener('click', sendMsg); if(msgInput) msgInput.addEventListener('keydown', e => { if(e.key==='Enter'){e.preventDefault();sendMsg();} }); }); /* — Formatear timestamp — */ function fmtTime(ts) { const d = new Date(ts); const now = new Date(); if (d.toDateString() === now.toDateString()) return d.toLocaleTimeString('es-ES',{hour:'2-digit',minute:'2-digit'}); return d.toLocaleDateString('es-ES',{day:'2-digit',month:'2-digit'}); } /* — Añadir burbuja de mensaje — */ function addBubble(msg, append=true) { const isMe = msg.sender_id === ME.id; const d = document.createElement('div'); d.className = `msg-bubble-wrap ${isMe?'me':'them'}`; d.dataset.msgId = msg.id; d.innerHTML = `
${msg.content.replace(/${fmtTime(msg.created_at)}
`; if (append) { msgMessages.appendChild(d); msgMessages.scrollTop = msgMessages.scrollHeight; } else { msgMessages.prepend(d); } } /* — Cargar mensajes de una conversación — */ async function loadMessages(userId) { msgMessages.innerHTML = ''; if (!db || !ME.id) return; const { data } = await db.from('mensajes') .select('*') .or(`and(sender_id.eq.${ME.id},receiver_id.eq.${userId}),and(sender_id.eq.${userId},receiver_id.eq.${ME.id})`) .order('created_at', { ascending: true }) .limit(50); (data||[]).forEach(m => addBubble(m)); msgMessages.scrollTop = msgMessages.scrollHeight; } /* — Cargar lista de conversaciones — */ async function loadConversations() { if (!db || !ME.id) { msgEmpty.innerHTML = '
🔑

Inicia sesión para ver tus mensajes.

'; return; } const { data } = await db.from('mensajes') .select('*') .or(`sender_id.eq.${ME.id},receiver_id.eq.${ME.id}`) .order('created_at', { ascending: false }); if (!data || data.length === 0) { msgEmpty.style.display='block'; return; } msgEmpty.style.display = 'none'; const convMap = {}; data.forEach(m => { const otherId = m.sender_id === ME.id ? m.receiver_id : m.sender_id; if (!convMap[otherId]) convMap[otherId] = { lastMsg: m, unread: 0 }; if (m.receiver_id === ME.id && !m.leido) convMap[otherId].unread++; }); msgList.innerHTML = ''; Object.entries(convMap).forEach(([uid, conv]) => { const div = document.createElement('div'); div.className = 'msg-conv'; div.innerHTML = `
${uid[0].toUpperCase()}
${uid}
${conv.lastMsg.content.slice(0,40)}${conv.lastMsg.content.length>40?'…':''}
${fmtTime(conv.lastMsg.created_at)} ${conv.unread>0?`${conv.unread}`:''}
`; div.addEventListener('click', () => showChat(uid, uid, null)); msgList.appendChild(div); }); } /* — Marcar como leídos — */ async function markRead(userId) { if (!db || !ME.id) return; await db.from('mensajes') .update({ leido: true }) .eq('receiver_id', ME.id) .eq('sender_id', userId) .eq('leido', false); updateBadge(); } /* — Contar no leídos — */ async function updateBadge() { if (!db || !ME.id) return; const { count } = await db.from('mensajes') .select('id', { count:'exact', head:true }) .eq('receiver_id', ME.id) .eq('leido', false); const badge = msgBadge || document.getElementById('msgBadge'); if (!badge) return; if (count > 0) { badge.textContent=count; badge.classList.add('show'); } else { badge.classList.remove('show'); } } /* — Enviar mensaje — */ async function sendMsg() { const text = msgInput.value.trim(); if (!text || !activeChatUserId || !db || !ME.id) return; msgInput.value = ''; msgInput.focus(); const { data, error } = await db.from('mensajes').insert({ sender_id: ME.id, receiver_id: activeChatUserId, content: text, leido: false }).select().single(); if (!error && data) addBubble(data); } /* — Realtime: escuchar mensajes entrantes — */ function subscribeRealtime() { if (!db || !ME.id) return; if (realtimeChannel) { db.removeChannel(realtimeChannel); } realtimeChannel = db.channel(`inbox-${ME.id}`) .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'mensajes', filter: `receiver_id=eq.${ME.id}` }, payload => { const msg = payload.new; if (activeChatUserId === msg.sender_id) { addBubble(msg); markRead(msg.sender_id); } else { updateBadge(); // Notificación nativa si está en background if (Notification.permission === 'granted') { new Notification('EstoyConLuna · Nuevo mensaje', { body: msg.content.slice(0,80), icon: '/logoluna.png' }); } } }) .subscribe(); } /* — Pedir permiso de notificaciones — */ function askNotifPermission() { if ('Notification' in window && Notification.permission === 'default') { setTimeout(() => Notification.requestPermission(), 5000); } } /* — Inicializar mensajería cuando hay sesión — */ function initMensajeria(userId, nick, av) { ME = { id: userId, nick, av }; localStorage.setItem('ecl_user_id', userId); localStorage.setItem('ecl_user_nick', nick||''); localStorage.setItem('ecl_user_av', av||''); subscribeRealtime(); updateBadge(); askNotifPermission(); } // Auto-init si ya hay sesión guardada if (ME.id && db) { subscribeRealtime(); updateBadge(); askNotifPermission(); } /* — Función global para abrir chat con un usuario — */ window.abrirChatCon = function(userId, nick, av) { openMsgSidebar(); showChat(userId, nick, av); }; /* ── SERVICE WORKER + INSTALL PROMPT ── */ if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('sw.js') .then(() => console.log('SW: EstoyConLuna ready')) .catch(e => console.log('SW error:', e)); }); } let installPrompt = null; const installBanner = document.getElementById('installBanner'); window.addEventListener('beforeinstallprompt', e => { e.preventDefault(); installPrompt = e; if (installBanner) installBanner.style.display = 'flex'; }); window.addEventListener('appinstalled', () => { if (installBanner) installBanner.style.display = 'none'; installPrompt = null; }); if (installBanner) { document.getElementById('installBtn').addEventListener('click', async () => { if (!installPrompt) return; installPrompt.prompt(); const { outcome } = await installPrompt.userChoice; if (outcome === 'accepted') installBanner.style.display = 'none'; installPrompt = null; }); document.getElementById('installDismiss').addEventListener('click', () => { installBanner.style.display = 'none'; }); } /* ══ LUNA ACADEMY QUIZ ══ */ (function(){ const PREGUNTAS = [ { cat:'IA Básica', q:'¿Qué significa GPT en ChatGPT?', opts:['General Purpose Technology','Generative Pre-trained Transformer','Google Processing Tool','Graphical Processor Terminal'], ok:1, ex:'GPT son las siglas de Generative Pre-trained Transformer — una arquitectura de redes neuronales que aprende de enormes cantidades de texto para generar respuestas.' }, { cat:'Empresas IA', q:'¿Qué empresa creó a Claude, la IA que impulsa Luna?', opts:['Google DeepMind','OpenAI','Anthropic','Meta AI'], ok:2, ex:'Anthropic creó a Claude en 2021. Fue fundada por ex-empleados de OpenAI y es conocida por su enfoque en IA segura y fiable.' }, { cat:'Conceptos', q:'¿Qué es un "prompt" en inteligencia artificial?', opts:['Un error del sistema','El resultado que genera la IA','La instrucción o pregunta que le das a la IA','Una función matemática'], ok:2, ex:'Un prompt es la instrucción que das a la IA. Cuanto más claro y específico sea, mejor y más útil será la respuesta que obtienes.' }, { cat:'Automatización', q:'¿Para qué sirve n8n?', opts:['Para diseñar páginas web','Para automatizar flujos de trabajo conectando apps y APIs','Para hacer videollamadas','Para gestionar redes sociales'], ok:1, ex:'n8n es una plataforma de automatización que conecta aplicaciones, servicios y APIs para que los procesos se ejecuten solos sin intervención humana.' }, { cat:'Machine Learning', q:'¿Qué es el Machine Learning?', opts:['Una app para aprender inglés','Una red social de ingenieros','Un sistema que aprende de datos sin ser programado explícitamente','Un antivirus avanzado'], ok:2, ex:'El Machine Learning permite a los ordenadores aprender y mejorar automáticamente a partir de datos y experiencias, sin ser reprogramados.' }, { cat:'Herramientas', q:'¿Para qué sirve Midjourney?', opts:['Para escribir código automáticamente','Para generar imágenes con inteligencia artificial','Para gestionar proyectos de empresa','Para traducir documentos'], ok:1, ex:'Midjourney es una IA generativa especializada en crear imágenes fotorrealistas o artísticas a partir de descripciones de texto.' }, { cat:'Historia IA', q:'¿Cuándo se lanzó ChatGPT al público general?', opts:['Enero 2021','Marzo 2023','Noviembre 2022','Junio 2020'], ok:2, ex:'ChatGPT se lanzó el 30 de noviembre de 2022 y alcanzó 1 millón de usuarios en solo 5 días — el producto de más rápido crecimiento de la historia.' }, { cat:'Conceptos', q:'¿Qué es una "alucinación" en IA?', opts:['Que la IA ve imágenes inexistentes','Que la IA inventa información falsa con total confianza','Que la IA se queda sin memoria','Que la IA tarda demasiado en responder'], ok:1, ex:'Las alucinaciones son cuando una IA genera información incorrecta pero la presenta con absoluta confianza. Es uno de los retos principales de la IA actual.' }, { cat:'Agentes IA', q:'¿Qué es un agente de IA?', opts:['Un vendedor de software','Un asistente virtual de voz','Un modelo que toma decisiones y ejecuta acciones de forma autónoma','Un sistema de copias de seguridad'], ok:2, ex:'Un agente de IA puede planificar, usar herramientas externas y ejecutar acciones encadenadas para lograr un objetivo sin intervención humana constante.' }, { cat:'EstoyConLuna', q:'¿Cuál es la moneda de la comunidad EstoyConLuna?', opts:['Lunas','Novas','Destellos','Fotones'], ok:2, ex:'Los Destellos son la moneda de EstoyConLuna, inspirados en las estrellas fugaces. Se ganan completando quizzes, publicando y conectando con la comunidad.' }, ]; const LEVELS=[{name:'🌱 Principiante',max:100},{name:'🔭 Explorador',max:300},{name:'⚡ Conocedor',max:700},{name:'🚀 Experto',max:1500},{name:'🌙 Maestro Luna',max:99999}]; let state=JSON.parse(localStorage.getItem('luna_acad')||'{}'); state.xp=state.xp||0; state.dst=state.dst||0; state.streak=state.streak||0; state.total=state.total||0; state.correct=state.correct||0; state.idx=state.idx||0; let answered=false, current=null; function getLv(xp){return LEVELS.findIndex(l=>xp0?LEVELS[li-1].max:0; const pct=ln?Math.min(100,((state.xp-prev)/(lv.max-prev))*100):100; document.getElementById('xpFill').style.width=pct+'%'; document.getElementById('xpNum').textContent=state.xp+(ln?' / '+lv.max+' XP':' XP'); document.getElementById('levelName').textContent=lv.name; document.getElementById('levelBadge').textContent='Nivel '+(li+1); document.getElementById('dstNum').textContent=state.dst; document.getElementById('statCorr').textContent=state.correct; document.getElementById('statStreak').textContent=state.streak; document.getElementById('statTotal').textContent=state.total; } function showQ(){ answered=false; current=PREGUNTAS[state.idx%PREGUNTAS.length]; document.getElementById('quizCat').textContent=current.cat; document.getElementById('quizNum').textContent=(state.idx%PREGUNTAS.length+1)+' / '+PREGUNTAS.length; document.getElementById('quizQ').textContent=current.q; document.getElementById('quizFeedback').className='quiz-feedback'; document.getElementById('quizFeedback').innerHTML=''; document.getElementById('quizNext').className='btn btn-main quiz-next'; document.getElementById('quizStreak').textContent=''; document.getElementById('quizCard').className='quiz-card'; const opts=document.getElementById('quizOpts'); opts.innerHTML=''; ['A','B','C','D'].forEach((l,i)=>{ const b=document.createElement('button'); b.className='quiz-opt'; b.innerHTML=''+l+''+current.opts[i]; b.addEventListener('click',()=>answer(i,b)); opts.appendChild(b); }); updateUI(); } function answer(idx,btn){ if(answered)return; answered=true; state.total++; document.querySelectorAll('.quiz-opt').forEach(b=>b.setAttribute('disabled','')); const fb=document.getElementById('quizFeedback'); if(idx===current.ok){ const xg=10+Math.min(state.streak*2,20), dg=5+Math.floor(state.streak/2); state.xp+=xg; state.dst+=dg; state.streak++; state.correct++; btn.classList.add('ok'); document.getElementById('quizCard').classList.add('correct'); fb.className='quiz-feedback ok show'; fb.innerHTML='✅ Correcto! '+current.ex; if(state.streak>1) document.getElementById('quizStreak').textContent='🔥 Racha x'+state.streak+' — bonus +'+(xg-10)+' XP extra!'; showReward(xg,dg); } else { state.streak=0; btn.classList.add('bad'); document.querySelectorAll('.quiz-opt')[current.ok].classList.add('ok'); document.getElementById('quizCard').classList.add('wrong'); fb.className='quiz-feedback bad show'; fb.innerHTML='❌ No era esa. '+current.ex; } state.idx++; save(); document.getElementById('quizNext').className='btn btn-main quiz-next show'; updateUI(); } function showReward(xp,dst){ document.getElementById('rewardXP').textContent=xp; document.getElementById('rewardDST').textContent=dst; const r=document.getElementById('quizReward'); r.className='quiz-reward show'; setTimeout(()=>r.className='quiz-reward',1900); } document.getElementById('quizNext')?.addEventListener('click',()=>{ showQ(); document.getElementById('quizCard').scrollIntoView({behavior:'smooth',block:'nearest'}); }); // Esperar al DOM completo antes de inicializar el quiz if(document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { showQ(); updateUI(); }); } else { setTimeout(() => { showQ(); updateUI(); }, 50); } })(); /* ══ LUNA NEURAL AURORA ══ */ (function(){ const cv=document.getElementById('luna-aurora'); if(!cv) return; const cx=cv.getContext('2d'); let W,H,T=0,nodes=[]; const mouse={x:-9999,y:-9999}; const COLS=['#00d4ff','#7c3aed','#f59e0b','#10b981','#f43f5e','#a78bfa']; function init(){ W=cv.width=cv.offsetWidth; H=cv.height=cv.offsetHeight; nodes=Array.from({length:75},()=>({ x:Math.random()*W, y:Math.random()*H, vx:(Math.random()-.5)*.38, vy:(Math.random()-.5)*.38, r:1.4+Math.random()*2.8, col:COLS[Math.floor(Math.random()*COLS.length)], ph:Math.random()*6.28, ps:.014+Math.random()*.022 })); } const AL=[ {c1:'#00d4ff',c2:'#7c3aed',yR:.60,amp:.115,fr:.0024,ph:0, sp:.0075}, {c1:'#7c3aed',c2:'#f59e0b',yR:.46,amp:.090,fr:.0032,ph:2.1, sp:.0100}, {c1:'#00d4ff',c2:'#10b981',yR:.75,amp:.070,fr:.0041,ph:4.2, sp:.0085}, {c1:'#f59e0b',c2:'#7c3aed',yR:.35,amp:.060,fr:.0050,ph:1.0, sp:.0060}, ]; function frame(){ cx.clearRect(0,0,W,H); AL.forEach(l=>{ const op=.14+.06*Math.sin(T*.006+l.ph); const hex=n=>Math.round(n*255).toString(16).padStart(2,'0'); const g=cx.createLinearGradient(0,0,W,0); g.addColorStop(0,'transparent'); g.addColorStop(.15,l.c1+hex(op)); g.addColorStop(.85,l.c2+hex(op)); g.addColorStop(1,'transparent'); cx.beginPath(); cx.moveTo(0,H); for(let x=0;x<=W;x+=3){ const y=H*(l.yR+l.amp*Math.sin(x*l.fr+T*l.sp+l.ph)+l.amp*.42*Math.sin(x*l.fr*2.4+T*l.sp*1.5)); x===0?cx.moveTo(x,y):cx.lineTo(x,y); } cx.lineTo(W,H); cx.closePath(); cx.fillStyle=g; cx.fill(); }); nodes.forEach(n=>{ n.ph+=n.ps; const mdx=mouse.x-n.x, mdy=mouse.y-n.y, md=Math.sqrt(mdx*mdx+mdy*mdy); if(md<180){ n.vx+=mdx/md*.07; n.vy+=mdy/md*.07; } n.vx*=.968; n.vy*=.968; n.x+=n.vx; n.y+=n.vy; if(n.x<0||n.x>W) n.vx*=-1; if(n.y<0||n.y>H) n.vy*=-1; }); for(let i=0;i{ const p=.65+.35*Math.sin(n.ph); const gw=cx.createRadialGradient(n.x,n.y,0,n.x,n.y,n.r*4.5*p); gw.addColorStop(0,n.col+'cc'); gw.addColorStop(1,n.col+'00'); cx.beginPath(); cx.arc(n.x,n.y,n.r*4.5*p,0,6.28); cx.fillStyle=gw; cx.fill(); cx.beginPath(); cx.arc(n.x,n.y,n.r*p,0,6.28); cx.fillStyle=n.col; cx.fill(); }); T++; requestAnimationFrame(frame); } cv.addEventListener('mousemove',e=>{const r=cv.getBoundingClientRect(); mouse.x=e.clientX-r.left; mouse.y=e.clientY-r.top;}); cv.addEventListener('mouseleave',()=>{mouse.x=mouse.y=-9999;}); cv.addEventListener('touchmove',e=>{const r=cv.getBoundingClientRect(); const t=e.touches[0]; mouse.x=t.clientX-r.left; mouse.y=t.clientY-r.top;},{passive:true}); init(); window.addEventListener('resize',init); frame(); })(); /* ══ FEED REAL ══ */ (function(){ let feedOffset = 0; const PAGE = 8; let feedChannel = null; let currentUser = null; // Escucha cambios de auth para mostrar/ocultar compositor if(db) { db.auth.onAuthStateChange((event, session) => { currentUser = session?.user || null; updateComposer(); }); db.auth.getUser().then(({data:{user}}) => { currentUser = user; updateComposer(); }); } function updateComposer() { const comp = document.getElementById('feedComposer'); const hint = document.getElementById('feedLoginHint'); if(!comp) return; if(currentUser) { comp.style.display = 'flex'; hint.style.display = 'none'; const av = document.getElementById('compAv'); if(av) av.textContent = (currentUser.user_metadata?.nick || currentUser.email || 'U')[0].toUpperCase(); } else { comp.style.display = 'none'; hint.style.display = 'block'; } } // ── CARGAR FEED ────────────────────────────── window.loadFeed = async function(append) { if(!db) { document.getElementById('feedList').innerHTML = '
Activa Supabase para ver el feed.
'; return; } if(!append) { feedOffset = 0; document.getElementById('feedList').innerHTML = '
🌙 Cargando...
'; } const { data: posts, error } = await db .from('posts') .select('id,user_id,nick,content,anonimo,luna_tip,likes_count,comments_count,created_at') .order('created_at', { ascending: false }) .range(feedOffset, feedOffset + PAGE - 1); const list = document.getElementById('feedList'); if(!append) list.innerHTML = ''; if(error || !posts?.length) { if(!append) list.innerHTML = '
🌙 Sé el primero en publicar. La comunidad te espera.
'; document.getElementById('feedMoreBtn').style.display = 'none'; return; } // Si hay usuario, obtener sus likes para marcar los posts let myLikes = []; if(currentUser) { const { data: likes } = await db.from('likes') .select('post_id').eq('user_id', currentUser.id) .in('post_id', posts.map(p=>p.id)); myLikes = (likes||[]).map(l=>l.post_id); } posts.forEach(p => list.appendChild(buildCard(p, myLikes.includes(p.id)))); feedOffset += posts.length; document.getElementById('feedMoreBtn').style.display = posts.length === PAGE ? 'block' : 'none'; }; // ── CONSTRUIR CARD ─────────────────────────── function buildCard(p, liked) { const el = document.createElement('div'); el.className = 'post-r'; el.dataset.postId = p.id; const isMe = currentUser?.id === p.user_id; const nick = p.anonimo ? 'Anónimo' : ('@' + (p.nick || 'usuario')); const av = (p.nick || 'A')[0].toUpperCase(); const ago = timeAgo(p.created_at); el.innerHTML = `
${av}
${nick}${p.anonimo?'':''}
${ago}
${p.anonimo ? '🎭 anónimo' : ''} ${isMe ? `` : ''}
${escH(p.content)}
${p.image_url ? `imagen` : ''} ${p.luna_tip ? `
💡 Tip de Luna${escH(p.luna_tip)}
` : ''}
`; return el; } // ── LIKE ───────────────────────────────────── window.toggleLike = async function(postId, btn) { if(!db || !currentUser) { alert('Inicia sesión para dar me gusta'); return; } const liked = btn.classList.contains('liked'); const cnt = document.getElementById('lc-' + postId); const svg = btn.querySelector('svg'); if(liked) { btn.classList.remove('liked'); svg.setAttribute('fill','none'); cnt.textContent = Math.max(0, parseInt(cnt.textContent) - 1); await db.from('likes').delete().eq('user_id',currentUser.id).eq('post_id',postId); } else { btn.classList.add('liked'); svg.setAttribute('fill','currentColor'); cnt.textContent = parseInt(cnt.textContent) + 1; await db.from('likes').insert({user_id:currentUser.id, post_id:postId}); } await db.from('posts').update({likes_count: parseInt(cnt.textContent)}).eq('id',postId); }; // ── COMENTARIOS ─────────────────────────────── window.toggleComments = async function(postId) { const box = document.getElementById('comments-' + postId); if(box.style.display === 'none') { box.style.display = 'block'; await loadComments(postId, box); } else { box.style.display = 'none'; } }; async function loadComments(postId, box) { const { data } = await db.from('comentarios').select('*').eq('post_id',postId).order('created_at',{ascending:true}); box.innerHTML = (data||[]).map(c=>`
${(c.nick||'A')[0].toUpperCase()}
${c.anonimo?'Anónimo':'@'+(c.nick||'usuario')}
${escH(c.content)}
`).join('') + (currentUser ? `
` : `
Inicia sesión para comentar
`); } window.sendComment = async function(postId) { if(!db || !currentUser) return; const inp = document.getElementById('ci-' + postId); const text = inp.value.trim(); if(!text) return; const nick = currentUser.user_metadata?.nick || currentUser.email?.split('@')[0] || 'usuario'; inp.value = ''; await db.from('comentarios').insert({user_id:currentUser.id, post_id:postId, nick, content:text}); const cnt = document.getElementById('cc-' + postId); const newCount = parseInt(cnt.textContent) + 1; cnt.textContent = newCount; await db.from('posts').update({comments_count:newCount}).eq('id',postId); const box = document.getElementById('comments-' + postId); await loadComments(postId, box); }; // ── BORRAR POST ─────────────────────────────── window.deletePost = async function(postId, btn) { if(!confirm('¿Borrar este post?')) return; await db.from('posts').delete().eq('id', postId); btn.closest('.post-r').remove(); }; // ── COMPARTIR ───────────────────────────────── window.shareP = function(id, preview) { const text = preview + '...\n\nEstoyConLuna · estoyconluna.es'; if(navigator.share) navigator.share({title:'EstoyConLuna', text}); else if(navigator.clipboard) { navigator.clipboard.writeText(text); alert('Copiado al portapapeles'); } }; // ── TABS ────────────────────────────────────── window.switchTab = function(tab) { document.getElementById('panel-feed').style.display = tab==='feed' ? 'block' : 'none'; document.getElementById('panel-personas').style.display = tab==='personas' ? 'block' : 'none'; document.getElementById('tab-feed').classList.toggle('active', tab==='feed'); document.getElementById('tab-personas').classList.toggle('active', tab==='personas'); if(tab==='personas' && currentUser) loadSuggestedPeople(); }; // ── FOTO PREVIEW ────────────────────────────── let selectedFile = null; window.previewPhoto = function(input) { const file = input.files[0]; if(!file) return; selectedFile = file; const reader = new FileReader(); reader.onload = e => { document.getElementById('compImgEl').src = e.target.result; document.getElementById('compImgPreview').style.display = 'block'; }; reader.readAsDataURL(file); }; window.removePhoto = function() { selectedFile = null; document.getElementById('compFileInput').value = ''; document.getElementById('compImgPreview').style.display = 'none'; document.getElementById('compImgEl').src = ''; }; // ── COMPOSITOR ──────────────────────────────── document.getElementById('compBtn')?.addEventListener('click', async () => { const text = document.getElementById('compInput').value.trim(); if(!text || text.length < 3) return; if(!db || !currentUser) { alert('Inicia sesión para publicar'); return; } const nick = currentUser.user_metadata?.nick || currentUser.user_metadata?.full_name?.split(' ')[0] || currentUser.email?.split('@')[0] || 'usuario'; const anonimo = document.getElementById('compAnon')?.checked || false; const btn = document.getElementById('compBtn'); btn.textContent = '⏳'; btn.disabled = true; let image_url = null; if(selectedFile) { const ext = selectedFile.name.split('.').pop(); const path = `${currentUser.id}/${Date.now()}.${ext}`; const { data: up, error: upErr } = await db.storage.from('post-images').upload(path, selectedFile, {cacheControl:'3600',upsert:false}); if(!upErr) { const { data: pub } = db.storage.from('post-images').getPublicUrl(path); image_url = pub.publicUrl; } } const { error } = await db.from('posts').insert({user_id:currentUser.id, nick, content:text, anonimo, image_url}); btn.textContent = 'Publicar'; btn.disabled = false; if(!error) { document.getElementById('compInput').value = ''; removePhoto(); await loadFeed(false); } else { alert('Error: ' + error.message); } }); // ── REALTIME ────────────────────────────────── function subscribeRealtime() { if(!db) return; db.channel('feed-live') .on('postgres_changes',{event:'INSERT',schema:'public',table:'posts'}, payload => { const list = document.getElementById('feedList'); const empty = list.querySelector('.feed-msg'); if(empty) empty.remove(); list.prepend(buildCard(payload.new, false)); }).subscribe(); } // ── HELPERS ─────────────────────────────────── function escH(t){return(t||'').replace(/&/g,'&').replace(/')} function timeAgo(ts){ const s=(Date.now()-new Date(ts))/1000; if(s<60) return 'ahora'; if(s<3600) return Math.floor(s/60)+'min'; if(s<86400) return Math.floor(s/3600)+'h'; if(s<604800) return Math.floor(s/86400)+'d'; return new Date(ts).toLocaleDateString('es-ES',{day:'2-digit',month:'short'}); } // ── BUSCAR PERSONAS ─────────────────────────── window.searchPeople = async function() { if(!db) return; const q = document.getElementById('peopleInp').value.trim().replace('@',''); if(!q) return; const list = document.getElementById('peopleList'); list.innerHTML = '
🌙 Buscando...
'; const { data } = await db.from('profiles').select('id,nick,full_name,bio,avatar_url') .or(`nick.ilike.%${q}%,full_name.ilike.%${q}%`).limit(20); if(!data?.length) { list.innerHTML = '
No se encontraron usuarios con ese nombre.
'; return; } let myFollows = []; if(currentUser) { const {data:f} = await db.from('follows').select('following_id').eq('follower_id',currentUser.id).in('following_id',data.map(u=>u.id)); myFollows = (f||[]).map(x=>x.following_id); } list.innerHTML = ''; data.forEach(u => { const isMe = currentUser?.id === u.id; const isFollowing = myFollows.includes(u.id); const card = document.createElement('div'); card.className = 'people-card'; card.innerHTML = `
${(u.nick||u.full_name||'U')[0].toUpperCase()}
@${u.nick||'usuario'} ${u.full_name?'· '+u.full_name:''}
${u.bio||'Sin descripción aún'}
${!isMe ? `` : ''} `; list.appendChild(card); }); }; window.toggleFollow = async function(userId, btn) { if(!db || !currentUser) { alert('Inicia sesión para seguir personas'); return; } const isFollowing = btn.classList.contains('following'); if(isFollowing) { btn.classList.remove('following'); btn.textContent = 'Seguir'; await db.from('follows').delete().eq('follower_id',currentUser.id).eq('following_id',userId); } else { btn.classList.add('following'); btn.textContent = 'Siguiendo'; await db.from('follows').insert({follower_id:currentUser.id, following_id:userId}); } }; async function loadSuggestedPeople() { if(!db) return; const list = document.getElementById('peopleList'); if(list.querySelector('.people-card')) return; const { data } = await db.from('profiles').select('id,nick,full_name,bio').limit(10); if(!data?.length) return; let myFollows = []; if(currentUser) { const {data:f} = await db.from('follows').select('following_id').eq('follower_id',currentUser.id); myFollows = (f||[]).map(x=>x.following_id); } list.innerHTML = '
Personas en LUNA+
'; data.forEach(u => { const isMe = currentUser?.id === u.id; const isFollowing = myFollows.includes(u.id); const card = document.createElement('div'); card.className = 'people-card'; card.innerHTML = `
${(u.nick||u.full_name||'U')[0].toUpperCase()}
@${u.nick||'usuario'}${u.full_name?' · '+u.full_name:''}
${u.bio||'Nuevo en LUNA+'}
${!isMe ? `` : ''} `; list.appendChild(card); }); } // ── ARRANCAR ────────────────────────────────── if(db) { loadFeed(false); subscribeRealtime(); } else document.getElementById('feedList').innerHTML = '
🌙 Activa Supabase para ver el feed en tiempo real.
'; })();
Luna
Antes de empezar quiero conocerte un poco. ¿Qué te trajo hasta aquí?
Luna
Cuando escuchas "inteligencia artificial", ¿qué sientes?
Luna
¿Cómo describirías tu nivel actual con la IA?
Luna
Si la IA te ayudara en algo, ¿qué cambiaría más tu vida?
Luna
Última pregunta, y es la más importante. ¿Hay algo sobre la IA que te preocupa, te da miedo o simplemente no entiendes? Puedes escribir lo que sea.
🌙
Luna está leyendo tu perfil…
Analizando tus respuestas para personalizar tu experiencia
🌙
Tu perfil de IA
Explorador
Descripción del perfil
Mensaje de Luna
847 personas en EstoyConLuna tienen un perfil similar al tuyo

💬 Mensajes

💬

Aún no tienes conversaciones.
Busca a alguien en el feed y empieza a hablar.

?
Usuario
En línea