Mobile-emulator / appstore_publishing.html
du3xzv
Create a new page for publishing apps to the app store
2aa4318
<script>if(window===top) location.href="/?app=appstore";</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>App Store - LunOS</title>
<script src="/static/lunid-auth.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #fff;
overflow-x: hidden;
}
.app-header {
padding: 30px 20px 15px;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
.app-back-btn {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.app-title {
font-size: 24px;
color: #fff;
font-weight: 600;
}
.search-bar {
background: #fff;
padding: 12px 16px;
border-bottom: 1px solid #e0e0e0;
margin-top: 75px;
padding-bottom: 65px;
}
.search-input {
background: #f1f3f4;
border-radius: 8px;
padding: 10px 16px;
display: flex;
align-items: center;
gap: 12px;
}
.search-input input {
flex: 1;
border: none;
background: transparent;
font-size: 16px;
color: #202124;
outline: none;
}
.store-tabs {
background: #fff;
display: flex;
border-bottom: 2px solid #e0e0e0;
position: sticky;
top: 137px;
z-index: 99;
}
.store-tab {
flex: 1;
text-align: center;
padding: 14px 0;
font-weight: 500;
color: #5f6368;
cursor: pointer;
border-bottom: 3px solid transparent;
}
.store-tab.active {
color: #3b82f6;
border-bottom-color: #3b82f6;
}
.tab-content {
display: none;
min-height: calc(100vh - 200px);
padding-bottom: 80px;
}
.tab-content.active {
display: block;
}
.app-card {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 16px;
cursor: pointer;
border-bottom: 1px solid #e8eaed;
}
.app-icon {
width: 56px;
height: 56px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
}
.app-info {
flex: 1;
min-width: 0;
}
.app-name {
font-weight: 500;
font-size: 14px;
color: #202124;
margin-bottom: 2px;
}
.app-developer {
font-size: 12px;
color: #5f6368;
margin-bottom: 4px;
}
.app-rating {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: #5f6368;
}
.install-btn {
padding: 6px 16px;
background: #3b82f6;
color: #fff;
border: none;
border-radius: 20px;
font-weight: 600;
font-size: 12px;
cursor: pointer;
}
.install-btn.installed {
background: #e8eaed;
color: #3b82f6;
}
.featured-section {
padding: 16px 0;
}
.section-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px 12px;
}
.section-title h3 {
font-size: 16px;
font-weight: 600;
color: #202124;
}
.horizontal-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding: 0 16px;
}
.horizontal-scroll::-webkit-scrollbar {
display: none;
}
.apps-row {
display: flex;
gap: 12px;
width: max-content;
}
.small-app-card {
width: 120px;
cursor: pointer;
}
.small-app-icon {
width: 120px;
height: 120px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
margin-bottom: 8px;
}
.category-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding: 16px;
}
.category-card {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: background 0.2s;
}
.category-card:hover {
background: #e8eaed;
}
.category-icon {
font-size: 40px;
margin-bottom: 8px;
}
.toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 24px;
border-radius: 20px;
font-size: 14px;
z-index: 10000;
display: none;
}
</style>
</head>
<body>
<div class="app-header">
<div class="app-back-btn" onclick="goBack()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
</svg>
</div>
<div class="app-title">App Store</div>
<div style="display: flex; gap: 8px;">
<div class="app-back-btn" onclick="showLunIDModal()" id="authBtn" style="background: rgba(255, 255, 255, 0.3);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="#fff">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
</div>
<div class="app-back-btn" onclick="if(window.parent !== window) { window.parent.postMessage({action:'openUploadApp'}, '*'); } else { window.location.href='upload-app.html'; }" style="background: rgba(255, 255, 255, 0.3);">
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</div>
</div>
</div>
<!-- LunID Auth Modal -->
<div id="lunidModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.9); z-index:20000; overflow-y:auto;">
<div style="max-width:400px; margin:40px auto; padding:20px;">
<div style="display:flex; justify-content:flex-end; margin-bottom:10px;">
<button onclick="closeLunIDModal()" style="background:none; border:none; color:#fff; font-size:28px; cursor:pointer;">&times;</button>
</div>
<div id="lunidAuthContainer"></div>
</div>
</div>
<div class="search-bar">
<div class="search-input">
<svg width="20" height="20" viewBox="0 0 24 24" fill="#5f6368">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
<input type="text" id="appStoreSearch" placeholder="Search for apps & games">
</div>
</div>
<div class="store-tabs">
<div class="store-tab active" onclick="switchTab('forYou')">For you</div>
<div class="store-tab" onclick="switchTab('topCharts')">Top charts</div>
<div class="store-tab" onclick="switchTab('categories')">Categories</div>
</div>
<div id="forYouContent" class="tab-content active"></div>
<div id="topChartsContent" class="tab-content"></div>
<div id="categoriesContent" class="tab-content"></div>
<div id="toast" class="toast"></div>
<script>
// Load user-submitted apps from localStorage
const userSubmittedApps = JSON.parse(localStorage.getItem('userSubmittedApps') || '[]');
const approvedUserApps = userSubmittedApps.filter(app => app.approved).map(app => ({
...app,
rating: app.rating || 4.5,
downloads: app.downloads || '100+'
}));
const availableApps = [
{
id: 'fynwriteApp',
name: 'FynWrite',
developer: 'LunOS Team',
category: 'Productivity',
description: 'Write, share, and discover amazing stories. FynWrite is your personal blog platform on LunOS.',
rating: 4.7,
downloads: '100K+',
icon: '📝',
color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
featured: true,
size: '2.5 MB',
ageRating: '12+',
url: 'fynwrite.html'
},
{
id: 'sudoku',
name: 'Pro Sudoku',
developer: 'LunOS Games',
category: 'Games',
description: 'Classic Sudoku puzzle with 4 difficulty levels. Train your brain with daily challenges, hints, and beautiful UI. Track your stats and improve your skills!',
rating: 4.9,
downloads: '5M+',
icon: '<svg viewBox="0 0 60 60" style="width:100%;height:100%"><defs><linearGradient id="sudokuGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#667eea"/><stop offset="100%" style="stop-color:#764ba2"/></linearGradient></defs><rect width="60" height="60" rx="12" fill="url(#sudokuGrad)"/><rect x="6" y="6" width="16" height="16" fill="white" opacity="0.9" rx="2"/><rect x="22" y="6" width="16" height="16" fill="white" opacity="0.7" rx="2"/><rect x="38" y="6" width="16" height="16" fill="white" opacity="0.9" rx="2"/><rect x="6" y="22" width="16" height="16" fill="white" opacity="0.7" rx="2"/><rect x="22" y="22" width="16" height="16" fill="white" opacity="0.9" rx="2"/><rect x="38" y="22" width="16" height="16" fill="white" opacity="0.7" rx="2"/><rect x="6" y="38" width="16" height="16" fill="white" opacity="0.9" rx="2"/><rect x="22" y="38" width="16" height="16" fill="white" opacity="0.7" rx="2"/><rect x="38" y="38" width="16" height="16" fill="white" opacity="0.9" rx="2"/></svg>',
color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
featured: true,
topFree: true,
size: '2.1 MB',
ageRating: '4+',
inAppPurchases: false,
url: 'sudoku.html',
screenshots: ['📱 Clean interface', '🎯 4 difficulty levels', '💡 Smart hints', '📊 Track progress']
},
...approvedUserApps,
{
id: 'game-snake',
name: 'Snake Game',
developer: 'FynNX Games',
category: 'Games',
description: 'Classic snake game with modern graphics.',
rating: 4.5,
downloads: '500K+',
icon: '🐍',
color: 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',
featured: true,
size: '1.5 MB',
ageRating: '4+'
},
{
id: 'app-todo',
name: 'Todo List Pro',
developer: 'Productivity Pro',
category: 'Productivity',
description: 'Simple todo list app to organize your tasks.',
rating: 4.8,
downloads: '1M+',
icon: '✓',
color: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
featured: true,
size: '0.8 MB',
ageRating: '4+'
},
{
id: 'game-puzzle',
name: 'Puzzle Master',
developer: 'FynNX Games',
category: 'Games',
description: 'Challenging puzzle game with hundreds of levels.',
rating: 4.6,
downloads: '2M+',
icon: '🧩',
color: 'linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%)',
topFree: true,
size: '3.2 MB',
ageRating: '9+'
}
];
function switchTab(tabName) {
document.querySelectorAll('.store-tab').forEach(tab => {
tab.classList.remove('active');
});
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
event.target.classList.add('active');
document.getElementById(tabName + 'Content').classList.add('active');
if (tabName === 'forYou') loadForYou();
else if (tabName === 'topCharts') loadTopCharts();
else if (tabName === 'categories') loadCategories();
}
function loadForYou() {
const container = document.getElementById('forYouContent');
const featured = availableApps.filter(app => app.featured);
container.innerHTML = `
<div class="featured-section">
<div class="section-title">
<h3>Featured Apps</h3>
</div>
<div class="horizontal-scroll">
<div class="apps-row">
${featured.map(app => `
<div class="small-app-card" onclick="installApp('${app.id}')">
<div class="small-app-icon" style="background: ${app.color}">${app.icon}</div>
<div style="font-size: 13px; font-weight: 500; color: #202124;">${app.name}</div>
<div style="font-size: 11px; color: #5f6368;">⭐ ${app.rating}</div>
</div>
`).join('')}
</div>
</div>
</div>
<div style="padding: 16px;">
${availableApps.map(app => createAppCard(app)).join('')}
</div>
`;
}
function loadTopCharts() {
const container = document.getElementById('topChartsContent');
const topApps = availableApps.filter(app => app.topFree);
container.innerHTML = `
<div style="padding: 16px;">
<h3 style="margin-bottom: 16px;">Top Free Apps</h3>
${topApps.map(app => createAppCard(app)).join('')}
</div>
`;
}
function loadCategories() {
const container = document.getElementById('categoriesContent');
const categories = [
{ name: 'Games', icon: '🎮' },
{ name: 'Productivity', icon: '💼' },
{ name: 'Music', icon: '🎵' },
{ name: 'Creative', icon: '🎨' }
];
container.innerHTML = `
<div class="category-grid">
${categories.map(cat => `
<div class="category-card">
<div class="category-icon">${cat.icon}</div>
<div style="font-weight: 500;">${cat.name}</div>
</div>
`).join('')}
</div>
`;
}
function createAppCard(app) {
const isInstalled = getInstalledApps().includes(app.id);
return `
<div class="app-card" onclick="showAppDetails('${app.id}')">
<div class="app-icon" style="background: ${app.color}">${app.icon}</div>
<div class="app-info">
<div class="app-name">${app.name}</div>
<div class="app-developer">${app.developer}</div>
<div class="app-rating">⭐ ${app.rating} • ${app.downloads || '1K+'} downloads</div>
</div>
<button class="install-btn ${isInstalled ? 'installed' : ''}" onclick="event.stopPropagation(); installApp('${app.id}')">
${isInstalled ? 'OPEN' : 'GET'}
</button>
</div>
`;
}
function showAppDetails(appId) {
const app = availableApps.find(a => a.id === appId);
if (!app) return;
const isInstalled = getInstalledApps().includes(app.id);
const detailsHTML = `
<div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: #fff; z-index: 10001; overflow-y: auto;">
<div class="app-header">
<div class="app-back-btn" onclick="closeAppDetails()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
</svg>
</div>
<div class="app-title">${app.name}</div>
<div style="width: 36px;"></div>
</div>
<div style="padding: 20px; padding-top: 80px;">
<div style="display: flex; gap: 16px; margin-bottom: 20px;">
<div style="width: 80px; height: 80px; border-radius: 18px; background: ${app.color}; display: flex; align-items: center; justify-content: center; font-size: 40px; flex-shrink: 0;">${app.icon}</div>
<div style="flex: 1;">
<div style="font-size: 20px; font-weight: 600; margin-bottom: 4px;">${app.name}</div>
<div style="color: #5f6368; margin-bottom: 8px;">${app.developer}</div>
<div style="display: flex; gap: 12px; font-size: 12px; color: #5f6368;">
<div>${app.downloads} downloads</div>
<div>⭐ ${app.rating}</div>
<div>${app.ageRating || '4+'}</div>
</div>
</div>
</div>
<button class="install-btn ${isInstalled ? 'installed' : ''}" onclick="installApp('${app.id}')" style="width: 100%; padding: 12px; font-size: 16px; margin-bottom: 20px;">
${isInstalled ? 'OPEN' : 'INSTALL'}
</button>
${app.screenshots ? `
<div style="margin-bottom: 20px;">
<div style="font-weight: 600; margin-bottom: 12px;">Screenshots & Features</div>
<div style="display: flex; gap: 12px; overflow-x: auto; padding-bottom: 10px; -webkit-overflow-scrolling: touch;">
${app.screenshots.map((ss, idx) => `
<div style="min-width: 160px; height: 280px; background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); border-radius: 12px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.1); backdrop-filter: blur(20px);"></div>
<div style="position: relative; text-align: center; color: white; font-size: 13px; padding: 16px; font-weight: 500;">
${ss}
</div>
</div>
`).join('')}
</div>
</div>
` : ''}
<div style="margin-bottom: 20px;">
<div style="font-weight: 600; margin-bottom: 8px;">About this app</div>
<div style="color: #5f6368; line-height: 1.6;">${app.description}</div>
</div>
<div style="padding: 16px; background: #f8f9fa; border-radius: 12px; margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 12px;">
<span style="color: #5f6368;">Size</span>
<span style="font-weight: 500;">${app.size || 'N/A'}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 12px;">
<span style="color: #5f6368;">Category</span>
<span style="font-weight: 500;">${app.category}</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: #5f6368;">In-app purchases</span>
<span style="font-weight: 500;">${app.inAppPurchases ? 'Yes' : 'No'}</span>
</div>
</div>
<div style="margin-bottom: 20px;">
<div style="font-weight: 600; margin-bottom: 8px;">Ratings and reviews</div>
<div style="display: flex; align-items: center; gap: 16px;">
<div style="font-size: 48px; font-weight: 600;">${app.rating}</div>
<div style="flex: 1;">
<div style="color: #fbbf24; margin-bottom: 4px;">★★★★★</div>
<div style="color: #5f6368; font-size: 12px;">${app.downloads} reviews</div>
</div>
</div>
</div>
</div>
</div>
`;
const detailsDiv = document.createElement('div');
detailsDiv.id = 'appDetails';
detailsDiv.innerHTML = detailsHTML;
document.body.appendChild(detailsDiv);
}
function closeAppDetails() {
const details = document.getElementById('appDetails');
if (details) details.remove();
}
function installApp(appId) {
const installed = getInstalledApps();
const app = availableApps.find(a => a.id === appId);
if (!app) return;
// Add app to home screen
const homeApps = JSON.parse(localStorage.getItem('homeScreenApps') || '[]');
if (!homeApps.find(a => a.id === app.id)) {
homeApps.push({
id: app.id,
name: app.name,
icon: app.icon,
color: app.color,
category: app.category,
url: app.url || null
});
localStorage.setItem('homeScreenApps', JSON.stringify(homeApps));
localStorage.setItem('installedStoreApps', JSON.stringify([...getInstalledApps(), appId]));
// Refresh parent UI immediately
if (window.parent !== window) {
window.parent.location.reload();
}
}
if (!installed.includes(appId)) {
installed.push(appId);
localStorage.setItem('installedStoreApps', JSON.stringify(installed));
// Add app to home screen if not already there
const homeApps = JSON.parse(localStorage.getItem('homeScreenApps') || '[]');
if (!homeApps.find(a => a.id === app.id)) {
homeApps.push({
id: app.id,
name: app.name,
icon: app.icon,
color: app.color,
category: app.category,
url: app.url || null
});
localStorage.setItem('homeScreenApps', JSON.stringify(homeApps));
}
showToast('App installed successfully!');
// Refresh local UI
loadForYou();
// Notify parent for live home screen/drawer update
if (window.parent !== window) {
window.parent.postMessage({
type: 'appInstalled',
app: {
id: app.id,
name: app.name,
icon: app.icon,
color: app.color,
url: app.url
}
}, '*');
}
} else {
// If already installed, open it via parent message to avoid iframe nesting issues
if (window.parent !== window) {
window.parent.postMessage({
action: 'openApp',
app: app.id
}, '*');
} else if (app.url) {
window.location.href = app.url;
} else {
showToast('App already installed');
}
}
}
function getInstalledApps() {
return JSON.parse(localStorage.getItem('installedStoreApps') || '[]');
}
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.style.display = 'block';
setTimeout(() => toast.style.display = 'none', 2000);
}
function goBack() {
if (window.parent !== window) {
window.parent.postMessage({action: 'closeApp'}, '*');
} else {
window.location.href = 'index.html';
}
}
// LunID Authentication with Account Picker
function showLunIDModal() {
const modal = document.getElementById('lunidModal');
modal.style.display = 'block';
if (window.lunidAuth) {
lunidAuth.createAccountPickerUI('lunidAuthContainer', {
appName: 'App Store',
onContinue: (user) => {
closeLunIDModal();
updateAuthUI();
showToast('Welcome, ' + user.username + '!');
},
onCancel: closeLunIDModal
});
}
}
function closeLunIDModal() { document.getElementById('lunidModal').style.display = 'none'; }
function handleLogout() { window.lunidAuth.logout(); closeLunIDModal(); updateAuthUI(); showToast('Signed out'); }
function updateAuthUI() {
const btn = document.getElementById('authBtn');
if (window.lunidAuth && window.lunidAuth.isAuthenticated) {
btn.innerHTML = `<span style="font-size:14px; font-weight:600;">${window.lunidAuth.currentUser.username.charAt(0).toUpperCase()}</span>`;
btn.style.background = 'linear-gradient(135deg,#667eea,#764ba2)';
} else {
btn.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="#fff"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>';
btn.style.background = 'rgba(255,255,255,0.3)';
}
}
// Add LunID styles
const styleEl = document.createElement('style');
styleEl.textContent = window.LunIDAuth ? window.LunIDAuth.getStyles() : '';
document.head.appendChild(styleEl);
// Initialize
updateAuthUI();
loadForYou();
document.getElementById('appStoreSearch').oninput = (e) => {
const query = e.target.value.toLowerCase();
if (query) {
const results = availableApps.filter(app =>
app.name.toLowerCase().includes(query) ||
app.category.toLowerCase().includes(query)
);
const container = document.getElementById('forYouContent');
container.innerHTML = `
<div style="padding: 16px;">
<div style="color: #5f6368; margin-bottom: 16px;">${results.length} results</div>
${results.map(app => createAppCard(app)).join('')}
</div>
`;
} else {
loadForYou();
}
};
</script>
</body>
</html>