<?php
// Force login + disable caching
session_start();
require_once __DIR__ . '/../core/db_config.php';
// Set page title for header
$GLOBALS['page_title'] = 'App Dashboard';
// Anti-cache headers (dev)
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Expires: 0');
// Redirect if not logged in
if (empty($_SESSION['username'])) {
$redirect = urlencode($_SERVER['REQUEST_URI'] ?? '/');
header("Location: /core/auth/login.php?redirect={$redirect}");
exit;
}
// Cache-busting helper (absolute & relative paths)
function asset($path) {
$isAbsolute = strlen($path) && $path[0] === '/';
$abs = $isAbsolute ? rtrim($_SERVER['DOCUMENT_ROOT'], '/') . $path : __DIR__ . '/' . $path;
$v = is_file($abs) ? filemtime($abs) : time();
return $path . '?v=' . $v;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>App Index</title>
<!-- Shared overlay CSS -->
<link rel="stylesheet" href="<?= asset('/core/css/overlay.css') ?>">
<style>
/* Global */
html, body {
height: 100%;
margin: 0;
overscroll-behavior-y: contain;
}
body {
background: linear-gradient(135deg, #0a0f1c 0%, #1e293b 100%);
color: #f1f5f9;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Inter, "Helvetica Neue", Arial;
line-height: 1.6;
}
.body-lock {
overflow: hidden !important;
touch-action: none;
}
/* Topbar */
.topbar {
position: sticky;
top: 0;
z-index: 50;
background: linear-gradient(135deg, rgba(10, 15, 28, 0.95) 0%, rgba(30, 41, 59, 0.95) 100%);
border-bottom: 1px solid rgba(71, 85, 105, 0.3);
backdrop-filter: blur(20px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.topbar-inner {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
/* Chips row: takes remaining space and scrolls horizontally */
#buttonRow {
flex: 1 1 auto;
min-width: 0;
display: flex;
gap: 0.875rem;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(148, 163, 184, 0.5) transparent;
-webkit-overflow-scrolling: touch;
padding: 0.25rem 0;
}
#buttonRow::-webkit-scrollbar {
height: 4px;
}
#buttonRow::-webkit-scrollbar-track {
background: transparent;
}
#buttonRow::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.5);
border-radius: 2px;
}
/* 3-dots container: pinned at far right */
#menuContainer {
flex: 0 0 auto;
margin-left: 0.5rem;
position: relative;
}
.chip {
flex: 0 0 auto;
border: 1px solid rgba(71, 85, 105, 0.4);
background: linear-gradient(135deg, rgba(30, 41, 59, 0.8) 0%, rgba(51, 65, 85, 0.8) 100%);
color: #f1f5f9;
padding: 0.625rem 1.125rem;
border-radius: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(10px);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
white-space: nowrap;
font-size: 0.9rem;
}
.chip:hover {
background: linear-gradient(135deg, rgba(51, 65, 85, 0.9) 0%, rgba(71, 85, 105, 0.9) 100%);
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.chip:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4);
transform: translateY(-1px);
}
.chip:active {
transform: translateY(0);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
/* Content */
.container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
.hero {
text-align: center;
padding: 3rem 0;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 51, 234, 0.1) 100%);
border-radius: 1rem;
border: 1px solid rgba(71, 85, 105, 0.3);
backdrop-filter: blur(10px);
margin-bottom: 2rem;
}
.hero h1 {
font-size: 2.5rem;
font-weight: 800;
margin: 0 0 1rem 0;
background: linear-gradient(135deg, #60a5fa, #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.025em;
}
.lead {
color: #94a3b8;
font-size: 1.125rem;
max-width: 600px;
margin: 0 auto;
line-height: 1.7;
}
/* 3-dots dropdown */
.menu-trigger {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
border-radius: 0.75rem;
}
.menu-list {
display: none;
position: absolute;
right: 0;
top: calc(100% + 8px);
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%);
border: 1px solid rgba(71, 85, 105, 0.4);
border-radius: 0.75rem;
min-width: 200px;
padding: 0.5rem;
z-index: 9999;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
backdrop-filter: blur(20px);
animation: menu-fade-in 0.2s ease-out;
}
.menu-list.open { display: block; }
@keyframes menu-fade-in {
from {
opacity: 0;
transform: translateY(-8px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.menu-item {
display: block;
width: 100%;
text-align: left;
background: none;
border: none;
color: #f1f5f9;
padding: 0.75rem 1rem;
cursor: pointer;
font: inherit;
border-radius: 0.5rem;
transition: all 0.15s ease;
font-weight: 500;
}
.menu-item:hover {
background: rgba(51, 65, 85, 0.6);
transform: translateX(2px);
}
/* Mobile responsive */
@media (max-width: 768px) {
.topbar-inner {
padding: 0.875rem;
}
.hero {
padding: 2rem 1rem;
margin: 1rem 0;
}
.hero h1 {
font-size: 2rem;
}
.container {
margin: 1rem auto;
padding: 0 0.875rem;
}
}
@media (max-width: 640px) {
.topbar-inner {
gap: 0.75rem;
}
#buttonRow {
gap: 0.625rem;
}
.chip {
padding: 0.5rem 0.875rem;
font-size: 0.85rem;
}
.hero h1 {
font-size: 1.75rem;
}
.lead {
font-size: 1rem;
}
.menu-trigger {
width: 40px;
height: 40px;
font-size: 1.125rem;
}
}
@media (max-width: 480px) {
.topbar-inner {
padding: 0.75rem;
}
.chip {
padding: 0.425rem 0.75rem;
font-size: 0.8rem;
}
.hero {
padding: 1.5rem 0.75rem;
}
.hero h1 {
font-size: 1.5rem;
}
.menu-list {
min-width: 180px;
}
.menu-item {
padding: 0.625rem 0.875rem;
font-size: 0.9rem;
}
}
</style>
</head>
<body>
<?php include __DIR__ . '/../core/auth/header.php'; ?>
<header class="topbar" aria-label="Top navigation">
<div class="topbar-inner">
<div id="buttonRow" role="tablist" aria-label="App sections"></div>
<div id="menuContainer" aria-label="More actions"></div>
</div>
</header>
<main class="container">
<div class="hero">
<h1>Modular Overlay System</h1>
<p class="lead">Each module adds its own button and overlay content. Experience seamless navigation with our modern, responsive interface.</p>
</div>
</main>
<!-- 1) Empty globals for modules -->
<script>
window.AppItems = []; // overlay sections
window.AppMenu = []; // 3-dots actions (optional)
</script>
<!-- 2) Shared overlay -->
<script src="<?= asset('/core/js/overlay.js') ?>" defer></script>
<!-- 3) Modules -->
<script src="<?= asset('files.js') ?>" defer></script>
<script src="<?= asset('menu.js') ?>" defer></script>
<script src="<?= asset('cutout.js') ?>" defer></script>
<script src="<?= asset('gameObject.js') ?>" defer></script>
<!-- 4) Render chips + menu -->
<script defer>
document.addEventListener('DOMContentLoaded', () => {
// Buttons from AppItems
const row = document.getElementById('buttonRow');
row.innerHTML = '';
(window.AppItems || []).forEach((item, i) => {
const btn = document.createElement('button');
btn.className = 'chip';
btn.textContent = item.title || `Item ${i+1}`;
btn.setAttribute('role', 'tab');
btn.setAttribute('aria-selected', 'false');
btn.onclick = () => {
if (window.AppOverlay) {
AppOverlay.open(window.AppItems, i, btn);
}
};
row.appendChild(btn);
});
// 3-dots menu
const menuContainer = document.getElementById('menuContainer');
menuContainer.innerHTML = '';
const menuItems = window.AppMenu || [];
if (menuItems.length > 0) {
const trigger = document.createElement('button');
trigger.className = 'chip menu-trigger';
trigger.type = 'button';
trigger.setAttribute('aria-haspopup','true');
trigger.setAttribute('aria-expanded','false');
trigger.setAttribute('aria-label','More actions');
trigger.innerHTML = '⋮';
menuContainer.appendChild(trigger);
const dropdown = document.createElement('div');
dropdown.className = 'menu-list';
dropdown.setAttribute('role','menu');
dropdown.setAttribute('aria-hidden','true');
menuContainer.appendChild(dropdown);
menuItems.forEach((m, idx) => {
const item = document.createElement('button');
item.className = 'menu-item';
item.type = 'button';
item.setAttribute('role','menuitem');
item.textContent = m.label || `Action ${idx+1}`;
item.onclick = () => {
closeMenu();
try {
if (m.action) m.action();
} catch(e) {
console.error('Menu action error:', e);
}
};
dropdown.appendChild(item);
});
function openMenu(){
dropdown.classList.add('open');
dropdown.setAttribute('aria-hidden','false');
trigger.setAttribute('aria-expanded','true');
document.addEventListener('click', handleOutside);
document.addEventListener('keydown', escClose);
// Focus first menu item for keyboard navigation
const firstItem = dropdown.querySelector('.menu-item');
if (firstItem) firstItem.focus();
}
function closeMenu(){
dropdown.classList.remove('open');
dropdown.setAttribute('aria-hidden','true');
trigger.setAttribute('aria-expanded','false');
document.removeEventListener('click', handleOutside);
document.removeEventListener('keydown', escClose);
trigger.focus(); // Return focus to trigger
}
function handleOutside(e){
if (!menuContainer.contains(e.target)) closeMenu();
}
function escClose(e){
if (e.key === 'Escape') {
e.preventDefault();
closeMenu();
}
}
// Keyboard navigation in menu
dropdown.addEventListener('keydown', (e) => {
const items = dropdown.querySelectorAll('.menu-item');
const currentIndex = Array.from(items).indexOf(document.activeElement);
switch(e.key) {
case 'ArrowDown':
e.preventDefault();
const nextIndex = (currentIndex + 1) % items.length;
items[nextIndex].focus();
break;
case 'ArrowUp':
e.preventDefault();
const prevIndex = (currentIndex - 1 + items.length) % items.length;
items[prevIndex].focus();
break;
case 'Home':
e.preventDefault();
items[0].focus();
break;
case 'End':
e.preventDefault();
items[items.length - 1].focus();
break;
}
});
trigger.addEventListener('click', (e) => {
e.stopPropagation();
dropdown.classList.contains('open') ? closeMenu() : openMenu();
});
}
});
</script>
</body>
</html>