add shell architecture with persistent status bar, nav bar, and content fragments

This commit is contained in:
2026-01-20 10:09:47 +01:00
parent 1f91d7508e
commit 2db7eea9f1
15 changed files with 1456 additions and 31 deletions

View File

@@ -0,0 +1,11 @@
<!-- Browser App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Browser</span>
</div>
<div class="app-body">
<span class="placeholder-text">Browser App</span>
</div>
</div>

View File

@@ -0,0 +1,44 @@
<!-- Camera App Content Fragment -->
<!-- Loaded into shell's #app-container -->
<!-- Styles are in shell.rml -->
<div class="camera-content">
<!-- Top Bar -->
<div class="camera-top-bar">
<div class="camera-btn" onclick="showToast('Flash: Auto')">
<img src="../../icons/flash.tga"/>
</div>
<div class="camera-btn">
<img src="../../icons/timer.tga"/>
</div>
<div class="camera-btn" onclick="showToast('Settings')">
<img src="../../icons/settings.tga"/>
</div>
</div>
<!-- Viewfinder -->
<div class="viewfinder-container">
<div class="viewfinder">
<span class="viewfinder-text">Camera Preview</span>
</div>
<div class="focus-indicator"></div>
</div>
<!-- Mode Selector -->
<div class="mode-selector">
<span class="mode-item">Video</span>
<span class="mode-item active">Photo</span>
<span class="mode-item">Portrait</span>
</div>
<!-- Bottom Controls -->
<div class="camera-bottom-bar">
<div class="gallery-preview"></div>
<div class="shutter-btn" onclick="showToast('Photo captured!', 'success')">
<div class="shutter-btn-inner"></div>
</div>
<div class="switch-camera-btn" onclick="showToast('Switched camera')">
<img src="../../icons/switch-camera.tga"/>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<!-- Contacts App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Contacts</span>
</div>
<div class="app-body">
<span class="placeholder-text">Contacts App</span>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<!-- Dialer App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Phone</span>
</div>
<div class="app-body">
<span class="placeholder-text">Dialer App</span>
</div>
</div>

View File

@@ -0,0 +1,76 @@
<!-- Home Screen Content Fragment -->
<!-- Loaded into shell's #app-container -->
<!-- Styles are in shell.rml -->
<div class="home-content-area">
<!-- App Grid -->
<div class="home-grid">
<div id="installed-apps" class="app-grid-section">
<!-- Apps populated dynamically -->
<div class="app-icon">
<div class="app-icon-image" style="background-color: #F44336;" onclick="shellNavigateTo('browser')">
<img src="../../icons/browser.tga"/>
</div>
<span class="app-icon-label">Browser</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #9C27B0;" onclick="shellNavigateTo('camera')">
<img src="../../icons/camera.tga"/>
</div>
<span class="app-icon-label">Camera</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #FF9800;" onclick="shellNavigateTo('contacts')">
<img src="../../icons/contacts.tga"/>
</div>
<span class="app-icon-label">Contacts</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #4CAF50;" onclick="shellNavigateTo('dialer')">
<img src="../../icons/phone.tga"/>
</div>
<span class="app-icon-label">Phone</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #2196F3;" onclick="shellNavigateTo('messages')">
<img src="../../icons/message.tga"/>
</div>
<span class="app-icon-label">Messages</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #E91E63;" onclick="shellNavigateTo('music')">
<img src="../../icons/music.tga"/>
</div>
<span class="app-icon-label">Music</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #607D8B;" onclick="shellNavigateTo('settings')">
<img src="../../icons/settings.tga"/>
</div>
<span class="app-icon-label">Settings</span>
</div>
<div class="app-icon">
<div class="app-icon-image" style="background-color: #3F51B5;" onclick="shellNavigateTo('store')">
<img src="../../icons/store.tga"/>
</div>
<span class="app-icon-label">Store</span>
</div>
</div>
</div>
<!-- Dock -->
<div class="home-dock">
<div class="dock-item" style="background-color: #4CAF50;" onclick="shellNavigateTo('dialer')">
<img src="../../icons/phone.tga"/>
</div>
<div class="dock-item" style="background-color: #2196F3;" onclick="shellNavigateTo('messages')">
<img src="../../icons/message.tga"/>
</div>
<div class="dock-item" style="background-color: #FF9800;" onclick="shellNavigateTo('contacts')">
<img src="../../icons/contacts.tga"/>
</div>
<div class="dock-item" style="background-color: #F44336;" onclick="shellNavigateTo('browser')">
<img src="../../icons/browser.tga"/>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<!-- Messages App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Messages</span>
</div>
<div class="app-body">
<span class="placeholder-text">Messages App</span>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<!-- Music App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Music</span>
</div>
<div class="app-body">
<span class="placeholder-text">Music App</span>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<!-- Settings App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Settings</span>
</div>
<div class="app-body">
<span class="placeholder-text">Settings App</span>
</div>
</div>

View File

@@ -0,0 +1,357 @@
-- System Shell for Mosis
-- Provides persistent navigation, notifications, toasts, and dialogs
local shell_document = nil
local app_container = nil
-- Navigation state (persistent in shell)
local nav_history = {}
local current_app = "home"
local current_app_path = nil
-- Dialog callback
local dialog_callback = nil
-- Notification state
local notifications_expanded = false
-- ===== INITIALIZATION =====
function initShell(doc)
print("[Shell] Initializing system shell...")
shell_document = doc
app_container = doc:GetElementById("app-container")
if not app_container then
print("[Shell] ERROR: app-container not found!")
return
end
-- Update time display
updateTime()
-- Load home screen by default
loadAppContent_internal("home", "apps/home/home_content.rml")
print("[Shell] Shell initialized")
end
-- Update status bar time
function updateTime()
local time_elem = shell_document:GetElementById("shell-time")
if time_elem then
time_elem.inner_rml = "12:30"
end
end
-- ===== APP LOADING =====
-- Internal function to load app content
function loadAppContent_internal(app_id, app_path)
if not app_container then
print("[Shell] ERROR: No app container")
return false
end
print("[Shell] Loading app: " .. app_id .. " from " .. app_path)
-- Show loading overlay
showLoading(true)
-- Load app content using C++ function
if loadAppContent then
local success = loadAppContent(app_container, app_path)
showLoading(false)
if success then
current_app = app_id
current_app_path = app_path
print("[Shell] App loaded: " .. app_id)
return true
else
print("[Shell] Failed to load app: " .. app_id)
showToast("Failed to load app", "error")
return false
end
else
showLoading(false)
print("[Shell] ERROR: loadAppContent not available, using fallback")
-- Fallback: try to load via inner_rml if we can read the file
-- This won't work without C++ support, but shows the intent
return false
end
end
-- Navigate to a system app by ID
function shellNavigateTo(app_id)
local app_paths = {
home = "apps/home/home_content.rml",
dialer = "apps/dialer/dialer_content.rml",
calling = "apps/dialer/calling_content.rml",
contacts = "apps/contacts/contacts_content.rml",
contact_detail = "apps/contacts/contact_detail_content.rml",
messages = "apps/messages/messages_content.rml",
chat = "apps/messages/chat_content.rml",
settings = "apps/settings/settings_content.rml",
browser = "apps/browser/browser_content.rml",
store = "apps/store/store_content.rml",
camera = "apps/camera/camera_content.rml",
music = "apps/music/music_content.rml"
}
local path = app_paths[app_id]
if path then
-- Push current app to history
if current_app and current_app ~= app_id then
table.insert(nav_history, {
app_id = current_app,
app_path = current_app_path
})
print("[Shell] Pushed to history: " .. current_app .. " (depth: " .. #nav_history .. ")")
end
return loadAppContent_internal(app_id, path)
else
print("[Shell] Unknown app: " .. tostring(app_id))
return false
end
end
-- ===== NAVIGATION =====
-- Go back to previous app
function shellGoBack()
print("[Shell] goBack called (history depth: " .. #nav_history .. ")")
-- Hide notifications if open
if notifications_expanded then
toggleNotifications()
end
if #nav_history > 0 then
local previous = table.remove(nav_history)
print("[Shell] Going back to: " .. previous.app_id)
-- Don't push to history when going back
local temp_app = current_app
current_app = nil
current_app_path = nil
local success = loadAppContent_internal(previous.app_id, previous.app_path)
if not success then
-- Restore state on failure
current_app = temp_app
end
return success
else
print("[Shell] No history - already at root")
if current_app ~= "home" then
shellGoHome()
end
end
return false
end
-- Go to home screen (clear history)
function shellGoHome()
print("[Shell] goHome called")
-- Hide notifications if open
if notifications_expanded then
toggleNotifications()
end
-- Clear history
nav_history = {}
-- Load home
current_app = nil
current_app_path = nil
loadAppContent_internal("home", "apps/home/home_content.rml")
end
-- Show recents (placeholder)
function shellShowRecents()
print("[Shell] showRecents called")
showToast("Recent apps not implemented", "warning")
end
-- Launch external app (from base-apps)
function shellLaunchApp(package_id, app_path, entry_point)
print("[Shell] Launching external app: " .. package_id)
-- Push current to history
if current_app then
table.insert(nav_history, {
app_id = current_app,
app_path = current_app_path
})
end
-- For external apps, we need content version of the entry
-- Convert "app.rml" to "app_content.rml" or load directly
local content_path = app_path .. "/" .. entry_point
-- Switch sandbox context if available
if switchAppSandbox then
switchAppSandbox(package_id, app_path)
end
return loadAppContent_internal(package_id, content_path)
end
-- ===== TOASTS =====
function showToast(message, type)
type = type or "default"
local container = shell_document:GetElementById("toast-container")
if not container then
print("[Shell] Toast container not found")
return
end
local class = "toast"
if type == "success" then
class = "toast toast-success"
elseif type == "error" then
class = "toast toast-error"
elseif type == "warning" then
class = "toast toast-warning"
end
-- Add toast element
local toast_html = '<div class="' .. class .. '">' .. message .. '</div>'
container.inner_rml = container.inner_rml .. toast_html
-- Auto-remove after 3 seconds (would need timer API)
if mosis and mosis.timer then
mosis.timer.setTimeout(function()
-- Remove first toast
container.inner_rml = ""
end, 3000)
end
print("[Shell] Toast: " .. message .. " (" .. type .. ")")
end
-- ===== DIALOGS =====
function showDialog(title, message, on_confirm, on_cancel)
local overlay = shell_document:GetElementById("dialog-overlay")
local title_elem = shell_document:GetElementById("dialog-title")
local message_elem = shell_document:GetElementById("dialog-message")
if overlay and title_elem and message_elem then
title_elem.inner_rml = title
message_elem.inner_rml = message
overlay:SetClass("visible", true)
dialog_callback = {
confirm = on_confirm,
cancel = on_cancel
}
print("[Shell] Dialog shown: " .. title)
end
end
function dialogConfirm()
local overlay = shell_document:GetElementById("dialog-overlay")
if overlay then
overlay:SetClass("visible", false)
end
if dialog_callback and dialog_callback.confirm then
dialog_callback.confirm()
end
dialog_callback = nil
end
function dialogCancel()
local overlay = shell_document:GetElementById("dialog-overlay")
if overlay then
overlay:SetClass("visible", false)
end
if dialog_callback and dialog_callback.cancel then
dialog_callback.cancel()
end
dialog_callback = nil
end
-- ===== NOTIFICATIONS =====
function toggleNotifications()
local panel = shell_document:GetElementById("notification-panel")
if panel then
notifications_expanded = not notifications_expanded
panel:SetClass("expanded", notifications_expanded)
print("[Shell] Notifications " .. (notifications_expanded and "expanded" or "collapsed"))
end
end
function addNotification(title, text, icon, app_id)
local list = shell_document:GetElementById("notification-list")
if not list then return end
icon = icon or "../../icons/message.tga"
local notification_html = [[
<div class="notification-item" onclick="onNotificationClick(']] .. (app_id or "") .. [[')">
<div class="notification-icon">
<img src="]] .. icon .. [["/>
</div>
<div class="notification-content">
<div class="notification-title">]] .. title .. [[</div>
<div class="notification-text">]] .. text .. [[</div>
</div>
<span class="notification-time">now</span>
</div>
]]
list.inner_rml = notification_html .. list.inner_rml
print("[Shell] Notification added: " .. title)
end
function onNotificationClick(app_id)
toggleNotifications()
if app_id and app_id ~= "" then
shellNavigateTo(app_id)
end
end
function clearNotifications()
local list = shell_document:GetElementById("notification-list")
if list then
list.inner_rml = ""
print("[Shell] Notifications cleared")
end
end
-- ===== LOADING OVERLAY =====
function showLoading(visible)
local overlay = shell_document:GetElementById("loading-overlay")
if overlay then
overlay:SetClass("visible", visible)
end
end
-- ===== UTILITY =====
function shellGetCurrentApp()
return current_app
end
function shellCanGoBack()
return #nav_history > 0
end
-- Make shell functions globally available for apps
_G.showToast = showToast
_G.showDialog = showDialog
_G.addNotification = addNotification
_G.shellNavigateTo = shellNavigateTo
_G.shellLaunchApp = shellLaunchApp
print("[Shell] Shell script loaded")

View File

@@ -0,0 +1,650 @@
<rml>
<head>
<link type="text/rcss" href="../../ui/html.rcss"/>
<link type="text/rcss" href="../../ui/theme.rcss"/>
<link type="text/rcss" href="../../ui/components.rcss"/>
<script src="shell.lua"></script>
<title>Mosis Shell</title>
<style>
.shell {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #121212;
position: relative;
}
/* Status bar at top - always visible */
.shell-status-bar {
height: 36px;
min-height: 36px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
background-color: #000000;
z-index: 100;
}
.shell-status-bar-time {
font-size: 16px;
color: #FFFFFF;
}
.shell-status-bar-icons {
display: flex;
gap: 8px;
}
.shell-status-bar-icons img {
width: 24px;
height: 24px;
pointer-events: none;
}
/* App content area - fills middle space */
#app-container {
flex: 1;
overflow: hidden;
position: relative;
background-color: #121212;
}
/* System navigation bar at bottom - always visible */
.shell-nav-bar {
height: 56px;
min-height: 56px;
display: flex;
justify-content: space-around;
align-items: center;
background-color: #1a1a1a;
z-index: 100;
}
.shell-nav-btn {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 8px;
}
.shell-nav-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.shell-nav-btn:active {
background-color: rgba(255, 255, 255, 0.2);
}
.shell-nav-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
/* Home indicator line */
.shell-home-indicator {
width: 134px;
height: 5px;
background-color: #FFFFFF;
border-radius: 3px;
opacity: 0.6;
}
/* ===== OVERLAY LAYERS ===== */
/* Toast container - bottom of screen, above nav bar */
#toast-container {
position: absolute;
bottom: 70px;
left: 16px;
right: 16px;
z-index: 500;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.toast {
background-color: #323232;
color: #FFFFFF;
padding: 14px 24px;
border-radius: 8px;
font-size: 14px;
text-align: center;
pointer-events: auto;
animation: toast-in 0.3s ease-out;
}
.toast-success {
background-color: #4CAF50;
}
.toast-error {
background-color: #F44336;
}
.toast-warning {
background-color: #FF9800;
}
@keyframes toast-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Dialog/Modal overlay */
#dialog-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 600;
display: none;
align-items: center;
justify-content: center;
}
#dialog-overlay.visible {
display: flex;
}
.dialog-box {
background-color: #2d2d2d;
border-radius: 16px;
padding: 24px;
min-width: 280px;
max-width: 400px;
margin: 24px;
}
.dialog-title {
font-size: 20px;
font-weight: bold;
color: #FFFFFF;
margin-bottom: 12px;
}
.dialog-message {
font-size: 16px;
color: #B0B0B0;
margin-bottom: 24px;
line-height: 1.4;
}
.dialog-buttons {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.dialog-btn {
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
.dialog-btn-cancel {
background-color: transparent;
color: #BB86FC;
}
.dialog-btn-cancel:hover {
background-color: rgba(187, 134, 252, 0.1);
}
.dialog-btn-confirm {
background-color: #BB86FC;
color: #000000;
}
.dialog-btn-confirm:hover {
background-color: #D4A9FF;
}
/* Notification panel - slides down from top */
#notification-panel {
position: absolute;
top: 36px;
left: 0;
right: 0;
max-height: 0;
overflow: hidden;
background-color: #1a1a1a;
z-index: 400;
transition: max-height 0.3s ease-out;
}
#notification-panel.expanded {
max-height: 400px;
}
.notification-list {
padding: 8px;
}
.notification-item {
display: flex;
align-items: flex-start;
padding: 12px;
background-color: #2d2d2d;
border-radius: 12px;
margin-bottom: 8px;
cursor: pointer;
}
.notification-item:hover {
background-color: #383838;
}
.notification-icon {
width: 40px;
height: 40px;
border-radius: 8px;
background-color: #BB86FC;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.notification-icon img {
width: 24px;
height: 24px;
}
.notification-content {
flex: 1;
}
.notification-title {
font-size: 14px;
font-weight: bold;
color: #FFFFFF;
}
.notification-text {
font-size: 13px;
color: #B0B0B0;
margin-top: 2px;
}
.notification-time {
font-size: 12px;
color: #808080;
margin-left: 8px;
}
/* Loading overlay */
#loading-overlay {
position: absolute;
top: 36px;
left: 0;
right: 0;
bottom: 56px;
background-color: rgba(0, 0, 0, 0.8);
z-index: 300;
display: none;
align-items: center;
justify-content: center;
}
#loading-overlay.visible {
display: flex;
}
.loading-spinner {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: #BB86FC;
}
/* ===== APP CONTENT STYLES ===== */
/* Styles for content fragments loaded into #app-container */
/* Home Content */
.home-content-area {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #121212;
}
.home-grid {
flex: 1;
padding: 8px;
overflow: auto;
}
.app-grid-section {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.app-icon {
width: 25%;
box-sizing: border-box;
padding: 8px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.app-icon-image {
width: 72px;
height: 72px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
cursor: pointer;
background-color: #2d2d2d;
}
.app-icon-image:hover {
background-color: #3d3d3d;
}
.app-icon-image img {
width: 48px;
height: 48px;
pointer-events: none;
}
.app-icon-label {
font-size: 14px;
color: #FFFFFF;
text-align: center;
}
.home-dock {
display: flex;
justify-content: space-around;
align-items: center;
padding: 12px 16px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 24px 24px 0 0;
}
.dock-item {
width: 56px;
height: 56px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dock-item:hover {
opacity: 0.9;
}
.dock-item img {
width: 32px;
height: 32px;
pointer-events: none;
}
/* Camera Content */
.camera-content {
width: 100%;
height: 100%;
background-color: #000000;
display: flex;
flex-direction: column;
position: relative;
}
.camera-top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
}
.camera-btn {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.camera-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.camera-btn img {
width: 28px;
height: 28px;
}
.viewfinder-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.viewfinder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 50%, #1a1a1a 100%);
display: flex;
align-items: center;
justify-content: center;
}
.viewfinder-text {
color: #666666;
font-size: 18px;
}
.focus-indicator {
position: absolute;
width: 80px;
height: 80px;
border: 2px solid #FFFFFF;
border-radius: 8px;
opacity: 0.7;
}
.camera-bottom-bar {
padding: 24px;
display: flex;
justify-content: space-around;
align-items: center;
background-color: rgba(0, 0, 0, 0.6);
}
.gallery-preview {
width: 48px;
height: 48px;
border-radius: 8px;
background-color: #333333;
}
.shutter-btn {
width: 72px;
height: 72px;
border-radius: 36px;
background-color: #FFFFFF;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.shutter-btn:hover {
background-color: #E0E0E0;
}
.shutter-btn-inner {
width: 60px;
height: 60px;
border-radius: 30px;
background-color: #FFFFFF;
border: 3px solid #000000;
}
.switch-camera-btn {
width: 48px;
height: 48px;
border-radius: 24px;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.switch-camera-btn:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.switch-camera-btn img {
width: 28px;
height: 28px;
}
.mode-selector {
display: flex;
justify-content: center;
gap: 24px;
padding: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.mode-item {
color: #888888;
font-size: 14px;
cursor: pointer;
}
.mode-item:hover {
color: #FFFFFF;
}
.mode-item.active {
color: #FFD700;
font-weight: bold;
}
/* Generic App Content - for apps without specific styles */
.app-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #121212;
}
.app-header {
padding: 16px;
background-color: #1a1a1a;
}
.app-header-title {
font-size: 24px;
font-weight: bold;
color: #FFFFFF;
}
.app-body {
flex: 1;
padding: 16px;
overflow: auto;
}
.placeholder-text {
color: #888888;
font-size: 18px;
text-align: center;
margin-top: 100px;
}
</style>
</head>
<body class="shell" onload="initShell(document)">
<!-- System Status Bar -->
<div class="shell-status-bar" onclick="toggleNotifications()">
<span id="shell-time" class="shell-status-bar-time">12:30</span>
<div class="shell-status-bar-icons">
<img src="../../icons/wifi.tga"/>
<img src="../../icons/signal.tga"/>
<img src="../../icons/battery.tga"/>
</div>
</div>
<!-- App Content Container -->
<div id="app-container">
<!-- App content will be loaded here dynamically -->
</div>
<!-- System Navigation Bar -->
<div class="shell-nav-bar">
<div class="shell-nav-btn" onclick="shellGoBack()">
<img src="../../icons/back.tga"/>
</div>
<div class="shell-nav-btn" onclick="shellGoHome()">
<div class="shell-home-indicator"></div>
</div>
<div class="shell-nav-btn" onclick="shellShowRecents()">
<img src="../../icons/menu.tga"/>
</div>
</div>
<!-- ===== OVERLAY LAYERS ===== -->
<!-- Notification Panel (swipe down from status bar) -->
<div id="notification-panel">
<div class="notification-list" id="notification-list">
<!-- Notifications will be added here dynamically -->
</div>
</div>
<!-- Toast Container -->
<div id="toast-container">
<!-- Toasts will be added here dynamically -->
</div>
<!-- Loading Overlay -->
<div id="loading-overlay">
<div class="loading-spinner"></div>
</div>
<!-- Dialog/Modal Overlay -->
<div id="dialog-overlay">
<div class="dialog-box">
<div id="dialog-title" class="dialog-title">Title</div>
<div id="dialog-message" class="dialog-message">Message</div>
<div class="dialog-buttons">
<div class="dialog-btn dialog-btn-cancel" onclick="dialogCancel()">Cancel</div>
<div class="dialog-btn dialog-btn-confirm" onclick="dialogConfirm()">OK</div>
</div>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,11 @@
<!-- Store App Content Fragment -->
<!-- Styles are in shell.rml -->
<div class="app-content">
<div class="app-header">
<span class="app-header-title">Store</span>
</div>
<div class="app-body">
<span class="placeholder-text">Store App</span>
</div>
</div>