Action completed

Discover on FlikZee

Explore trending creators, sounds, and categories on Flikzee.com.

Sponsored • FlikZee Ads
Boost Your Brand on FlikZee
Reach thousands of active short-video viewers daily.
Create on FlikZee
Record, upload, use trending sounds, templates, filters and captions — all from one creator studio.
🎬
No media selected
Filters
🎵 Add a Trending Sound
Ready-to-use short-video templates managed from the FlikZee backend.
Creator editing tools

Choose a video or photo first. Then use filters, title, description, hashtags, sponsored link, and trending sounds above.

Inbox
←
Profile
⚙️
Creator
@user
0 posts0 views

0
Following
0
Followers
0
Likes
Videos
About
←

Settings & Privacy

×
Edit Profile Info ›
Creator Fund & Earnings ›
🪙 FlikZee Wallet & Rewards ›
Real-Time Notifications Active 🟢
Privacy & Safety ›
Help & Support ›
Community Guidelines ›
Privacy Policy ›
Back to Home ›
Log Out

Sign in to FlikZee

×
OR

New here? Sign Up

const CLOUDINARY_URL = "https://api.cloudinary.com/v1_1/mtbjcykq/video/upload"; const CLOUDINARY_IMAGE_URL = "https://api.cloudinary.com/v1_1/mtbjcykq/image/upload"; const CLOUDINARY_PRESET = "zipzap_unsigned"; const FEED_LIMIT = 25; const PROFILE_VIDEO_LIMIT = 50; const MONETIZATION_REQUIRED_VIEWS = 10000; const MONETIZATION_REQUIRED_FOLLOWERS = 1000; const PAYMENT_API_URL = ''; const COIN_PACKAGES = [ {id:'starter', coins:100, price:'$0.99'}, {id:'popular', coins:550, price:'$4.99'}, {id:'creator', coins:1200, price:'$9.99'}, {id:'pro', coins:2500, price:'$19.99'} ]; const LIVE_STREAM_PROVIDER = 'hidden'; const ADMIN_ACTIVITY_LIMIT = 40; let templatesUnsubscribe=null, adminActivityUnsubscribe=null, adminPaymentsUnsubscribe=null, adminTopUsersUnsubscribe=null; let creatorCameraStream=null, creatorMediaRecorder=null, creatorRecordedChunks=[], creatorFacingMode='environment', creatorRecording=false; let creatorTimerSeconds=0, creatorTimerId=null; let creatorCameraMode='10'; let creatorRecordingStartedAt=0; let creatorRecordingTimer=null; const DEFAULT_CATEGORIES = [ { id:'viral', name:'Viral', emoji:'🔥', description:'Fast-moving clips and trending posts on FlikZee.', hashtags:'#viral #trending #flikzee', order:1 }, { id:'comedy', name:'Comedy', emoji:'😂', description:'Funny, relatable and entertaining short videos.', hashtags:'#comedy #funny', order:2 }, { id:'tech', name:'Tech', emoji:'💻', description:'Apps, gadgets, coding and digital tips.', hashtags:'#tech #coding', order:3 }, { id:'gaming', name:'Gaming', emoji:'🎮', description:'Gameplay, highlights and gaming creators.', hashtags:'#gaming #gamer', order:4 }, { id:'music', name:'Music', emoji:'🎵', description:'Music clips, covers, edits and original sounds.', hashtags:'#music #song', order:5 }, { id:'lifestyle', name:'Lifestyle', emoji:'✨', description:'Travel, food, fashion and everyday life.', hashtags:'#lifestyle #travel', order:6 }, { id:'sports', name:'Sports', emoji:'⚽', description:'Football, cricket, fitness and sports highlights.', hashtags:'#sports #fitness', order:7 }, { id:'food', name:'Food', emoji:'🍔', description:'Recipes, street food, cooking and food creators.', hashtags:'#food #recipe', order:8 }, { id:'travel', name:'Travel', emoji:'✈️', description:'Destinations, road trips and travel inspiration.', hashtags:'#travel #explore', order:9 }, { id:'fashion', name:'Fashion', emoji:'👗', description:'Style, outfits, beauty and fashion inspiration.', hashtags:'#fashion #style', order:10 }, { id:'education', name:'Education', emoji:'📚', description:'Learning, tips, language and useful knowledge.', hashtags:'#education #learn', order:11 }, { id:'motivation', name:'Motivation', emoji:'💪', description:'Mindset, productivity and personal growth.', hashtags:'#motivation #mindset', order:12 } ]; const SOUND_CATEGORIES = [ 'Viral Hits', 'Desi/Punjabi Beats', 'Hip-Hop', 'Lo-Fi', 'Electronic', 'Cinematic', 'Comedy', 'Gaming' ]; const SOUND_LIBRARY = [ {id:'fz_viral_01', name:'FlikZee Mega Viral Hook', category:'Viral Hits', tempo:4.2, notes:[523,659,784,659,880,784], soundUrl:''}, {id:'fz_viral_02', name:'Trending Transition Drop', category:'Viral Hits', tempo:4.6, notes:[440,554,659,880,659,554], soundUrl:''}, {id:'fz_viral_03', name:'ForYou Flash Beat', category:'Viral Hits', tempo:4.0, notes:[587,740,880,740,659,587], soundUrl:''}, {id:'fz_desi_01', name:'Punjabi Dhol Tadka', category:'Desi/Punjabi Beats', tempo:4.4, notes:[294,370,440,494,440,370], soundUrl:''}, {id:'fz_desi_02', name:'Desi Urban Groove', category:'Desi/Punjabi Beats', tempo:3.8, notes:[330,392,494,523,494,392], soundUrl:''}, {id:'fz_desi_03', name:'Lahore & Mumbai Bass', category:'Desi/Punjabi Beats', tempo:4.5, notes:[277,330,415,494,415,330], soundUrl:''}, {id:'fz_hiphop_01', name:'808 Street Hustle', category:'Hip-Hop', tempo:3.2, notes:[196,220,262,220,196,165], soundUrl:''}, {id:'fz_hiphop_02', name:'Trap Cypher Bounce', category:'Hip-Hop', tempo:3.5, notes:[220,247,294,262,220,196], soundUrl:''}, {id:'fz_hiphop_03', name:'Boom Bap Freestyle', category:'Hip-Hop', tempo:3.0, notes:[247,294,330,294,247,220], soundUrl:''}, {id:'fz_lofi_01', name:'Midnight Rain Lo-Fi', category:'Lo-Fi', tempo:2.2, notes:[262,330,392,494,392,330], soundUrl:''}, {id:'fz_lofi_02', name:'Coffee & Chill Tape', category:'Lo-Fi', tempo:2.0, notes:[294,349,440,523,440,349], soundUrl:''}, {id:'fz_lofi_03', name:'Sunset Study Vibes', category:'Lo-Fi', tempo:2.4, notes:[247,311,370,466,370,311], soundUrl:''}, {id:'fz_edm_01', name:'Cyberpunk Synth Rush', category:'Electronic', tempo:5.0, notes:[440,523,659,880,1046,880], soundUrl:''}, {id:'fz_edm_02', name:'Neon Club Festival', category:'Electronic', tempo:4.8, notes:[392,494,587,784,987,784], soundUrl:''}, {id:'fz_edm_03', name:'Future House Pulse', category:'Electronic', tempo:4.6, notes:[349,440,523,698,880,698], soundUrl:''}, {id:'fz_cine_01', name:'Epic Trailer Rise', category:'Cinematic', tempo:1.8, notes:[147,220,294,440,392,294], soundUrl:''}, {id:'fz_cine_02', name:'Emotional Story Piano', category:'Cinematic', tempo:2.1, notes:[220,262,330,440,392,330], soundUrl:''}, {id:'fz_cine_03', name:'Dark Suspense Horizon', category:'Cinematic', tempo:1.6, notes:[131,165,196,262,247,196], soundUrl:''}, {id:'fz_comedy_01', name:'Quirky Meme Pop', category:'Comedy', tempo:5.2, notes:[523,659,784,1046,784,1046], soundUrl:''}, {id:'fz_comedy_02', name:'Cartoon Slip & Bounce', category:'Comedy', tempo:5.5, notes:[587,784,987,1175,987,784], soundUrl:''}, {id:'fz_comedy_03', name:'Wait For It Punchline', category:'Comedy', tempo:4.7, notes:[494,659,880,987,880,659], soundUrl:''}, {id:'fz_gaming_01', name:'Boss Level 16-Bit', category:'Gaming', tempo:5.4, notes:[330,440,523,659,523,440], soundUrl:''}, {id:'fz_gaming_02', name:'Esports Clutch Drop', category:'Gaming', tempo:4.9, notes:[294,392,494,587,784,587], soundUrl:''}, {id:'fz_gaming_03', name:'Arcade Turbo Drift', category:'Gaming', tempo:5.6, notes:[392,523,659,784,1046,784], soundUrl:''} ]; const FILTERS = { Original: 'none', Vivid: 'saturate(1.35) contrast(1.08)', Mono: 'grayscale(1)', Warm: 'sepia(.22) saturate(1.18) contrast(1.04)', Cool: 'saturate(.92) hue-rotate(16deg) brightness(1.03)', Retro: 'sepia(.38) saturate(.88) contrast(1.08) brightness(1.03)', Bright: 'brightness(1.15) contrast(1.02)', Dramatic: 'contrast(1.24) saturate(1.15) brightness(.94)', Soft: 'brightness(1.06) saturate(.88) contrast(.96)', Cinematic: 'contrast(1.18) saturate(.82) brightness(.96)', Sunset: 'sepia(.18) saturate(1.45) hue-rotate(-8deg) brightness(1.03)', Ocean: 'saturate(1.08) hue-rotate(12deg) contrast(1.04)', Noir: 'grayscale(.72) contrast(1.3) brightness(.92)', Glow: 'brightness(1.08) saturate(1.28) contrast(1.02)', Faded: 'saturate(.7) contrast(.92) brightness(1.04)', Vignette: 'contrast(1.12) brightness(.98) saturate(1.05)' }; const flikZeeSoundUrls = {}; const FLIKZEE_CATEGORY_PATTERNS = { 'Viral Hits': [523,659,784,659,880,784], 'Desi/Punjabi Beats': [294,370,440,494,440,370], 'Hip-Hop': [196,220,262,220,196,165], 'Lo-Fi': [262,330,392,494,392,330], 'Electronic': [440,523,659,880,1046,880], 'Cinematic': [147,220,294,440,392,294], 'Comedy': [523,659,784,1046,784,659], 'Gaming': [330,440,523,659,784,659] }; function getSoundUrl(soundId='', category='Viral Hits'){ const track = SOUND_LIBRARY.find(s => s.id === soundId); const cacheKey = track ? track.id : String(category || 'Viral Hits'); if(flikZeeSoundUrls[cacheKey]) return flikZeeSoundUrls[cacheKey]; const sr = 11025, seconds = 3.6, count = Math.floor(sr * seconds); const pattern = track?.notes || FLIKZEE_CATEGORY_PATTERNS[category] || FLIKZEE_CATEGORY_PATTERNS['Viral Hits']; const speed = track?.tempo || 3.6; const buffer = new ArrayBuffer(44 + count * 2), view = new DataView(buffer); const write = (o,t) => { for(let i=0;i document.getElementById(id); const defaultAvatar = (seed='flikzee') => `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(seed)}`; const getUsername = (user) => (user?.displayName || 'creator').trim().toLowerCase().replace(/[^a-z0-9_]+/g,'_').replace(/^_+|_+$/g,'').slice(0,24) || 'creator'; const formatCount = (n=0) => { const x = Number(n) || 0; if (x >= 1000000) return (x/1000000).toFixed(x >= 10000000 ? 0 : 1) + 'M'; if (x >= 1000) return (x/1000).toFixed(x >= 10000 ? 0 : 1) + 'K'; return String(x); }; const escapeHtml = (s='') => String(s).replace(/[&<>"']/g, ch => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch])); const timestampMillis = (value) => value?.toMillis ? value.toMillis() : (value?.seconds ? value.seconds * 1000 : 0); const safeUrl = (url, fallback='') => typeof url === 'string' && /^https?:\/\//i.test(url) ? url : fallback; function normalizeHashtags(raw=''){ const parts=String(raw||'').split(/[\s,]+/).map(x=>x.trim()).filter(Boolean); const out=[]; const seen=new Set(); parts.forEach(x=>{let tag=x.startsWith('#')?x:'#'+x; tag=tag.replace(/[^#\p{L}\p{N}_]+/gu,''); if(tag.length>1){const key=tag.toLowerCase();if(!seen.has(key)){seen.add(key);out.push(tag);}}}); return out; } function renderHashtagHtml(text='', hashtags=[]){ let html=escapeHtml(text); html=html.replace(/#[\p{L}\p{N}_]+/gu,m=>`${m}`); if(hashtags?.length){ const present=new Set((String(text||'').match(/#[\p{L}\p{N}_]+/gu)||[]).map(x=>x.toLowerCase())); const extra=hashtags.filter(h=>h && !present.has(String(h).toLowerCase())).map(h=>`${escapeHtml(h.startsWith('#')?h:'#'+h)}`).join(' '); if(extra) html += ' '+extra; } return html; } function renderHashtagChips(raw,containerId){ const c=$(containerId); if(!c)return; c.innerHTML=normalizeHashtags(raw).map(h=>`${escapeHtml(h)}`).join(''); } window.handleHashtagInput=(value)=>renderHashtagChips(value,'hashtagPreview'); window.renderEditHashtagPreview=(value)=>renderHashtagChips(value,'editHashtagPreview'); function authErrorMessage(err) { const map = { 'auth/email-already-in-use':'This email is already registered.', 'auth/invalid-email':'Please enter a valid email address.', 'auth/weak-password':'Password is too weak. Use at least 6 characters.', 'auth/invalid-credential':'Email/password is incorrect.', 'auth/user-not-found':'No account was found for this email.', 'auth/popup-closed-by-user':'Google sign-in was cancelled.', 'auth/popup-blocked':'Your browser blocked the Google sign-in popup.', 'auth/operation-not-allowed':'Enable Google sign-in in Firebase Authentication first.' }; return map[err?.code] || err?.message || 'Something went wrong. Please try again.'; } function showAuthPanel(mode='login') { $('authModal').style.display = 'flex'; document.body.classList.add('modal-open'); $('loginFormWrap').style.display = mode === 'login' ? 'block' : 'none'; $('signupFormWrap').style.display = mode === 'signup' ? 'block' : 'none'; $('verificationWrap').style.display = mode === 'verify' ? 'block' : 'none'; $('authTitle').innerText = mode === 'signup' ? 'Create FlikZee Account' : mode === 'verify' ? 'Email Verification' : 'Sign in to FlikZee'; } window.toggleAuth = showAuthPanel; window.closeAuthModal = () => { $('authModal').style.display = 'none'; document.body.classList.remove('modal-open'); }; async function ensureUserDoc(user, extra={}) { if (!user) return null; const userRef = doc(db, 'users', user.uid); const existing = await getDoc(userRef); const existingData = existing.exists() ? existing.data() : {}; const base = { uid: user.uid, displayName: existingData.displayName || user.displayName || extra.fullName || 'Creator', username: existingData.username || extra.username || getUsername(user), avatar: existingData.avatar || user.photoURL || defaultAvatar(user.uid), bio: existingData.bio || extra.bio || 'FlikZee Creator 🎬', email: user.email || existingData.email || '', dob: existingData.dob || extra.dob || '', followers: Array.isArray(existingData.followers) ? existingData.followers : [], following: Array.isArray(existingData.following) ? existingData.following : [], totalViews: Number(existingData.totalViews) || 0, coins: Number(existingData.coins) || 50, rewardPoints: Number(existingData.rewardPoints) || 0 }; if (!existing.exists()) base.createdAt = serverTimestamp(); await setDoc(userRef, base, { merge:true }); window.profileCache[user.uid] = { ...existingData, ...base }; return userRef; } async function activateVerifiedUser(user) { window.currentUser = user; window.userLoggedIn = true; $('authModal').style.display = 'none'; document.body.classList.remove('modal-open'); try { await ensureUserDoc(user); } catch(e) { console.error('User profile sync:', e); } startNotificationListener(user.uid); startWalletListeners(user.uid); $('nav-profile-btn').querySelector('.nav-text').innerText = 'Profile'; renderLiveHub(); updateAdminVisibility(); } onAuthStateChanged(auth, async (user) => { if (!user) { window.currentUser = null; window.userLoggedIn = false; if (notificationUnsubscribe) { notificationUnsubscribe(); notificationUnsubscribe = null; } notificationCache = []; if(walletUnsubscribe){walletUnsubscribe();walletUnsubscribe=null;} if(walletTxUnsubscribe){walletTxUnsubscribe();walletTxUnsubscribe=null;} $('inboxBadge').style.display = 'none'; if($('adminCenterSetting')) $('adminCenterSetting').style.display='none'; return; } window.currentUser = user; if (user.emailVerified) { await activateVerifiedUser(user); authUserBootstrapped = true; return; } window.userLoggedIn = false; $('verificationEmail').innerText = user.email || ''; showAuthPanel('verify'); }); window.handleGoogleLogin = async () => { try { const result = await signInWithPopup(auth, googleProvider); const user = result.user; await ensureUserDoc(user, { fullName:user.displayName || 'Creator' }); showToastMsg(`Welcome ${user.displayName || 'to FlikZee'}!`); } catch(err) { alert(authErrorMessage(err)); } }; window.handleSignup = async (e) => { e.preventDefault(); const fullName = $('regName').value.trim(); const dob = $('regDob').value; const email = $('regEmail').value.trim(); const password = $('regPassword').value; const password2 = $('regPassword2').value; if (password !== password2) return alert('Passwords do not match.'); if (!fullName || !dob || !email || !password) return alert('Please complete all fields.'); const btn = $('signupBtn'); btn.disabled = true; btn.innerText = 'Creating...'; try { const cred = await createUserWithEmailAndPassword(auth, email, password); await updateProfile(cred.user, { displayName: fullName }); await ensureUserDoc(cred.user, { fullName, dob, username:getUsername({displayName:fullName}), bio:'FlikZee Creator 🎬' }); await sendEmailVerification(cred.user); $('verificationEmail').innerText = email; showAuthPanel('verify'); showToastMsg('Verification email sent.'); } catch(err) { alert(authErrorMessage(err)); } finally { btn.disabled = false; btn.innerText = 'Create Account'; } }; window.resendVerification = async () => { const user = auth.currentUser; if (!user) return showAuthPanel('login'); try { await sendEmailVerification(user); showToastMsg('Verification email sent again.'); } catch(err) { alert(authErrorMessage(err)); } }; window.checkVerificationStatus = async () => { const user = auth.currentUser; if (!user) return showAuthPanel('login'); try { await reload(user); if (auth.currentUser.emailVerified) { await activateVerifiedUser(auth.currentUser); showToastMsg('Email verified successfully!'); } else { showToastMsg('Email is not verified yet. Please open the verification link first.'); } } catch(err) { alert(authErrorMessage(err)); } }; window.signOutForAuth = async () => { try { await signOut(auth); showAuthPanel('login'); } catch(err) { console.error(err); } }; window.handleLogin = async (e) => { e.preventDefault(); const email = $('logEmail').value.trim(); const password = $('logPassword').value; const btn = $('loginBtn'); btn.disabled = true; btn.innerText = 'Signing in...'; try { const cred = await signInWithEmailAndPassword(auth, email, password); await reload(cred.user); if (!cred.user.emailVerified) { window.currentUser = cred.user; window.userLoggedIn = false; $('verificationEmail').innerText = email; showAuthPanel('verify'); showToastMsg('Please verify your email before continuing.'); } } catch(err) { alert(authErrorMessage(err)); } finally { btn.disabled = false; btn.innerText = 'Sign In'; } }; window.logoutApp = async () => { try { if (notificationUnsubscribe) { notificationUnsubscribe(); notificationUnsubscribe = null; } if (walletUnsubscribe) { walletUnsubscribe(); walletUnsubscribe = null; } if (walletTxUnsubscribe) { walletTxUnsubscribe(); walletTxUnsubscribe = null; } await signOut(auth); $('settingsMenu').classList.remove('open'); switchTab('home'); showToastMsg('Logged out.'); } catch(err) { alert(authErrorMessage(err)); } }; function startNotificationListener(uid) { if (notificationUnsubscribe) notificationUnsubscribe(); const notifRef = collection(db, 'users', uid, 'notifications'); const q = query(notifRef, orderBy('createdAt','desc'), limit(100)); notificationUnsubscribe = onSnapshot(q, snapshot => { notificationCache = snapshot.docs.map(d => ({id:d.id, ...d.data()})); renderNotifications(notificationCache); const unread = notificationCache.filter(n => !n.read).length; const b = $('inboxBadge'); b.innerText = unread > 99 ? '99+' : String(unread); b.style.display = unread ? 'flex' : 'none'; }, err => console.error('Notification listener:', err)); } function formatNotificationTime(value) { const ms = timestampMillis(value); if (!ms) return 'now'; const diff = Math.max(0, Date.now()-ms); const mins = Math.floor(diff/60000); if (mins < 1) return 'now'; if (mins < 60) return `${mins}m`; const hrs = Math.floor(mins/60); if (hrs < 24) return `${hrs}h`; const days = Math.floor(hrs/24); return `${days}d`; } function renderNotifications(notifs) { const container = $('notificationsContainer'); if (!notifs.length) { container.innerHTML = '
No notifications yet. Likes, comments, tips and follows will appear here in real-time.
'; return; } const frag = document.createDocumentFragment(); notifs.forEach(n => { const row = document.createElement('div'); row.className = 'inbox-item' + (n.read ? '' : ' notification-unread'); const avatar = document.createElement('img'); avatar.className = 'inbox-avatar'; avatar.src = safeUrl(n.fromUserAvatar, defaultAvatar(n.fromUserId || 'user')); avatar.alt=''; avatar.onclick = () => { if(n.fromUserId) openCreatorProfile(n.fromUserId); }; const wrap = document.createElement('div'); wrap.className='inbox-item-text'; const name = document.createElement('h4'); name.innerText='@' + (n.fromUsername || 'flikzee_user'); name.onclick = () => { if(n.fromUserId) openCreatorProfile(n.fromUserId); }; const msg = document.createElement('p'); msg.innerText=n.message || 'You have a new notification.'; const time = document.createElement('div'); time.className='notification-time'; time.innerText=formatNotificationTime(n.createdAt); wrap.append(name,msg,time); row.append(avatar,wrap); frag.append(row); }); container.innerHTML=''; container.appendChild(frag); } async function markNotificationsRead() { if (!window.userLoggedIn || !notificationCache.some(n => !n.read)) return; try { await Promise.all(notificationCache.filter(n=>!n.read).slice(0,100).map(n => updateDoc(doc(db,'users',window.currentUser.uid,'notifications',n.id), {read:true}))); } catch(err) { console.error('Mark notifications:', err); } } function startCategoryListener() { if (categoryUnsubscribe) return; const q = query(collection(db,'categories'), orderBy('order','asc'), limit(30)); categoryUnsubscribe = onSnapshot(q, snapshot => { if (snapshot.empty) { window.categoryData = DEFAULT_CATEGORIES.slice(); } else { window.categoryData = snapshot.docs.map(d => ({id:d.id, ...d.data()})); } renderCategories(); renderDiscoverSearch($('searchInput').value); }, err => { console.warn('Categories listener unavailable, using defaults:', err); window.categoryData = DEFAULT_CATEGORIES.slice(); renderCategories(); }); } window.loadFeed = function() { if (feedUnsubscribe) return; const q = query(collection(db,'videos'), orderBy('timestamp','desc'), limit(FEED_LIMIT)); feedUnsubscribe = onSnapshot(q, snapshot => { const docs = snapshot.docs.map(d => ({id:d.id, ...d.data()})); if (snapshot.empty) { window.allVideos = []; feedInitialized = true; usingFallbackFeed = false; renderEmptyFeed(); return; } if (usingFallbackFeed || !feedInitialized) { $('feed').innerHTML=''; usingFallbackFeed=false; feedInitialized=true; window.allVideos=docs; docs.forEach(v=>appendVideoToFeed(v)); } else { const byId = new Map(window.allVideos.map(v=>[v.id,v])); snapshot.docChanges().forEach(change => { const v = {id:change.doc.id, ...change.doc.data()}; if (change.type === 'removed') { byId.delete(change.doc.id); const old = $('feed').querySelector(`[data-id="${CSS.escape(change.doc.id)}"]`); old?.remove(); return; } byId.set(change.doc.id, v); if (change.type === 'added') insertVideoCardAt(v, change.newIndex); else if (change.type === 'modified') { const w = $('feed').querySelector(`[data-id="${CSS.escape(change.doc.id)}"]`); if (!w) insertVideoCardAt(v, change.newIndex); else updateVideoCardUI(v,w); if (w && change.newIndex !== change.oldIndex) moveVideoCard(w,change.newIndex); } }); window.allVideos = snapshot.docs.map(d=>({id:d.id,...d.data()})); } setupObserver(); renderDiscoverSearch($('searchInput').value); }, err => console.error('Feed listener:', err)); }; function renderEmptyFeed() { const feed = $('feed'); feed.innerHTML = `
🎬

Welcome to FlikZee

Be the first creator to publish a short video on FlikZee and earn creator rewards.

`; } function appendVideoToFeed(video) { insertVideoCardAt(video, $('feed').children.length); } function insertVideoCardAt(video, index) { const feed = $('feed'); const existing = feed.querySelector(`[data-id="${CSS.escape(video.id)}"]`); if (existing) { updateVideoCardUI(video,existing); return existing; } const wrapper = document.createElement('div'); wrapper.className='video-wrapper'; wrapper.dataset.id=video.id; const mediaUrl=safeUrl(video.mediaUrl || video.videoUrl); let mediaEl; if((video.mediaType||'video')==='text'){ mediaEl=document.createElement('div'); mediaEl.style.cssText='width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:32px;text-align:center;font-size:1.35rem;font-weight:800;line-height:1.5;background:radial-gradient(circle at center,#1f1024,#080808);color:#fff;'; mediaEl.innerText=video.caption||'FlikZee Post'; } else if((video.mediaType||'video')==='image'){ mediaEl=document.createElement('img'); mediaEl.src=mediaUrl; mediaEl.alt=video.title||'FlikZee post'; mediaEl.loading='lazy'; mediaEl.decoding='async'; mediaEl.style.cssText='width:100%;height:100%;object-fit:cover;'; mediaEl.style.filter=FILTERS[video.videoFilter] || FILTERS.Original; mediaEl.addEventListener('click',()=>openSingleMedia(mediaUrl,video.id)); } else { mediaEl=document.createElement('video'); mediaEl.src=mediaUrl; mediaEl.playsInline=true; mediaEl.loop=true; mediaEl.preload='none'; mediaEl.muted=window.isGlobalMuted; mediaEl.style.filter=FILTERS[video.videoFilter] || FILTERS.Original; mediaEl.addEventListener('click', () => { if (mediaEl.paused) { pauseAllFeedVideosExcept(mediaEl); mediaEl.play().catch(()=>{}); if(wrapper._soundAudio && !window.isGlobalMuted) wrapper._soundAudio.play().catch(()=>{}); wrapper.querySelector('.play-pause-icon').classList.remove('show'); } else { mediaEl.pause(); if(wrapper._soundAudio){wrapper._soundAudio.pause(); wrapper._soundAudio.currentTime=0;} wrapper.querySelector('.play-pause-icon').classList.add('show'); } }); mediaEl.addEventListener('dblclick', (e) => { e.preventDefault(); const likeBtn=wrapper.querySelector('[data-action="like"]'); toggleLike(video.id,likeBtn); }); } let soundAudio=null; const resolvedSoundUrl=video.soundUrl||((video.soundId||video.soundCategory)?getSoundUrl(video.soundId,video.soundCategory):''); if(resolvedSoundUrl && video.soundCategory !== 'Original'){ soundAudio=document.createElement('audio'); soundAudio.src=safeUrl(resolvedSoundUrl); soundAudio.loop=true; soundAudio.preload='none'; soundAudio.volume=.78; soundAudio.muted=window.isGlobalMuted; soundAudio.style.display='none'; wrapper._soundAudio=soundAudio; } const openCreatorHandler = (e) => { e.preventDefault(); e.stopPropagation(); if (video.userId) openCreatorProfile(video.userId); }; const playIcon=document.createElement('div'); playIcon.className='play-pause-icon'; playIcon.innerText='▶'; const overlay=document.createElement('div'); overlay.className='overlay-ui'; const info=document.createElement('div'); info.className='text-info'; const user=document.createElement('span'); user.className='username'; user.innerHTML = '@' + escapeHtml(video.username||'creator') + ((video.isBoosted || video.sponsoredUrl) ? ' ' : ''); user.onclick = openCreatorHandler; const title=document.createElement('div'); title.className='media-title'; title.innerText=video.title||''; title.style.display=video.title?'block':'none'; const desc=document.createElement('p'); desc.className='description'; desc.innerHTML=renderHashtagHtml(video.caption||'', video.hashtags||[]); const music=document.createElement('div'); music.className='music-bar'; music.innerText='🎵 '+(video.soundName || ('Original Sound - @'+(video.username||'creator'))); info.append(user,title,desc,music); if (video.sponsoredUrl) { const cta = document.createElement('a'); cta.className = 'sponsored-cta-btn'; cta.href = safeUrl(video.sponsoredUrl, '#'); cta.target = '_blank'; cta.rel = 'noopener noreferrer'; cta.innerText = 'Visit Partner Link ↗'; cta.onclick = (e) => e.stopPropagation(); info.appendChild(cta); } const actions=document.createElement('div'); actions.className='action-buttons'; const profileWrap=document.createElement('div'); profileWrap.className='profile-avatar-wrap'; profileWrap.title='Open creator profile'; profileWrap.onclick = openCreatorHandler; const profileImg=document.createElement('img'); profileImg.className='profile-img-btn'; profileImg.src=safeUrl(video.userAvatar,defaultAvatar(video.username||video.userId)); profileImg.alt=''; profileImg.loading='lazy'; profileImg.onclick = openCreatorHandler; const followBadge=document.createElement('div'); followBadge.className='profile-follow-badge'; followBadge.innerText='+'; profileWrap.append(profileImg,followBadge); const commentTotal = Number(video.commentsCount) || 0; const likeBtn=createActionButton('like','♥',video.likes?.length||0,()=>toggleLike(video.id,likeBtn)); const commentBtn=createActionButton('comment','💬',commentTotal,()=>openCommentsModal(video.id)); const shareBtn=createActionButton('share','↗',video.shares||0,()=>shareVideo(video.id), 'share-btn'); actions.append(profileWrap,likeBtn,commentBtn,shareBtn); overlay.append(info,actions); const viewPill=document.createElement('div'); viewPill.className='video-view-pill'; viewPill.innerText=`${formatCount(video.views||0)} views`; overlay.appendChild(viewPill); if(window.currentUser?.uid===video.userId){ const more=document.createElement('button'); more.type='button'; more.className='media-more-btn'; more.innerText='⋯'; more.title='More options'; const menu=document.createElement('div'); menu.className='video-menu'; menu.innerHTML=''; more.onclick=(e)=>{e.stopPropagation();document.querySelectorAll('.video-menu.open').forEach(x=>x.classList.remove('open'));menu.classList.toggle('open');}; const [editBtn,shareMenuBtn,deleteBtn]=menu.children; editBtn.onclick=(e)=>{e.stopPropagation();openEditVideo(video.id);}; shareMenuBtn.onclick=(e)=>{e.stopPropagation();menu.classList.remove('open');shareVideo(video.id);}; deleteBtn.onclick=(e)=>{e.stopPropagation();menu.classList.remove('open');deleteVideo(video.id);}; wrapper.append(more,menu); } wrapper.append(mediaEl,playIcon,overlay); if(soundAudio) wrapper.appendChild(soundAudio); updateVideoCardUI(video,wrapper); const ref=feed.children[index] || null; if (ref) feed.insertBefore(wrapper,ref); else feed.appendChild(wrapper); return wrapper; } function createActionButton(type, symbol, count, handler, extraClass='') { const btn=document.createElement('button'); btn.className='action-btn '+extraClass; btn.type='button'; btn.dataset.action=type; btn.onclick=(e)=>{ e.stopPropagation(); handler(); }; const circle=document.createElement('div'); circle.className='action-icon-circle'; if(type==='like') circle.innerHTML=''; else circle.innerText=symbol; const span=document.createElement('span'); span.style='font-size:12px;font-weight:700;'; span.innerText=formatCount(count); btn.append(circle,span); return btn; } function updateVideoCardUI(video, wrapper) { const likeBtn=wrapper.querySelector('[data-action="like"]'); const commentBtn=wrapper.querySelector('[data-action="comment"]'); const shareBtn=wrapper.querySelector('[data-action="share"]'); const currentUid=window.currentUser?.uid; likeBtn?.classList.toggle('liked', !!currentUid && (video.likes||[]).includes(currentUid)); if(likeBtn) likeBtn.querySelector('span').innerText=formatCount((video.likes||[]).length); if(commentBtn) commentBtn.querySelector('span').innerText=formatCount(Number(video.commentsCount)||0); if(shareBtn) shareBtn.querySelector('span').innerText=formatCount(video.shares||0); const views=wrapper.querySelector('.video-view-pill'); if(views) views.innerText=`${formatCount(video.views||0)} views`; const media=wrapper.querySelector('video, img'); if(media){ const nextUrl = safeUrl(video.mediaUrl || video.videoUrl, media.src); if (media.src !== nextUrl && nextUrl) media.src = nextUrl; media.style.filter=FILTERS[video.videoFilter] || FILTERS.Original; if(media.tagName==='VIDEO') media.muted=window.isGlobalMuted; } const openCreatorHandler = (e) => { e.preventDefault(); e.stopPropagation(); if (video.userId) openCreatorProfile(video.userId); }; const profileWrap=wrapper.querySelector('.profile-avatar-wrap'); if(profileWrap) profileWrap.onclick=openCreatorHandler; const img=wrapper.querySelector('.profile-img-btn'); if(img){ img.src=safeUrl(video.userAvatar,defaultAvatar(video.username||video.userId)); img.onclick=openCreatorHandler; } const user=wrapper.querySelector('.username'); if(user){ user.innerHTML = '@' + escapeHtml(video.username||'creator') + ((video.isBoosted || video.sponsoredUrl) ? ' ' : ''); user.onclick = openCreatorHandler; } const title=wrapper.querySelector('.media-title'); if(title){title.innerText=video.title||'';title.style.display=video.title?'block':'none';} const desc=wrapper.querySelector('.description'); if(desc) desc.innerHTML=renderHashtagHtml(video.caption||'', video.hashtags||[]); const music=wrapper.querySelector('.music-bar'); if(music) music.innerText='🎵 '+(video.soundName || ('Original Sound - @'+(video.username||'creator'))); } function moveVideoCard(wrapper,index) { const feed=$('feed'); const nodes=Array.from(feed.children).filter(n=>n!==wrapper); const ref=nodes[index] || null; if(ref) feed.insertBefore(wrapper,ref); else feed.appendChild(wrapper); } async function recordVideoView(videoId, video){ if(!videoId || !window.userLoggedIn || !window.currentUser) return; if(!video || video.userId===window.currentUser.uid) return; if(window.sessionViewedVideos.has(videoId)) return; const day=new Date().toISOString().slice(0,10); const storageKey=`flikzee_view_${window.currentUser.uid}_${videoId}_${day}`; if(localStorage.getItem(storageKey)) { window.sessionViewedVideos.add(videoId); return; } window.sessionViewedVideos.add(videoId); localStorage.setItem(storageKey,'1'); try { await updateDoc(doc(db,'videos',videoId),{views:increment(1)}); if(video.userId) { await setDoc(doc(db,'users',video.userId),{totalViews:increment(1), rewardPoints:increment(1)},{merge:true}); } } catch(err){ console.warn('View count update:',err); window.sessionViewedVideos.delete(videoId); localStorage.removeItem(storageKey); } } function pauseAllFeedVideosExcept(activeVideoEl = null) { document.querySelectorAll('#feed .video-wrapper').forEach(w => { const v = w.querySelector('video'); if (v && v !== activeVideoEl) { if (!v.paused) v.pause(); v.preload = 'none'; } if (w._soundAudio && (!activeVideoEl || w.querySelector('video') !== activeVideoEl)) { w._soundAudio.pause(); w._soundAudio.currentTime = 0; } }); } let feedObserver; function setupObserver() { if (feedObserver) feedObserver.disconnect(); feedObserver=new IntersectionObserver(entries=>entries.forEach(entry=>{ const media=entry.target.querySelector('video, img'); const audio=entry.target._soundAudio; if(entry.isIntersecting){ if(media && media.tagName==='VIDEO'){ pauseAllFeedVideosExcept(media); media.preload = 'metadata'; media.muted = window.isGlobalMuted; media.play().catch(()=>{}); entry.target.querySelector('.play-pause-icon')?.classList.remove('show'); } else { pauseAllFeedVideosExcept(null); } if(audio && !window.isGlobalMuted) audio.play().catch(()=>{}); const id=entry.target.dataset.id; if(id){ const currentVideo=window.allVideos.find(x=>x.id===id); recordVideoView(id,currentVideo); } } else { if(media && media.tagName==='VIDEO'){ media.pause(); media.currentTime=0; media.preload='none'; } if(audio){audio.pause();audio.currentTime=0;} } }),{threshold:0.60}); document.querySelectorAll('#feed .video-wrapper').forEach(w=>feedObserver.observe(w)); } function spawnHeartEffect(btn){ if(!btn)return; const r=btn.getBoundingClientRect(); const burst=document.createElement('div'); burst.className='heart-burst'; burst.textContent='❤'; burst.style.left=(r.left+r.width/2)+'px'; burst.style.top=(r.top+r.height/2)+'px'; document.body.appendChild(burst); setTimeout(()=>burst.remove(),500); for(let i=0;i<4;i++){const h=document.createElement('div');h.className='heart-fly';h.textContent=i%2?'💗':'❤';h.style.left=(r.left+r.width/2)+'px';h.style.top=(r.top+4)+'px';h.style.setProperty('--dx',`${(Math.random()*90-45).toFixed(0)}px`);h.style.setProperty('--rot',`${(Math.random()*50-25).toFixed(0)}deg`);document.body.appendChild(h);setTimeout(()=>h.remove(),950);} } window.toggleLike=async (videoId,btn)=>{ if(!window.userLoggedIn){showAuthPanel('login');return;} const uid=window.currentUser.uid; const video=window.allVideos.find(v=>v.id===videoId); if(!video) return; const liked=(video.likes||[]).includes(uid); spawnHeartEffect(btn); const nextLikes=liked?(video.likes||[]).filter(x=>x!==uid):[...(video.likes||[]),uid]; video.likes=nextLikes; const wrapper=$('feed').querySelector(`[data-id="${CSS.escape(videoId)}"]`); if(wrapper) updateVideoCardUI(video,wrapper); try{ await updateDoc(doc(db,'videos',videoId),{likes:liked?arrayRemove(uid):arrayUnion(uid)}); if(!liked && video.userId && video.userId!==uid){ await setDoc(doc(db,'users',video.userId),{rewardPoints:increment(2)},{merge:true}); await addNotification(video.userId,{fromUserId:uid,fromUsername:getUsername(window.currentUser),fromUserAvatar:window.currentUser.photoURL||'',message:`liked your video: "${(video.caption||'').slice(0,40)}"`}); } }catch(err){ video.likes=liked?nextLikes.concat(uid):nextLikes.filter(x=>x!==uid); if(wrapper) updateVideoCardUI(video,wrapper); console.error('Like error:',err); showToastMsg('Like failed. Try again.'); } }; async function addNotification(targetUid,payload){ if(!targetUid || !window.currentUser || targetUid===window.currentUser.uid) return; await addDoc(collection(db,'users',targetUid,'notifications'),{...payload,read:false,createdAt:serverTimestamp()}); } function syncCommentCountOnFeedUI(videoId, newCount) { const countNum = Math.max(0, Number(newCount) || 0); const video = window.allVideos.find(v => v.id === videoId); if (video) video.commentsCount = countNum; const wrapper = $('feed').querySelector(`[data-id="${CSS.escape(videoId)}"]`); if (wrapper) { const commentSpan = wrapper.querySelector('[data-action="comment"] span'); if (commentSpan) commentSpan.innerText = formatCount(countNum); } if (window.activeVideoIdForComment === videoId) { $('commentHeaderTitle').innerText = `Comments (${countNum})`; } } window.openCommentsModal=async(videoId)=>{ window.activeVideoIdForComment=videoId; if(commentsUnsubscribe){commentsUnsubscribe();commentsUnsubscribe=null;} $('commentsModal').classList.add('open'); document.body.classList.add('modal-open'); const video=window.allVideos.find(v=>v.id===videoId); $('commentHeaderTitle').innerText=`Comments (${Number(video?.commentsCount)||0})`; const list=$('commentsList'); list.innerHTML='
Loading comments...
'; const commRef=collection(db,'videos',videoId,'comments'); const q=query(commRef,orderBy('createdAt','desc'),limit(100)); commentsUnsubscribe=onSnapshot(q,snapshot=>{ const liveCount = snapshot.size; syncCommentCountOnFeedUI(videoId, liveCount); if(snapshot.empty){list.innerHTML='
No comments yet. Be first!
';return;} const frag=document.createDocumentFragment(); snapshot.docs.forEach(ds=>{ const c=ds.data(); const item=document.createElement('div'); item.className='comment-item'; const img=document.createElement('img'); img.className='comment-avatar'; img.src=safeUrl(c.userAvatar,defaultAvatar(c.userId)); img.alt=''; img.onclick=()=>{ if(c.userId){ closeModal('commentsModal'); openCreatorProfile(c.userId); } }; const content=document.createElement('div'); content.className='comment-content'; const h=document.createElement('h4'); h.innerText='@'+(c.username||'user'); h.onclick=()=>{ if(c.userId){ closeModal('commentsModal'); openCreatorProfile(c.userId); } }; const p=document.createElement('p'); p.innerText=c.text||''; content.append(h,p); item.append(img,content); frag.append(item); }); list.innerHTML='';list.appendChild(frag); },err=>{list.innerHTML='
Could not load comments.
';console.error(err);}); }; window.postLiveComment=async()=>{ if(!window.userLoggedIn){showAuthPanel('login');return;} const text=$('newCommentText').value.trim(); const videoId=window.activeVideoIdForComment; if(!text||!videoId)return; const uid=window.currentUser.uid; const video=window.allVideos.find(v=>v.id===videoId); const previousCount = Number(video?.commentsCount) || 0; syncCommentCountOnFeedUI(videoId, previousCount + 1); $('newCommentText').disabled=true; try{ const userDoc = window.profileCache[uid] || {}; const uname = userDoc.username || getUsername(window.currentUser); const uavatar = userDoc.avatar || window.currentUser.photoURL || defaultAvatar(uid); const batch=writeBatch(db); const commentRef=doc(collection(db,'videos',videoId,'comments')); batch.set(commentRef,{userId:uid,username:uname,userAvatar:uavatar,text,createdAt:serverTimestamp()}); batch.update(doc(db,'videos',videoId),{commentsCount:increment(1)}); if(video?.userId && video.userId!==uid){ const notifRef=doc(collection(db,'users',video.userId,'notifications')); batch.set(notifRef,{fromUserId:uid,fromUsername:uname,fromUserAvatar:uavatar,message:`commented on your video: "${text.slice(0,40)}"`,read:false,createdAt:serverTimestamp()}); } await batch.commit(); $('newCommentText').value=''; }catch(err){ syncCommentCountOnFeedUI(videoId, previousCount); console.error('Comment error:',err); showToastMsg('Comment failed. Try again.'); } finally{$('newCommentText').disabled=false;} }; window.shareVideo=async(videoId)=>{ const video=window.allVideos.find(v=>v.id===videoId); const url=`${location.origin}${location.pathname}?video=${encodeURIComponent(videoId)}`; try{ if(navigator.share) await navigator.share({title:'FlikZee Video',text:video?.caption||'Watch this on FlikZee (Flikzee.com)',url}); else { await navigator.clipboard.writeText(url); showToastMsg('FlikZee video link copied!'); } }catch(err){ if(err?.name!=='AbortError'){ try{await navigator.clipboard.writeText(url);showToastMsg('FlikZee video link copied!');}catch(_){} } } try{ if(videoId) await updateDoc(doc(db,'videos',videoId),{shares:increment(1)}); }catch(err){console.warn('Share counter:',err);} }; window.closeSingleVideo=()=>{ document.querySelectorAll('#singleVideoWrapper video,#singleVideoWrapper audio').forEach(m=>{try{m.pause();m.src='';}catch(_){}}); $('singleVideoWrapper').innerHTML=''; $('singleVideoView').style.display='none'; }; function startProfileListeners(uid){ if(profileUnsubscribe)profileUnsubscribe(); if(profileVideosUnsubscribe)profileVideosUnsubscribe(); profileUnsubscribe=null; profileVideosUnsubscribe=null; const userRef=doc(db,'users',uid); profileUnsubscribe=onSnapshot(userRef,snap=>{ const p=snap.exists()?snap.data():{uid,displayName:'Creator',username:uid.slice(0,10),avatar:defaultAvatar(uid),bio:'FlikZee Creator 🎬',followers:[],following:[],dob:''}; window.profileCache[uid]=p; renderProfileHeader(p); },err=>console.error('Profile listener:',err)); const vq=query(collection(db,'videos'),where('userId','==',uid),limit(PROFILE_VIDEO_LIMIT)); profileVideosUnsubscribe=onSnapshot(vq,snap=>{ currentProfileVideos=snap.docs.map(d=>({id:d.id,...d.data()})).sort((a,b)=>timestampMillis(b.timestamp)-timestampMillis(a.timestamp)); renderProfileVideos(currentProfileVideos); const likes=currentProfileVideos.reduce((sum,v)=>sum+(v.likes?.length||0),0); const views=currentProfileVideos.reduce((sum,v)=>sum+(v.views||0),0); $('statLikes').innerText=formatCount(likes); $('profPostCount').innerText=`${currentProfileVideos.length} posts`; $('profViewCount').innerText=`${formatCount(views)} views`; renderMonetization(currentProfileVideos, window.profileCache[uid] || {}); },err=>console.error('Profile videos listener:',err)); } function renderProfileHeader(p){ $('profTopName').innerText=p.displayName||p.username||'Profile'; $('profDisplayName').innerText=p.displayName||'Creator'; $('profUsername').innerText='@'+(p.username||p.uid||'user'); $('profImage').src=safeUrl(p.avatar,defaultAvatar(p.uid)); $('profBio').innerText=p.bio||''; $('statFollowers').innerText=formatCount(p.followers?.length||0); $('statFollowing').innerText=formatCount(p.following?.length||0); profileIsMine=!!window.currentUser && window.currentUser.uid===p.uid; $('myProfileActions').style.display=profileIsMine?'flex':'none'; $('creatorProfileActions').style.display=profileIsMine?'none':'flex'; $('avatarChangeBtn').style.display=profileIsMine?'flex':'none'; $('profileWalletRow').style.display=profileIsMine?'flex':'none'; if(!profileIsMine){ const following=(p.followers||[]).includes(window.currentUser?.uid); $('followBtn').innerText=following?'Following':'Follow'; $('followBtn').style.background=following?'#171717':''; $('followBtn').style.border=following?'1px solid #333':''; } const dob=p.dob||'Not provided'; const email=profileIsMine ? (window.currentUser?.email || p.email || 'Not provided') : 'Hidden'; $('profileAboutCard').innerHTML=`
Full Name${escapeHtml(p.displayName||'Creator')}
Username@${escapeHtml(p.username||'user')}
Email${escapeHtml(email)}
Date of Birth${escapeHtml(profileIsMine ? dob : 'Hidden')}
Bio${escapeHtml(p.bio||'')}
`; renderMonetization(currentProfileVideos,p); } function renderProfileVideos(videos){ const grid=$('profileVideoGrid'); grid.innerHTML=''; if(!videos.length){grid.innerHTML='
No videos uploaded yet.
';return;} const frag=document.createDocumentFragment(); videos.forEach(v=>{ const item=document.createElement('div'); item.className='grid-item'; item.onclick=()=>openSingleMedia(v.mediaUrl||v.videoUrl,v.id); let media; if((v.mediaType||'video')==='image'){ media=document.createElement('img'); media.src=safeUrl(v.mediaUrl||v.videoUrl); media.alt=v.title||'FlikZee post'; media.loading='lazy'; media.style.cssText='width:100%;height:100%;object-fit:cover;'; } else if((v.mediaType||'video')==='text'){ media=document.createElement('div'); media.style.cssText='width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:8px;text-align:center;font-size:.75rem;font-weight:700;background:#181020;color:#fff;'; media.innerText=(v.caption||'Text').slice(0,60); } else { media=document.createElement('video'); media.src=safeUrl(v.mediaUrl||v.videoUrl); media.muted=true; media.playsInline=true; media.preload='none'; } media.style.filter=FILTERS[v.videoFilter]||FILTERS.Original; const stats=document.createElement('div'); stats.className='grid-item-stats'; stats.innerText=`❤️ ${formatCount(v.likes?.length||0)}`; const views=document.createElement('div'); views.className='video-view-pill'; views.innerText=`${formatCount(v.views||0)} views`; item.append(media,stats,views); if(profileIsMine){ const menuBtn=document.createElement('button'); menuBtn.className='profile-grid-menu'; menuBtn.type='button'; menuBtn.innerText='⋯'; const menu=document.createElement('div'); menu.className='grid-video-menu'; menu.innerHTML=''; menuBtn.onclick=(e)=>{e.stopPropagation();document.querySelectorAll('.grid-video-menu.open').forEach(x=>x.classList.remove('open'));menu.classList.toggle('open');}; const [eb,sb,dbtn]=menu.children; eb.onclick=e=>{e.stopPropagation();openEditVideo(v.id);}; sb.onclick=e=>{e.stopPropagation();shareVideo(v.id);menu.classList.remove('open');}; dbtn.onclick=e=>{e.stopPropagation();deleteVideo(v.id);menu.classList.remove('open');}; item.append(menuBtn,menu); } frag.append(item); }); grid.appendChild(frag); } window.openCreatorProfile=async(uid)=>{ if(!uid)return; window.currentViewingCreatorUid=uid; closeModal('commentsModal'); if($('singleVideoView').style.display==='block') closeSingleVideo(); switchTab('profile',true); startProfileListeners(uid); }; window.openMyProfile=()=>{ if(!window.userLoggedIn){showAuthPanel('login');return;} openCreatorProfile(window.currentUser.uid); }; window.goBackFromProfile=()=>{ switchTab(profileIsMine?'home':lastNonProfileTab); }; window.toggleFollowCreator=async()=>{ if(!window.userLoggedIn){showAuthPanel('login');return;} const targetUid=window.currentViewingCreatorUid; if(!targetUid||targetUid===window.currentUser.uid)return; const target=window.profileCache[targetUid]||{}; const following=(target.followers||[]).includes(window.currentUser.uid); $('followBtn').disabled=true; try{ await setDoc(doc(db,'users',targetUid),{followers:following?arrayRemove(window.currentUser.uid):arrayUnion(window.currentUser.uid)},{merge:true}); await setDoc(doc(db,'users',window.currentUser.uid),{following:following?arrayRemove(targetUid):arrayUnion(targetUid)},{merge:true}); if(!following) await addNotification(targetUid,{fromUserId:window.currentUser.uid,fromUsername:getUsername(window.currentUser),fromUserAvatar:window.currentUser.photoURL||'',message:'started following you'}); showToastMsg(following?'Unfollowed':'Following creator!'); }catch(err){console.error('Follow error:',err);showToastMsg('Follow action failed.');} finally{$('followBtn').disabled=false;} }; window.shareProfile=async()=>{ const uid=window.currentViewingCreatorUid||window.currentUser?.uid; if(!uid)return; const p=window.profileCache[uid]||{}; const url=`${location.origin}${location.pathname}?profile=${encodeURIComponent(uid)}`; try{ if(navigator.share) await navigator.share({title:'FlikZee Profile',text:`Check out @${p.username||'creator'} on FlikZee`,url}); else {await navigator.clipboard.writeText(url);showToastMsg('Profile link copied!');} }catch(err){if(err?.name!=='AbortError')showToastMsg('Could not share profile.');} }; window.switchProfilePanel=(panel)=>{ $('profileTabVideos').classList.toggle('active',panel==='videos'); $('profileTabAbout').classList.toggle('active',panel==='about'); $('profilePanelVideos').classList.toggle('active',panel==='videos'); $('profilePanelAbout').classList.toggle('active',panel==='about'); }; window.openEditProfile=()=>{ if(!window.currentUser)return showAuthPanel('login'); const p=window.profileCache[window.currentUser.uid]||{}; $('editProfileName').value=p.displayName||window.currentUser.displayName||''; $('editProfileUsername').value=p.username||getUsername(window.currentUser); $('editProfileBio').value=p.bio||''; $('editProfileDob').value=p.dob||''; $('editProfileModal').classList.add('open'); document.body.classList.add('modal-open'); $('settingsMenu').classList.remove('open'); }; window.saveProfileEdit=async()=>{ if(!window.currentUser)return; const name=$('editProfileName').value.trim(); const username=$('editProfileUsername').value.trim().toLowerCase().replace(/[^a-z0-9_]+/g,'_').replace(/^_+|_+$/g,'').slice(0,24); const bio=$('editProfileBio').value.trim(); if(!name||!username)return alert('Name and username are required.'); const btn=$('saveProfileBtn'); btn.disabled=true; btn.innerText='Saving...'; try{ await updateProfile(window.currentUser,{displayName:name}); await setDoc(doc(db,'users',window.currentUser.uid),{displayName:name,username,bio,avatar:window.currentUser.photoURL||defaultAvatar(window.currentUser.uid),email:window.currentUser.email||''},{merge:true}); $('editProfileModal').classList.remove('open'); document.body.classList.remove('modal-open'); showToastMsg('Profile updated!'); }catch(err){console.error(err);alert(err.message||'Profile update failed.');} finally{btn.disabled=false;btn.innerText='Save Changes';} }; window.handleAvatarUpload=async(e)=>{ const file=e.target.files?.[0]; if(!file||!window.currentUser)return; if(!file.type.startsWith('image/'))return alert('Please choose an image.'); showToastMsg('Uploading profile picture...'); try{ const formData=new FormData(); formData.append('file',file); formData.append('upload_preset',CLOUDINARY_PRESET); const res=await fetch(CLOUDINARY_IMAGE_URL,{method:'POST',body:formData}); const data=await res.json(); if(!data.secure_url)throw new Error(data.error?.message||'Image upload failed'); await updateProfile(window.currentUser,{photoURL:data.secure_url}); await setDoc(doc(db,'users',window.currentUser.uid),{avatar:data.secure_url},{merge:true}); $('profImage').src=data.secure_url; showToastMsg('Profile picture updated!'); }catch(err){console.error(err);alert(err.message||'Profile picture upload failed.');} finally{e.target.value='';} }; function renderMonetization(videos,p){ const totalViews=(videos||[]).reduce((s,v)=>s+(v.views||0),0); const followers=(p?.followers||[]).length; const rewardPts = Number(p?.rewardPoints) || Math.floor(totalViews / 5); const estEarnings = ((rewardPts * 0.005) + (totalViews * 0.0005)).toFixed(2); const vp=Math.min(100,totalViews/MONETIZATION_REQUIRED_VIEWS*100); const fp=Math.min(100,followers/MONETIZATION_REQUIRED_FOLLOWERS*100); const ready=totalViews>=MONETIZATION_REQUIRED_VIEWS && followers>=MONETIZATION_REQUIRED_FOLLOWERS; $('monetizationContent').innerHTML=`
${ready?'🎉 FlikZee Partner Ready':'FlikZee Creator Rewards & Ad-Share'}
Earn Creator Points from views, likes, tips, and sponsored ad-share. Full Partner threshold: ${MONETIZATION_REQUIRED_VIEWS.toLocaleString()} views and ${MONETIZATION_REQUIRED_FOLLOWERS.toLocaleString()} followers.
${formatCount(rewardPts)}Creator Pts ⭐
$${estEarnings}Est. Bonus 💵
${ready?'ACTIVE':'GROWING'}Ad-Share Status
Views${formatCount(totalViews)} / ${formatCount(MONETIZATION_REQUIRED_VIEWS)}
Followers${formatCount(followers)} / ${formatCount(MONETIZATION_REQUIRED_FOLLOWERS)}
`; } window.openCreatorFund=()=>{ if(!window.userLoggedIn)return showAuthPanel('login'); const p=window.profileCache[window.currentUser.uid]||{}; renderMonetization(currentProfileVideos.length && window.currentViewingCreatorUid===window.currentUser.uid ? currentProfileVideos : (window.allVideos.filter(v=>v.userId===window.currentUser.uid)),p); $('creatorFundModal').classList.add('open'); document.body.classList.add('modal-open'); $('settingsMenu').classList.remove('open'); }; function renderCoinPackages(){ const box=$('coinPackages'); if(!box)return; box.innerHTML=''; COIN_PACKAGES.forEach(pkg=>{ const b=document.createElement('button'); b.className='coin-pack'; b.type='button'; b.innerHTML=`🪙 ${pkg.coins.toLocaleString()} Coins${pkg.price} · Top-Up`; b.onclick=()=>startCoinPurchase(pkg); box.appendChild(b); }); } function updateWalletModalUI(userData = {}) { const coins = Number(userData.coins) || 0; const pts = Number(userData.rewardPoints) || 0; const usd = (pts * 0.005).toFixed(2); if ($('walletCoinBalance')) $('walletCoinBalance').innerText = formatCount(coins); if ($('walletRewardPoints')) $('walletRewardPoints').innerText = formatCount(pts); if ($('walletEstUsd')) $('walletEstUsd').innerText = `$${usd}`; } function startWalletListeners(uid){ if(walletUnsubscribe){walletUnsubscribe();walletUnsubscribe=null;} walletUnsubscribe = onSnapshot(doc(db, 'users', uid), snap => { if (snap.exists()) { const data = snap.data(); window.profileCache[uid] = { ...(window.profileCache[uid] || {}), ...data }; updateWalletModalUI(data); } }, err => console.warn('Wallet sync:', err)); } window.openWallet=()=>{ if(!window.userLoggedIn)return showAuthPanel('login'); const p = window.profileCache[window.currentUser.uid] || {}; updateWalletModalUI(p); $('walletModal').classList.add('open'); document.body.classList.add('modal-open'); }; window.claimDailyReward = async () => { if (!window.userLoggedIn || !window.currentUser) return showAuthPanel('login'); const uid = window.currentUser.uid; const today = new Date().toISOString().slice(0, 10); const key = `flikzee_daily_${uid}_${today}`; if (localStorage.getItem(key)) { return showToastMsg('You already claimed today\'s +25 FlikCoins!'); } try { localStorage.setItem(key, '1'); await setDoc(doc(db, 'users', uid), { coins: increment(25), rewardPoints: increment(5) }, { merge: true }); showToastMsg('🎉 +25 FlikCoins & +5 Creator Pts added!'); } catch (err) { localStorage.removeItem(key); console.error('Daily reward error:', err); showToastMsg('Could not claim reward right now.'); } }; window.sendCreatorTip = async () => { if (!window.userLoggedIn || !window.currentUser) return showAuthPanel('login'); const targetUid = window.currentViewingCreatorUid; if (!targetUid || targetUid === window.currentUser.uid) return showToastMsg('Choose another creator to tip.'); const myData = window.profileCache[window.currentUser.uid] || {}; if ((Number(myData.coins) || 0) < 10) { openWallet(); return showToastMsg('Need at least 10 FlikCoins. Claim daily reward or top-up!'); } try { await setDoc(doc(db, 'users', window.currentUser.uid), { coins: increment(-10) }, { merge: true }); await setDoc(doc(db, 'users', targetUid), { coins: increment(10), rewardPoints: increment(10) }, { merge: true }); await addNotification(targetUid, { fromUserId: window.currentUser.uid, fromUsername: getUsername(window.currentUser), fromUserAvatar: window.currentUser.photoURL || '', message: 'sent you a 10 FlikCoins creator tip! 🪙' }); showToastMsg('🪙 Sent 10 FlikCoins tip to creator!'); } catch (err) { console.error('Tip error:', err); showToastMsg('Could not send tip.'); } }; window.boostMyLatestVideo = async () => { if (!window.userLoggedIn || !window.currentUser) return showAuthPanel('login'); const uid = window.currentUser.uid; const myVideos = window.allVideos.filter(v => v.userId === uid); if (!myVideos.length) return showToastMsg('Upload a video first to boost it on the feed!'); const myData = window.profileCache[uid] || {}; if ((Number(myData.coins) || 0) < 50) { return showToastMsg('Need 50 FlikCoins to boost a video. Claim daily rewards or top-up!'); } try { const targetVideo = myVideos[0]; await setDoc(doc(db, 'users', uid), { coins: increment(-50) }, { merge: true }); await updateDoc(doc(db, 'videos', targetVideo.id), { isBoosted: true }); showToastMsg('🚀 Your latest video is now Sponsored on FlikZee!'); closeModal('walletModal'); } catch (err) { console.error('Boost error:', err); showToastMsg('Boost failed. Try again.'); } }; window.requestCreatorPayout = async () => { if (!window.userLoggedIn || !window.currentUser) return showAuthPanel('login'); const uid = window.currentUser.uid; const myData = window.profileCache[uid] || {}; const pts = Number(myData.rewardPoints) || 0; if (pts < 1000) { return showToastMsg(`Minimum 1,000 Creator Pts required for payout (Current: ${pts} Pts).`); } try { await addDoc(collection(db, 'payments'), { userId: uid, userEmail: window.currentUser.email || '', packageId: 'Creator Payout Request', amount: `$${(pts * 0.005).toFixed(2)}`, status: 'pending_review', createdAt: serverTimestamp() }); showToastMsg('💸 Payout request submitted to FlikZee Admin!'); } catch (err) { console.error('Payout request:', err); showToastMsg('Payout request logged.'); } }; window.startCoinPurchase=async(pkg)=>{ if(!window.userLoggedIn)return showAuthPanel('login'); if(!PAYMENT_API_URL){ try { await addDoc(collection(db, 'payments'), { userId: window.currentUser.uid, userEmail: window.currentUser.email || '', packageId: `${pkg.coins} FlikCoins`, amount: pkg.price, status: 'checkout_requested', createdAt: serverTimestamp() }); } catch (_) {} showToastMsg(`Selected ${pkg.coins} FlikCoins (${pkg.price}) — Merchant checkout ready.`); return; } try{ const token=await window.currentUser.getIdToken(); const res=await fetch(PAYMENT_API_URL,{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},body:JSON.stringify({action:'create_checkout',packageId:pkg.id})}); const data=await res.json(); if(!res.ok||!data.checkoutUrl)throw new Error(data.error||'Could not create payment checkout.'); location.href=data.checkoutUrl; }catch(err){console.error('Coin purchase:',err);alert(err.message||'Payment could not be started.');} }; function renderLiveHub(){ const box=$('liveHubContent'); if(!box) return; box.innerHTML='

FlikZee Live

Live broadcasting is hidden during initial launch phase.

'; } window.openLiveHub=()=>{ showToastMsg('FlikZee Live will unlock in the next phase.'); }; renderCoinPackages(); function populateFilters(){ const row=$('filterRow'); row.innerHTML=''; Object.keys(FILTERS).forEach(name=>{const b=document.createElement('button');b.type='button';b.className='filter-btn'+(name===selectedFilterName?' active':'');b.innerText=name;b.onclick=()=>{selectedFilterName=name;document.querySelectorAll('#filterRow .filter-btn').forEach(x=>x.classList.remove('active'));b.classList.add('active');applyUploadFilter(name);};row.appendChild(b);}); } function populateEditFilters(){ const row=$('editFilterRow'); row.innerHTML=''; Object.keys(FILTERS).forEach(name=>{const b=document.createElement('button');b.type='button';b.className='filter-btn'+(name===selectedEditFilterName?' active':'');b.innerText=name;b.onclick=()=>{selectedEditFilterName=name;row.querySelectorAll('.filter-btn').forEach(x=>x.classList.remove('active'));b.classList.add('active');};row.appendChild(b);}); } function applyUploadFilter(name){ $('uploadPreview').style.filter=FILTERS[name]; $('uploadImagePreview').style.filter=FILTERS[name]; } window.openCameraPicker=()=>{if(!window.userLoggedIn)return showAuthPanel('login');$('videoCameraInput').click();}; window.openGalleryPicker=()=>{if(!window.userLoggedIn)return showAuthPanel('login');$('videoGalleryInput').click();}; window.handleFileSelect=(e)=>{ const file=e.target.files?.[0]; if(!file)return; if(!file.type.startsWith('video/') && !file.type.startsWith('image/')) return alert('Please choose a video or image.'); uploadSelectedFile=file; uploadSelectedMediaType=file.type.startsWith('image/')?'image':'video'; $('fileNameDisplay').innerText=`${file.name} ✔️`; $('fileNameDisplay').style.color='var(--primary)'; const url=URL.createObjectURL(file); if(uploadSelectedMediaType==='image'){ $('uploadPreview').style.display='none'; $('uploadImagePreview').style.display='block'; $('uploadImagePreview').src=url; } else { $('uploadImagePreview').style.display='none'; $('uploadPreview').style.display='block'; $('uploadPreview').src=url; $('uploadPreview').play().catch(()=>{}); } $('uploadPreviewWrap').style.display='block'; applyUploadFilter(selectedFilterName); e.target.value=''; }; function renderSoundCategories(){ const row=$('soundCategoryRow'); if(!row) return; row.innerHTML=''; SOUND_CATEGORIES.forEach(cat=>{ const b=document.createElement('button'); b.type='button'; b.className='sound-cat'+(cat===selectedSoundCategory?' active':''); b.innerText=cat; b.onclick=()=>{selectedSoundCategory=cat;renderSoundCategories();renderSoundLibrary();}; row.appendChild(b); }); } let soundPreviewAudio=null; let soundPreviewTimer=null; window.playSoundPreview = function(soundId){ const sound=SOUND_LIBRARY.find(x=>x.id===soundId); if(!sound)return; const soundUrl=sound.soundUrl||getSoundUrl(sound.id,sound.category); try{ if(soundPreviewAudio){soundPreviewAudio.pause();soundPreviewAudio.currentTime=0;} if(soundPreviewTimer) clearTimeout(soundPreviewTimer); soundPreviewAudio=new Audio(soundUrl); soundPreviewAudio.volume=.85; soundPreviewAudio.play().catch(()=>{}); showToastMsg(`🎵 Playing: ${sound.name}`); soundPreviewTimer=setTimeout(()=>{if(soundPreviewAudio) soundPreviewAudio.pause();},6500); }catch(e){console.warn('Sound preview:',e);} }; window.renderSoundLibrary = function(){ const q=String($('soundSearchInput')?.value||'').trim().toLowerCase(); const list=$('soundList'); if(!list)return; const sounds=SOUND_LIBRARY.filter(s=>(q ? (s.name.toLowerCase().includes(q) || s.category.toLowerCase().includes(q)) : s.category===selectedSoundCategory)); list.innerHTML=''; if(!sounds.length){list.innerHTML='
No sounds found in this category.
';return;} const frag=document.createDocumentFragment(); sounds.forEach(sound=>{ const card=document.createElement('div'); card.className='sound-card'+(selectedSound?.id===sound.id?' selected':''); card.onclick=()=>{selectedSound={...sound};renderSoundLibrary();showToastMsg(`Selected: ${sound.name}`);}; card.innerHTML=`
🎵
${escapeHtml(sound.name)}${escapeHtml(sound.category)} • FlikZee Audio
`; const play=document.createElement('button');play.type='button';play.className='sound-play';play.innerText='▶'; play.onclick=e=>{e.stopPropagation();playSoundPreview(sound.id);}; card.appendChild(play);frag.appendChild(card); }); list.appendChild(frag); }; window.clearSelectedSound=()=>{ selectedSound=null; if(soundPreviewAudio){soundPreviewAudio.pause();soundPreviewAudio.currentTime=0;} renderSoundLibrary(); showToastMsg('Using Original Video Audio'); }; renderCategories(); populateFilters(); populateEditFilters(); renderSoundCategories(); renderSoundLibrary(); window.processUpload=async()=>{ if(!window.userLoggedIn){showAuthPanel('login');return;} if(!uploadSelectedFile)return alert('Please choose a video or image first.'); if(soundPreviewAudio){soundPreviewAudio.pause();soundPreviewAudio.currentTime=0;} const title=$('videoTitle').value.trim(); const desc=$('videoDesc').value.trim(); const hashtags=normalizeHashtags($('videoHashtags').value); const sponsoredUrl=safeUrl(($('videoSponsoredUrl')?.value||'').trim(),''); const btn=$('uploadBtn'); const status=$('uploadStatus'); const fill=$('progressFill'); btn.disabled=true; $('progressBg').style.display='block'; fill.style.width='5%'; status.innerText='Uploading media to FlikZee...'; try{ const endpoint=uploadSelectedMediaType==='image'?CLOUDINARY_IMAGE_URL:CLOUDINARY_URL; const formData=new FormData(); formData.append('file',uploadSelectedFile); formData.append('upload_preset',CLOUDINARY_PRESET); const res=await fetch(endpoint,{method:'POST',body:formData}); const data=await res.json(); if(!data.secure_url)throw new Error(data.error?.message||'Cloudinary upload failed'); fill.style.width='75%'; status.innerText='Publishing post...'; const userDoc=window.profileCache[window.currentUser.uid]||{}; const payload={ userId:window.currentUser.uid, username:userDoc.username||getUsername(window.currentUser), userAvatar:userDoc.avatar||window.currentUser.photoURL||defaultAvatar(window.currentUser.uid), title, caption:desc, hashtags, sponsoredUrl, mediaType:uploadSelectedMediaType, mediaUrl:data.secure_url, likes:[], commentsCount:0, views:0, shares:0, videoFilter:selectedFilterName, soundId:selectedSound?.id||'', soundName:selectedSound?.name||'', soundCategory:selectedSound?.category||'Original', soundUrl:'', timestamp:serverTimestamp() }; if(uploadSelectedMediaType==='video') payload.videoUrl=data.secure_url; await addDoc(collection(db,'videos'),payload); await setDoc(doc(db,'users',window.currentUser.uid),{rewardPoints:increment(10),coins:increment(5)},{merge:true}); fill.style.width='100%'; status.innerText='Posted successfully! (+10 Creator Pts)'; showToastMsg('🎉 Your FlikZee post is live!'); setTimeout(()=>{ switchTab('home'); btn.disabled=false; $('progressBg').style.display='none'; fill.style.width='0'; status.innerText=''; uploadSelectedFile=null; $('videoTitle').value=''; $('videoDesc').value=''; $('videoHashtags').value=''; if($('videoSponsoredUrl')) $('videoSponsoredUrl').value=''; $('hashtagPreview').innerHTML=''; $('fileNameDisplay').innerText='No media selected'; $('fileNameDisplay').style.color='#fff'; $('uploadPreviewWrap').style.display='none'; $('uploadPreview').src=''; $('uploadImagePreview').src=''; selectedFilterName='Original'; selectedSound=null; applyUploadFilter('Original'); populateFilters(); renderSoundLibrary(); },600); }catch(err){console.error('Upload:',err);status.innerText='Upload error: '+(err.message||'Failed');btn.disabled=false;} }; window.openSingleMedia=(url,mediaId=null)=>{ pauseAllFeedVideosExcept(null); const view=$('singleVideoView'); const wrap=$('singleVideoWrapper'); wrap.innerHTML=''; const current=mediaId?window.allVideos.find(x=>x.id===mediaId):null; const type=current?.mediaType||'video'; if(type==='image'){const img=document.createElement('img');img.src=safeUrl(url);img.alt=current?.title||'FlikZee post';img.style.cssText='width:100%;height:100%;object-fit:contain;background:#000;';wrap.appendChild(img);} else if(type==='text'){const d=document.createElement('div');d.style.cssText='width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:32px;text-align:center;font-size:1.35rem;font-weight:800;background:#111;color:#fff;';d.innerText=current?.caption||'';wrap.appendChild(d);} else {const video=document.createElement('video');video.src=safeUrl(url);video.controls=true;video.autoplay=true;video.playsInline=true;video.loop=true;video.style.cssText='width:100%;height:100%;object-fit:cover;';video.muted=window.isGlobalMuted;wrap.appendChild(video);} const singleSoundUrl=current?.soundUrl||((current?.soundId||current?.soundCategory)?getSoundUrl(current.soundId,current.soundCategory):''); if(singleSoundUrl && current?.soundCategory !== 'Original'){const audio=document.createElement('audio');audio.src=safeUrl(singleSoundUrl);audio.loop=true;audio.preload='none';audio.muted=window.isGlobalMuted;audio.style.display='none';audio.volume=.78;wrap.appendChild(audio);if(!window.isGlobalMuted)audio.play().catch(()=>{});} view.style.display='block'; if(mediaId) recordVideoView(mediaId,current); }; window.openSingleVideo=(url,videoId=null)=>openSingleMedia(url,videoId); window.handleSearch=(queryStr)=>renderDiscoverSearch(queryStr); function renderDiscoverSearch(queryStr){ const q=String(queryStr||'').toLowerCase().trim(); const container=$('searchResults'); if(!q){container.innerHTML='';return;} const matchedVideos=window.allVideos.filter(v=>(v.caption||'').toLowerCase().includes(q)||(v.title||'').toLowerCase().includes(q)||(v.username||'').toLowerCase().includes(q)||(v.hashtags||[]).some(h=>String(h).toLowerCase().includes(q))); const creators=[]; const seen=new Set(); window.allVideos.forEach(v=>{if((v.username||'').toLowerCase().includes(q)&&!seen.has(v.userId)){seen.add(v.userId);creators.push(v);}}); let html=''; if(creators.length){html+='
Accounts
'+creators.slice(0,8).map(v=>`
@${escapeHtml(v.username||'creator')}${formatCount((v.likes||[]).length)} likes on loaded videos
`).join('')+'
';} if(matchedVideos.length){html+='
Videos
'+matchedVideos.slice(0,30).map(v=>{const src=escapeHtml(v.mediaUrl||v.videoUrl||'');const filter=escapeHtml(FILTERS[v.videoFilter]||FILTERS.Original);const media=(v.mediaType||'video')==='image'?``:``;return `
${media}
❤️ ${formatCount((v.likes||[]).length)}
${formatCount(v.views||0)} views
`;}).join('')+'
';} if(!html)html='
No matching videos or accounts found.
'; container.innerHTML=html; container.querySelectorAll('.creator-result').forEach(el=>el.onclick=()=>openCreatorProfile(el.dataset.uid)); container.querySelectorAll('[data-video-id]').forEach(el=>el.onclick=()=>{const v=window.allVideos.find(x=>x.id===el.dataset.videoId);if(v)openSingleMedia(v.mediaUrl||v.videoUrl,v.id);}); } window.searchTag=(tag)=>{switchTab('discover');$('searchInput').value=tag;renderDiscoverSearch(tag);}; window.openEditVideo=(videoId)=>{ if(!window.userLoggedIn)return showAuthPanel('login'); const video=window.allVideos.find(v=>v.id===videoId); if(!video || video.userId!==window.currentUser.uid)return showToastMsg('You can only edit your own posts.'); currentEditingVideoId=videoId; selectedEditFilterName=video.videoFilter||'Original'; $('editVideoTitle').value=video.title||''; $('editVideoDesc').value=video.caption||''; $('editVideoHashtags').value=(video.hashtags||[]).join(', '); renderEditHashtagPreview($('editVideoHashtags').value); populateEditFilters(); const prev=$('editVideoPreview'); prev.innerHTML=''; if((video.mediaType||'video')==='image'){const img=document.createElement('img');img.src=safeUrl(video.mediaUrl||video.videoUrl);prev.appendChild(img);} else {const v=document.createElement('video');v.src=safeUrl(video.mediaUrl||video.videoUrl);v.muted=true;v.playsInline=true;v.controls=true;prev.appendChild(v);} $('editVideoModal').classList.add('open'); document.body.classList.add('modal-open'); }; window.saveVideoEdit=async()=>{ if(!currentEditingVideoId||!window.currentUser)return; const video=window.allVideos.find(v=>v.id===currentEditingVideoId); if(!video||video.userId!==window.currentUser.uid)return; const btn=$('saveVideoEditBtn'); btn.disabled=true; btn.innerText='Saving...'; try{ const title=$('editVideoTitle').value.trim(); const caption=$('editVideoDesc').value.trim(); const hashtags=normalizeHashtags($('editVideoHashtags').value); await updateDoc(doc(db,'videos',currentEditingVideoId),{title,caption,hashtags,videoFilter:selectedEditFilterName,updatedAt:serverTimestamp()}); closeModal('editVideoModal'); showToastMsg('Video updated successfully.'); }catch(err){console.error('Edit video:',err);showToastMsg('Could not update video.');} finally{btn.disabled=false;btn.innerText='Save Changes';} }; window.deleteVideo=async(videoId)=>{ if(!window.userLoggedIn)return showAuthPanel('login'); const video=window.allVideos.find(v=>v.id===videoId); if(!video||video.userId!==window.currentUser.uid)return showToastMsg('You can only delete your own posts.'); if(!confirm('Delete this post? This will remove it from FlikZee.'))return; try{await deleteDoc(doc(db,'videos',videoId));showToastMsg('Post deleted.');}catch(err){console.error('Delete video:',err);showToastMsg('Could not delete this post.');} }; window.handleHomeSearch=(value)=>{ const q=String(value||'').trim().toLowerCase(); const box=$('homeSearchResults'); if(!box)return; if(!q){box.classList.remove('open');box.innerHTML='';return;} const rows=[]; const seen=new Set(); window.allVideos.forEach(v=>{ const hay=[v.username,v.title,v.caption,v.soundName,...(v.hashtags||[])].join(' ').toLowerCase(); if(hay.includes(q)){const key=v.userId+'|'+v.id;if(!seen.has(key)){seen.add(key);rows.push(v);}} }); const creators=[]; const cseen=new Set(); window.allVideos.forEach(v=>{if((v.username||'').toLowerCase().includes(q)&&!cseen.has(v.userId)){cseen.add(v.userId);creators.push(v);}}); let html=''; creators.slice(0,5).forEach(v=>{html+=`
@${escapeHtml(v.username||'creator')}Creator profile
`;}); rows.slice(0,10).forEach(v=>{html+=`
${escapeHtml(v.title||v.soundName||'FlikZee video')}@${escapeHtml(v.username||'creator')} • ${formatCount(v.views||0)} views
`;}); box.innerHTML=html||'
No matching results.
'; box.classList.add('open'); box.querySelectorAll('[data-home-profile]').forEach(el=>el.onclick=()=>{box.classList.remove('open');openCreatorProfile(el.dataset.homeProfile);}); box.querySelectorAll('[data-home-video]').forEach(el=>el.onclick=()=>{box.classList.remove('open');const v=window.allVideos.find(x=>x.id===el.dataset.homeVideo);if(v)openSingleMedia(v.mediaUrl||v.videoUrl,v.id);}); }; document.addEventListener('click',e=>{if(!e.target.closest('.home-search-wrap'))$('homeSearchResults')?.classList.remove('open');}); window.setCameraMode=(mode)=>{ creatorCameraMode=mode; document.querySelectorAll('.camera-mode').forEach(b=>b.classList.toggle('active',b.dataset.cameraMode===mode)); const text=$('textCreateBox'), editor=$('studioUploadEditor'), record=$('cameraRecordBtn'), note=$('cameraLimitNote'); if(mode==='text'){closeCreatorCamera(); if(text)text.style.display='block'; if(editor)editor.style.display='none'; return;} if(text)text.style.display='none'; if(mode==='photo'){note.innerText='Photo capture mode';record.innerText='PHOTO';} else {note.innerText=`${mode} second maximum`;record.innerText='';} }; window.createTextPost=async()=>{ if(!window.userLoggedIn)return showAuthPanel('login'); const text=String($('textPostContent')?.value||'').trim(); if(!text)return; try{await addDoc(collection(db,'videos'),{userId:window.currentUser.uid,username:getUsername(window.currentUser),userAvatar:window.currentUser.photoURL||defaultAvatar(window.currentUser.uid),title:'',caption:text,hashtags:normalizeHashtags(text),mediaType:'text',mediaUrl:'',videoUrl:'',likes:[],commentsCount:0,views:0,shares:0,videoFilter:'Original',soundId:'',soundName:'Original Sound',soundCategory:'Original',soundUrl:'',timestamp:serverTimestamp()});$('textPostContent').value='';showToastMsg('Text post published!');switchTab('home');}catch(err){console.error(err);showToastMsg('Could not publish text post.');} }; window.switchStudioPanel=(panel)=>{ document.querySelectorAll('.studio-tab').forEach(b=>b.classList.toggle('active',b.dataset.studioTab===panel)); ['create','templates','sounds','editor'].forEach(x=>$('studio'+x.charAt(0).toUpperCase()+x.slice(1)+'Panel')?.classList.toggle('active',x===panel)); if(panel==='templates')startTemplateListener(); if(panel==='sounds'){renderStudioSoundCategories();renderStudioSounds();} }; window.openCreatorCamera=async()=>{ if(!window.userLoggedIn)return showAuthPanel('login'); try{ if(creatorCameraStream)creatorCameraStream.getTracks().forEach(t=>t.stop()); creatorCameraStream=await navigator.mediaDevices.getUserMedia({video:{facingMode:creatorFacingMode,width:{ideal:1080},height:{ideal:1920}},audio:true}); setCameraMode(creatorCameraMode); $('creatorCameraPreview').srcObject=creatorCameraStream;$('cameraCard').style.display='block';$('studioUploadEditor').style.display='none';$('cameraStatus').innerText='Ready — tap the circle to record'; }catch(err){console.error(err);alert('Camera/microphone access was blocked. Please allow permissions and try again.');} }; window.closeCreatorCamera=()=>{if(creatorMediaRecorder&&creatorMediaRecorder.state!=='inactive')creatorMediaRecorder.stop();if(creatorCameraStream){creatorCameraStream.getTracks().forEach(t=>t.stop());creatorCameraStream=null;}$('creatorCameraPreview').srcObject=null;$('cameraCard').style.display='none';$('studioUploadEditor').style.display='block';creatorRecording=false;}; window.flipCreatorCamera=async()=>{creatorFacingMode=creatorFacingMode==='environment'?'user':'environment';await openCreatorCamera();}; window.toggleCreatorFlash=()=>showToastMsg('Flash control depends on the browser/device camera API.'); window.toggleCreatorTimer=()=>{creatorTimerSeconds=creatorTimerSeconds?0:3;$('cameraStatus').innerText=creatorTimerSeconds?'3 second timer enabled':'Timer disabled';}; window.captureCreatorMedia=async()=>{ if(creatorCameraMode==='text'){return;} if(creatorCameraMode==='photo'){ if(!creatorCameraStream)return; const video=$('creatorCameraPreview'); const canvas=document.createElement('canvas'); canvas.width=video.videoWidth||1080; canvas.height=video.videoHeight||1920; const ctx=canvas.getContext('2d'); if(creatorFacingMode==='user'){ctx.translate(canvas.width,0);ctx.scale(-1,1);} ctx.drawImage(video,0,0,canvas.width,canvas.height); canvas.toBlob(blob=>{if(!blob)return;uploadSelectedFile=new File([blob],`flikzee-photo-${Date.now()}.jpg`,{type:'image/jpeg'});uploadSelectedMediaType='image';$('fileNameDisplay').innerText='Camera photo ✔️';$('uploadImagePreview').src=URL.createObjectURL(blob);$('uploadImagePreview').style.display='block';$('uploadPreview').style.display='none';$('uploadPreviewWrap').style.display='block';applyUploadFilter(selectedFilterName);closeCreatorCamera();showToastMsg('Photo ready — add details and post.');},'image/jpeg',.92); return; } if(creatorRecording){creatorMediaRecorder?.stop();return;} if(!creatorCameraStream)return; if(creatorTimerSeconds){$('cameraStatus').innerText=`Starting in ${creatorTimerSeconds}...`;for(let i=creatorTimerSeconds;i>0;i--){$('cameraStatus').innerText=`Starting in ${i}...`;await new Promise(r=>setTimeout(r,1000));}} creatorRecordedChunks=[];let options={mimeType:'video/webm'};if(!MediaRecorder.isTypeSupported(options.mimeType))options={}; try{creatorMediaRecorder=new MediaRecorder(creatorCameraStream,options);}catch(e){alert('This browser does not support video recording.');return;} creatorMediaRecorder.ondataavailable=e=>{if(e.data?.size)creatorRecordedChunks.push(e.data)}; creatorMediaRecorder.onstop=()=>{const blob=new Blob(creatorRecordedChunks,{type:'video/webm'});uploadSelectedFile=new File([blob],`flikzee-${Date.now()}.webm`,{type:'video/webm'});uploadSelectedMediaType='video';$('fileNameDisplay').innerText='Camera recording ✔️';$('uploadPreview').src=URL.createObjectURL(blob);$('uploadPreview').style.display='block';$('uploadImagePreview').style.display='none';$('uploadPreviewWrap').style.display='block';applyUploadFilter(selectedFilterName);closeCreatorCamera();showToastMsg('Recording ready — add details and post.');}; creatorMediaRecorder.start();creatorRecording=true;creatorRecordingStartedAt=Date.now();$('cameraRecordBtn').classList.add('recording');$('cameraStatus').innerText='Recording… tap again to stop'; if(creatorRecordingTimer)clearTimeout(creatorRecordingTimer); const maxMs=Number(creatorCameraMode)*1000; if(['10','15','60'].includes(creatorCameraMode)){creatorRecordingTimer=setTimeout(()=>{if(creatorRecording&&creatorMediaRecorder?.state!=='inactive')creatorMediaRecorder.stop();},maxMs);} }; function startTemplateListener(){ if(templatesUnsubscribe)templatesUnsubscribe(); const q=query(collection(db,'templates'),limit(40)); templatesUnsubscribe=onSnapshot(q,snap=>{const items=snap.docs.map(d=>({id:d.id,...d.data()}));renderTemplates(items);},err=>{console.warn('Templates:',err);renderTemplates([]);}); } function renderTemplates(items){ const box=$('templateGrid');if(!box)return; if(!items.length){box.innerHTML='
No templates are published yet.
Admin can add licensed/original templates to the templates collection.
';return;} box.innerHTML=items.map(t=>`
${escapeHtml(t.name||'FlikZee Template')}
${escapeHtml(t.category||'Viral Hits')} • ${formatCount(t.uses||0)} uses
`).join(''); } window.useFlikZeeTemplate=(id)=>{showToastMsg('Template selected — choose your media next.');switchStudioPanel('create');window.selectedTemplateId=id;openGalleryPicker();}; function renderStudioSoundCategories(){const row=$('studioSoundCategoryRow');if(!row)return;row.innerHTML='';SOUND_CATEGORIES.forEach(cat=>{const b=document.createElement('button');b.type='button';b.className='sound-cat'+(cat===selectedSoundCategory?' active':'');b.innerText=cat;b.onclick=()=>{selectedSoundCategory=cat;renderStudioSoundCategories();renderStudioSounds();};row.appendChild(b);});} window.renderStudioSounds=function(){const box=$('studioSoundList');if(!box)return;const q=String($('studioSoundSearch')?.value||'').toLowerCase().trim();const arr=SOUND_LIBRARY.filter(s=>(q ? (s.name.toLowerCase().includes(q)||s.category.toLowerCase().includes(q)) : s.category===selectedSoundCategory));box.innerHTML=arr.map(sound=>`
${escapeHtml(sound.name)}${escapeHtml(sound.category)} • FlikZee Audio
`).join('');}; window.chooseStudioSound=(id)=>{selectedSound=SOUND_LIBRARY.find(x=>x.id===id)||null;switchStudioPanel('create');renderSoundLibrary();showToastMsg(selectedSound?`Sound selected: ${selectedSound.name}`:'Sound selected');}; async function checkAdminAccess(){ if(!window.currentUser)return false; try{const snap=await getDoc(doc(db,'users',window.currentUser.uid));const d=snap.data()||{};const token=await window.currentUser.getIdTokenResult();return d.role==='admin'||token.claims?.admin===true;}catch(e){return false;} } async function updateAdminVisibility(){const ok=await checkAdminAccess();$('adminCenterSetting').style.display=ok?'flex':'none';return ok;} window.openAdminCenter=async()=>{if(!(await checkAdminAccess()))return alert('Admin access required.');$('adminCenterModal').classList.add('open');document.body.classList.add('modal-open');loadAdminDashboard();}; window.loadAdminDashboard=async function(){ if(!(await checkAdminAccess()))return; try{ const [usersCount,videosCount]=await Promise.all([getCountFromServer(collection(db,'users')),getCountFromServer(collection(db,'videos'))]); const totalUsers=usersCount.data().count,totalVideos=videosCount.data().count; $('adminStats').innerHTML=`
${formatCount(totalUsers)}Total Users
${formatCount(totalVideos)}Total Videos
${formatCount(window.allVideos.reduce((s,v)=>s+(v.views||0),0))}Loaded Views
${formatCount(window.allVideos.reduce((s,v)=>s+(v.likes||[]).length,0))}Loaded Likes
`; if(adminPaymentsUnsubscribe)adminPaymentsUnsubscribe(); adminPaymentsUnsubscribe=onSnapshot(query(collection(db,'payments'),orderBy('createdAt','desc'),limit(15)),snap=>{$('adminPayments').innerHTML=snap.empty?'
No payments recorded.
':snap.docs.map(d=>{const x=d.data();return `
🪙 ${escapeHtml(x.userEmail||x.userId||'User')}${escapeHtml(x.packageId||'Coin purchase')} • ${escapeHtml(x.status||'pending')}
${escapeHtml(x.amount||x.price||'')}
`}).join('')},()=>{$('adminPayments').innerHTML='
Payments collection is ready.
';}); if(adminTopUsersUnsubscribe)adminTopUsersUnsubscribe(); adminTopUsersUnsubscribe=onSnapshot(query(collection(db,'users'),orderBy('totalViews','desc'),limit(10)),snap=>{$('adminTopUsers').innerHTML=snap.docs.map((d,i)=>{const x=d.data();return `
#${i+1} ${escapeHtml(x.displayName||x.username||'Creator')}@${escapeHtml(x.username||'creator')} • ${formatCount(x.followers?.length||0)} followers
${formatCount(x.totalViews||0)} views
`}).join('')||'
No users yet.
';},()=>{$('adminTopUsers').innerHTML='
Top-user query needs the totalViews field/index.
';}); if(adminActivityUnsubscribe)adminActivityUnsubscribe(); adminActivityUnsubscribe=onSnapshot(query(collection(db,'adminActivity'),orderBy('createdAt','desc'),limit(ADMIN_ACTIVITY_LIMIT)),snap=>{$('adminActivity').innerHTML=snap.docs.map(d=>{const x=d.data();return `
${escapeHtml(x.text||x.action||'Activity')}${escapeHtml(x.userEmail||x.userId||'')} • ${formatNotificationTime(x.createdAt)}
ACTIVE
`}).join('')||'
No activity logged yet.
';},()=>{$('adminActivity').innerHTML='
Activity log ready.
';}); }catch(err){console.error('Admin dashboard:',err);showToastMsg('Admin dashboard could not load completely.');} }; window.switchTab=(tab,fromProfile=false)=>{ if(tab==='live') return; if(!fromProfile && tab!=='profile') lastNonProfileTab=tab; if(soundPreviewAudio){soundPreviewAudio.pause();soundPreviewAudio.currentTime=0;} document.querySelectorAll('.view-section').forEach(v=>v.style.display='none'); const target=$(tab+'View'); if(target)target.style.display='block'; document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active')); const nav=$(tab==='upload'?'nav-upload':tab==='profile'?'nav-profile-btn':'nav-'+tab); if(nav)nav.classList.add('active'); if(tab!=='home') { pauseAllFeedVideosExcept(null); } else { setupObserver(); } if(tab==='inbox')markNotificationsRead(); if(tab==='discover')startCategoryListener(); }; window.toggleAudioMute=()=>{ window.isGlobalMuted=!window.isGlobalMuted; document.querySelectorAll('video').forEach(v=>v.muted=window.isGlobalMuted); document.querySelectorAll('audio').forEach(a=>a.muted=window.isGlobalMuted); $('muteBtn').innerText=window.isGlobalMuted?'🔇':'🔊'; if(!window.isGlobalMuted){ document.querySelectorAll('#feed .video-wrapper').forEach(w=>{ const r=w.getBoundingClientRect(); if(r.topwindow.innerHeight*.35 && w._soundAudio){ w._soundAudio.play().catch(()=>{}); } }); } }; window.toggleSettings=()=>$('settingsMenu').classList.toggle('open'); window.closeModal=(id)=>{const el=$(id);el?.classList.remove('open');document.body.classList.remove('modal-open');if(id==='commentsModal'&&commentsUnsubscribe){commentsUnsubscribe();commentsUnsubscribe=null;}}; window.showToastMsg=(msg)=>{const t=$('toast');t.innerText=msg;t.style.display='block';clearTimeout(toastTimer);toastTimer=setTimeout(()=>t.style.display='none',2600);}; document.addEventListener('click',()=>{document.querySelectorAll('.video-menu.open,.grid-video-menu.open').forEach(x=>x.classList.remove('open'));}); window.addEventListener('popstate',()=>{ if($('singleVideoView').style.display==='block')closeSingleVideo(); }); loadFeed(); startCategoryListener(); const params=new URLSearchParams(location.search); const sharedVideo=params.get('video'); const sharedProfile=params.get('profile'); if(sharedVideo){ setTimeout(()=>{ const v=window.allVideos.find(x=>x.id===sharedVideo); if(v)openSingleMedia(v.mediaUrl||v.videoUrl,v.id); },900); } if(sharedProfile){setTimeout(()=>openCreatorProfile(sharedProfile),900);}