Split web root into desktop and mobile

This commit is contained in:
2026-02-02 23:59:30 +01:00
parent 4c15f0685c
commit c685c27eac
22 changed files with 1444 additions and 9 deletions

View File

@@ -4,15 +4,15 @@
Alles, was später auf den Webserver gehört, lebt unter `web/`. Die anderen Ordner dokumentieren Planung und Infos, die nicht direkt ausgeliefert werden.
## Struktur
- `web/public/`: Webserver-Entry-Point und Assets (CSS/JS) für die HUD-Demo.
- `web/src/partials/`: Die PHP-Partial-Templates für Menüs, Ressourcen und Content.
- `web/desktop/`: Desktop-geeigneter Build mit eigenem `public/` (Entry-Point) und `src/partials/`.
- `web/mobile/`: Mobile-Version (aktuell ein Spiegel des Desktop-Builds; anpassbar für responsive Varianten).
- `docs/`: Projektdokumentation; siehe `docs/README.md` für Details zur Anwendung.
- `planning/`: Freifläche für Skizzen, Notizen oder Quelltext, der nicht ins Webroot gehört.
## Entwicklung
1. `cd /path/to/Space-Theme`
2. `php -S localhost:8000 -t web/public`
3. Öffne `http://localhost:8000/index.php?s=overview&p=dashboard`
2. `php -S localhost:8000 -t web/desktop/public`
3. Öffne `http://localhost:8000/index.php?s=overview&p=dashboard` (für mobile Tests `-t web/mobile/public`).
## Weitere Infos
- Die ausführliche Demo-Beschreibung und Abläufe stehen in `docs/README.md`.

View File

@@ -4,9 +4,10 @@
Diese Demo zeigt eine HUD-artige Navigation mit statischem Hauptmenü, kontextabhängiger Subnav, Sticky-Ressourcenleiste, Toasts und einem generischen Content-Bereich. Das HTML wird vom zentralen Entry-Point `public/index.php` zusammengesetzt, die einzelnen Bausteine liegen als Partial-Templates in `src/partials`.
## Verzeichnisstruktur
- `web/public/`: Dokumentenwurzel, in der der Entry Point sitzt und auf die Assets unter `web/public/assets/` zugreift. Alles, was ausgeliefert wird, gehört hierhin.
- `web/public/assets/`: CSS- und JS-Dateien (`style.css`, `ui.js`, `starfield.js`), die das HUD und den Starfield-Canvas bedienen.
- `web/src/partials/`: Menü-, Footer-, Ressourcendarstellung und Content-Renderer, die `web/public/index.php` inkludiert.
- `web/desktop/public/`: Dokumentenwurzel für den Desktop-Entry-Point und seine Assets (`assets/`).
- `web/desktop/src/partials/`: Die PHP-Partial-Templates für Menüs, Ressourcen und Content im Desktop-Build.
- `web/mobile/public/`: Mobile-Entry-Point; derzeit eine Kopie des Desktop-Systems, die für responsive Änderungen angepasst werden kann.
- `web/mobile/src/partials/`: Mobile-Partial-Templates (momentan identisch zum Desktop-Set).
## Layout-Highlights
- **Sidebar**: Hauptmenü (links) mit statischem Link-Set plus optionaler Planetenliste auf der Unterseite.
@@ -18,8 +19,8 @@ Diese Demo zeigt eine HUD-artige Navigation mit statischem Hauptmenü, kontextab
## Lokale Entwicklung
1. `cd /path/to/Space-Theme`.
2. `php -S localhost:8000 -t web/public` (setzt die Dokumentenwurzel auf `web/public/`).
3. Öffne `http://localhost:8000/index.php` und wechsle z.B. über `?s=build&p=demolish` die Sections.
2. `php -S localhost:8000 -t web/desktop/public` (setzt die Dokumentenwurzel auf den Desktop-Build).
3. Öffne `http://localhost:8000/index.php` und wechsle z.B. über `?s=build&p=demolish` die Sections oder wechsle mit `-t web/mobile/public` zur mobilen Variante.
## Hinweise
- Die PHP-Session (`session_start()`) im Entry-Point dient Flash-Toasts und Alert-Messages.

View File

@@ -0,0 +1,114 @@
(() => {
const canvas = document.getElementById("starfield");
if (!canvas) return;
const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;
if (reduceMotion) return;
const ctx = canvas.getContext("2d", { alpha: true });
const cfg = {
get perf(){ return (document.documentElement.dataset.perf || "auto").toLowerCase(); },
starCount(){ return this.perf === "high" ? 1100 : this.perf === "medium" ? 650 : this.perf === "low" ? 0 : 500; },
fps(){ return this.perf === "high" ? 60 : this.perf === "medium" ? 30 : this.perf === "low" ? 0 : 25; },
dpr(){ return this.perf === "high" ? Math.min(devicePixelRatio||1, 2) : 1; },
twinkle(){ return this.perf === "high" ? 0.45 : 0.35; },
};
let w=0,h=0,dpr=1,stars=[],mouseX=0,mouseY=0,last=performance.now(),acc=0;
let lastPerf = "";
function rand(min,max){ return min + Math.random()*(max-min); }
function newStar(randomZ=false){
return { x: rand(-1,1), y: rand(-1,1), z: randomZ ? Math.random() : 1, r: rand(0.6,1.7), t: Math.random()*Math.PI*2 };
}
function initStars(){
const n = cfg.starCount();
stars = Array.from({length:n}, ()=>newStar(true));
}
function resize(){
dpr = cfg.dpr();
w = Math.floor(window.innerWidth);
h = Math.floor(window.innerHeight);
canvas.width = Math.floor(w*dpr);
canvas.height = Math.floor(h*dpr);
canvas.style.width = w+"px";
canvas.style.height = h+"px";
ctx.setTransform(dpr,0,0,dpr,0,0);
initStars();
}
function project(s){
const cx=w*0.5, cy=h*0.5;
const p = 1/(s.z*1.25);
return { x: cx + s.x*cx*p, y: cy + s.y*cy*p, size: s.r*p };
}
function tick(dt){
ctx.clearRect(0,0,w,h);
const g = ctx.createRadialGradient(w*0.5,h*0.45,0,w*0.5,h*0.45,Math.min(w,h)*0.85);
g.addColorStop(0,"rgba(66,245,255,0.04)");
g.addColorStop(0.5,"rgba(255,61,242,0.02)");
g.addColorStop(1,"rgba(0,0,0,0)");
ctx.fillStyle=g;
ctx.fillRect(0,0,w,h);
const mx=(mouseX-w*0.5)/(w*0.5), my=(mouseY-h*0.5)/(h*0.5);
const speed = (cfg.perf==="high" ? 0.03 : 0.022);
for(let i=0;i<stars.length;i++){
const s=stars[i];
s.z -= speed*dt;
if(s.z<=0.02) stars[i]=newStar(false);
s.t += dt*rand(2.0,5.0);
const p=project(s);
if(p.x<-60||p.x>w+60||p.y<-60||p.y>h+60) continue;
const tw = 0.7 + Math.sin(s.t)*cfg.twinkle()*0.25;
const alpha = Math.min(0.95, (0.18 + (1-s.z)*0.85)*tw);
const par = 0.22*(1-s.z);
const x2 = p.x + mx*18*par;
const y2 = p.y + my*12*par;
ctx.fillStyle = `rgba(240,247,255,${alpha})`;
ctx.beginPath();
ctx.arc(x2,y2,Math.max(0.7,p.size*0.55),0,Math.PI*2);
ctx.fill();
}
}
function frame(now){
const perf = cfg.perf;
if(perf !== lastPerf){
lastPerf = perf;
resize();
}
const fps = cfg.fps();
if(fps <= 0){ requestAnimationFrame(frame); return; }
const dt = Math.min((now-last)/1000, 0.05);
last = now;
acc += dt;
const step = 1/fps;
while(acc >= step){
tick(step);
acc -= step;
}
requestAnimationFrame(frame);
}
window.addEventListener("mousemove",(e)=>{ mouseX=e.clientX; mouseY=e.clientY; }, {passive:true});
window.addEventListener("resize", resize, {passive:true});
resize();
mouseX = window.innerWidth*0.5; mouseY = window.innerHeight*0.5;
requestAnimationFrame(frame);
})();

View File

@@ -0,0 +1,733 @@
:root{
--gap: 16px;
--radius: 18px;
--bg0: #050510;
--bg1: #070a1f;
--card0: rgba(10, 14, 40, .65);
--card1: rgba(16, 22, 60, .55);
--border: rgba(145, 220, 255, .18);
--text: rgba(240, 247, 255, .92);
--muted: rgba(200, 220, 255, .65);
--neon-cyan: #42f5ff;
--neon-blue: #5b7cff;
--neon-pink: #ff3df2;
--neon-green: #3dffb5;
--neon-warn: #ffd34d;
--danger: #ff3b6a;
--shadow: 0 12px 40px rgba(0,0,0,.45);
--glow-cyan: 0 0 18px rgba(66,245,255,.35), 0 0 46px rgba(66,245,255,.18);
--glow-pink: 0 0 18px rgba(255,61,242,.28), 0 0 46px rgba(255,61,242,.16);
--sidebarW: 320px;
--font-ui: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
--font-sci: "Orbitron", system-ui, sans-serif;
}
/* Performance profiles */
html[data-perf="low"] .starfield{ display:none; }
html[data-perf="low"] .space-bg::after{ display:none; } /* planet off in low */
*{ box-sizing: border-box; }
html, body{ height: 100%; }
body{
margin: 0;
color: var(--text);
font-family: var(--font-ui);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background:
radial-gradient(1200px 800px at 20% 10%, rgba(91,124,255,.18), transparent 65%),
radial-gradient(900px 700px at 80% 0%, rgba(255,61,242,.12), transparent 60%),
radial-gradient(900px 800px at 60% 110%, rgba(66,245,255,.10), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
overflow-x: hidden;
}
/* Starfield Canvas */
.starfield{
position: fixed;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: .98;
}
/* Nebula/Planet Overlay */
.space-bg{
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
}
.space-bg::before{
content:"";
position:absolute;
inset: 0;
background:
radial-gradient(900px 600px at 20% 15%, rgba(91,124,255,.12), transparent 60%),
radial-gradient(700px 500px at 80% 10%, rgba(255,61,242,.10), transparent 55%),
radial-gradient(900px 700px at 60% 110%, rgba(66,245,255,.08), transparent 60%);
opacity: .9;
}
.space-bg::after{
content:"";
position:absolute;
width: 420px;
height: 420px;
left: 72%;
top: 55%;
border-radius: 50%;
background:
radial-gradient(closest-side at 30% 30%, rgba(255,255,255,.20), transparent 55%),
radial-gradient(closest-side at 55% 60%, rgba(66,245,255,.16), transparent 62%),
radial-gradient(closest-side at 70% 75%, rgba(0,0,0,.50), rgba(0,0,0,.80) 70%),
radial-gradient(circle at 40% 40%, rgba(90,140,255,.30), rgba(40,60,120,.14) 55%, rgba(0,0,0,0) 72%);
box-shadow: 0 0 50px rgba(91,124,255,.18), 0 0 120px rgba(255,61,242,.09);
animation: planetFloat 90s ease-in-out infinite;
opacity: .58;
}
@keyframes planetFloat{
0% { transform: translate(-40px,-20px) scale(1); }
50% { transform: translate(30px,10px) scale(1.02); }
100% { transform: translate(-40px,-20px) scale(1); }
}
/* Layout */
.container{
width: min(1280px, 100% - 32px);
margin-inline: auto;
padding-block: 16px;
position: relative;
z-index: 2;
}
.container::before{
content:"";
position:absolute;
inset:-14px;
pointer-events:none;
background:
radial-gradient(1200px 700px at 50% 40%, transparent 55%, rgba(0,0,0,.55) 85%),
linear-gradient(135deg, rgba(255,255,255,.05), transparent 35%);
border-radius: 28px;
opacity: .55;
filter: blur(.2px);
}
.app{
min-height: calc(100vh - 32px);
display:grid;
gap: var(--gap);
grid-template-columns: var(--sidebarW) 1fr;
grid-template-rows: auto auto auto auto 1fr auto;
grid-template-areas:
"sidebar topbar"
"sidebar alertbar"
"sidebar subnav"
"sidebar resourcebar"
"sidebar content"
"footer footer";
}
.sidebar{ grid-area: sidebar; }
.topbar{ grid-area: topbar; }
.alertbar{ grid-area: alertbar; }
.subnav{ grid-area: subnav; }
.resourcebar{ grid-area: resourcebar; }
.content{ grid-area: content; }
.footer{ grid-area: footer; }
.sidebar{
display:grid;
gap: var(--gap);
align-content:start;
position: sticky;
top: 16px;
max-height: calc(100vh - 32px);
overflow: auto;
padding-right: 4px;
}
/* Cards / Panels + Hologramm */
.card{
border-radius: var(--radius);
border: 1px solid var(--border);
background: linear-gradient(180deg, rgba(10, 14, 40, .65), rgba(16, 22, 60, .55));
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
.panel{ padding: 14px 16px; }
.card::after{
content:"";
position:absolute;
inset:-2px;
border-radius: inherit;
background:
radial-gradient(600px 120px at 20% 0%, rgba(66,245,255,.18), transparent 70%),
radial-gradient(500px 120px at 80% 0%, rgba(255,61,242,.12), transparent 70%);
pointer-events:none;
opacity:.85;
}
.card::before{
content:"";
position:absolute;
inset:0;
pointer-events:none;
background:
repeating-linear-gradient(180deg, rgba(66,245,255,.00) 0px, rgba(66,245,255,.00) 3px, rgba(66,245,255,.05) 4px);
opacity: .22;
mix-blend-mode: screen;
}
@keyframes hudShimmer{
0% { transform: translateX(-120%) skewX(-20deg); opacity: 0; }
20% { opacity: .35; }
100% { transform: translateX(120%) skewX(-20deg); opacity: 0; }
}
.card:hover .panel::after{
content:"";
position:absolute;
top:0; left:0;
height:100%;
width:40%;
background: linear-gradient(90deg, transparent, rgba(66,245,255,.18), transparent);
filter: blur(2px);
pointer-events:none;
animation: hudShimmer 1.1s ease;
}
/* Typography */
.panel-title,
.brand-title,
.hud-title,
h1,h2,h3,
button,.btn,input[type="submit"], .chip, .tab,
label,.label{
font-family: var(--font-sci);
letter-spacing: .6px;
}
.hud-title{ margin: 0 0 6px; text-transform: uppercase; }
.h2{ margin: 0 0 10px; }
.muted{ color: var(--muted); font-size: .95rem; }
.tiny{ font-size: .8rem; }
.divider{ height: 1px; background: rgba(145,220,255,.14); margin: 14px 0; }
.w-full{ display:block; width: 100%; text-align:center; }
/* Topbar */
.topbar{ position: relative; }
.topbar-inner{
display:flex;
align-items:center;
justify-content:space-between;
gap: 12px;
flex-wrap: wrap;
}
.brand{ display:flex; align-items:center; gap: 12px; }
.brand-dot{
width: 12px; height: 12px;
border-radius: 50%;
background: linear-gradient(180deg, rgba(66,245,255,.85), rgba(255,61,242,.55));
box-shadow: var(--glow-cyan);
}
.top-actions{ display:flex; gap: 10px; align-items:center; flex-wrap: wrap; }
.iconbtn{
border: 1px solid rgba(145,220,255,.18);
background: rgba(0,0,0,.18);
color: var(--text);
border-radius: 14px;
padding: 9px 12px;
cursor:pointer;
font-family: var(--font-sci);
letter-spacing: .6px;
}
.iconbtn:hover{ box-shadow: var(--glow-cyan); border-color: rgba(66,245,255,.45); transform: translateY(-1px); }
.badge{
display:inline-block;
margin-left: 8px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid rgba(255,61,242,.25);
background: rgba(255,61,242,.10);
font-size: .8rem;
}
/* Notification panel */
.notif{
position: absolute;
right: 12px;
top: calc(100% + 10px);
width: min(360px, 100%);
border-radius: 16px;
border: 1px solid rgba(145,220,255,.18);
background: linear-gradient(180deg, rgba(10, 14, 40, .92), rgba(16, 22, 60, .75));
box-shadow: var(--shadow);
overflow: hidden;
}
.notif-head{
display:flex;
align-items:center;
justify-content:space-between;
padding: 10px 12px;
border-bottom: 1px solid rgba(145,220,255,.12);
}
.notif-list{ display:grid; }
.notif-item{
display:flex;
gap: 10px;
align-items:center;
padding: 10px 12px;
border: 0;
background: transparent;
color: var(--text);
text-align:left;
cursor:pointer;
}
.notif-item:hover{ background: rgba(66,245,255,.06); }
.notif-ico{ width: 24px; text-align:center; }
.notif-title{ display:block; font-family: var(--font-sci); letter-spacing:.6px; }
.notif-meta{ display:block; color: var(--muted); font-size: .82rem; margin-top: 2px; }
/* Subnav (Kontextmenü in der Mitte) */
.subnav{
display:flex;
align-items:center;
justify-content:space-between;
gap: 12px;
flex-wrap: wrap;
}
.subnav-tabs{ display:flex; gap: 10px; flex-wrap: wrap; }
.tab{
display:inline-flex;
align-items:center;
padding: 8px 12px;
border-radius: 999px;
border: 1px solid rgba(145,220,255,.18);
background: rgba(0,0,0,.18);
color: var(--text);
text-decoration:none;
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.tab:hover{ transform: translateY(-1px); border-color: rgba(66,245,255,.45); box-shadow: var(--glow-cyan); }
.tab.is-active{
border-color: rgba(66,245,255,.55);
box-shadow: var(--glow-cyan);
background: linear-gradient(180deg, rgba(66,245,255,.18), rgba(91,124,255,.10));
}
/* Main menu list */
.navlist{ list-style:none; padding:0; margin: 12px 0 0; display:grid; gap: 10px; }
.navlist a{
display:flex;
align-items:center;
justify-content:flex-start;
gap: 10px;
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(145,220,255,.14);
background: rgba(0,0,0,.18);
transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease;
text-decoration: none;
color: var(--text);
}
.navlist a:hover{
transform: translateY(-1px);
border-color: rgba(66,245,255,.35);
box-shadow: var(--glow-cyan);
}
.navlist a.is-active{
border-color: rgba(66,245,255,.55);
box-shadow: var(--glow-cyan);
}
.mi{ width: 22px; text-align:center; }
/* Planet list */
.planetlist{ list-style:none; padding:0; margin: 12px 0 0; display:grid; gap: 10px; }
.planetlist a{
display:flex;
align-items:center;
gap: 10px;
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(145,220,255,.14);
background: rgba(0,0,0,.18);
text-decoration: none;
color: var(--text);
justify-content:space-between;
}
.planetlist a:hover{ border-color: rgba(66,245,255,.35); box-shadow: var(--glow-cyan); transform: translateY(-1px); }
.planetlist a.is-active{ border-color: rgba(66,245,255,.55); box-shadow: var(--glow-cyan); }
.pl-name{ flex: 1; }
.pl-dot{
width: 10px; height: 10px; border-radius: 999px;
background: rgba(145,220,255,.20);
box-shadow: 0 0 10px rgba(255,255,255,.08);
}
.pl-dot.on{ background: var(--neon-green); box-shadow: 0 0 18px rgba(61,255,181,.24); }
.navhint{
color: rgba(200,220,255,.55);
font-size: .8rem;
border: 1px solid rgba(145,220,255,.16);
border-radius: 10px;
padding: 2px 8px;
background: rgba(0,0,0,.18);
}
/* Buttons */
button, .btn, input[type="submit"]{
appearance:none;
border: 1px solid rgba(66,245,255,.28);
background: linear-gradient(180deg, rgba(66,245,255,.18), rgba(91,124,255,.10));
color: var(--text);
border-radius: 14px;
padding: 10px 14px;
cursor:pointer;
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease, filter .15s ease;
text-decoration: none;
display: inline-block;
}
button:hover, .btn:hover, input[type="submit"]:hover{
transform: translateY(-1px);
border-color: rgba(66,245,255,.55);
box-shadow: var(--glow-cyan);
filter: brightness(1.08);
}
button:active, .btn:active, input[type="submit"]:active{ transform: translateY(0) scale(.99); }
.btn-primary{
border-color: rgba(255,61,242,.32);
background: linear-gradient(180deg, rgba(255,61,242,.18), rgba(66,245,255,.08));
}
.btn-primary:hover{ box-shadow: var(--glow-pink); }
.btn-mini{ padding: 7px 10px; border-radius: 12px; font-size: .85rem; }
.actions{ display:flex; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
/* Resourcebar sticky */
.resourcebar{
position: sticky;
top: 16px;
z-index: 5;
}
.resource-row, .footer-row{
display:flex;
align-items:center;
justify-content:space-between;
gap: 12px;
flex-wrap: wrap;
}
.stats{ display:flex; gap: 12px; flex-wrap: wrap; }
.stat{
border: 1px solid rgba(145,220,255,.14);
background: rgba(0,0,0,.18);
border-radius: 14px;
padding: 8px 12px;
min-width: 160px;
}
.stat-k{ color: var(--muted); font-size: .86rem; }
.stat-v{ font-family: var(--font-sci); letter-spacing: .6px; margin-top: 2px; }
.dot{
display:inline-block;
width: 8px; height: 8px;
border-radius: 999px;
margin-right: 8px;
box-shadow: 0 0 14px rgba(255,255,255,.12);
}
.dot-cyan{ background: var(--neon-cyan); box-shadow: var(--glow-cyan); }
.dot-pink{ background: var(--neon-pink); box-shadow: var(--glow-pink); }
.dot-green{ background: var(--neon-green); box-shadow: 0 0 18px rgba(61,255,181,.24); }
.dot-warn{ background: var(--neon-warn); box-shadow: 0 0 18px rgba(255,211,77,.18); }
.stat-bar{
margin-top: 8px;
height: 6px;
border-radius: 999px;
background: rgba(145,220,255,.10);
overflow: hidden;
}
.stat-bar > span{
display:block;
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, rgba(66,245,255,.55), rgba(255,61,242,.35));
}
/* Cockpit Window */
.cockpit{ padding-top: 10px; }
.cockpit-hud{
display:flex;
align-items:center;
justify-content:space-between;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 12px;
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(145,220,255,.16);
background: rgba(0,0,0,.18);
}
.hud-left, .hud-right{
font-family: var(--font-sci);
letter-spacing: .7px;
font-size: .85rem;
color: rgba(240,247,255,.88);
text-transform: uppercase;
}
.hud-pill{
display:inline-block;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid rgba(66,245,255,.20);
background: rgba(0,0,0,.16);
margin-left: 8px;
}
.cockpit::after{
content:"";
position:absolute;
inset: 10px;
border-radius: calc(var(--radius) - 6px);
pointer-events:none;
border: 1px solid rgba(66,245,255,.22);
box-shadow: 0 0 22px rgba(66,245,255,.14), inset 0 0 20px rgba(255,61,242,.06);
}
.cockpit::before{
content:"";
position:absolute;
inset: 0;
pointer-events:none;
background:
linear-gradient(90deg, rgba(66,245,255,.35), rgba(66,245,255,0)) 0 0 / 120px 2px no-repeat,
linear-gradient(180deg, rgba(66,245,255,.35), rgba(66,245,255,0)) 0 0 / 2px 120px no-repeat,
linear-gradient(90deg, rgba(66,245,255,0), rgba(66,245,255,.35)) 100% 0 / 120px 2px no-repeat,
linear-gradient(180deg, rgba(66,245,255,.35), rgba(66,245,255,0)) 100% 0 / 2px 120px no-repeat,
linear-gradient(90deg, rgba(66,245,255,.35), rgba(66,245,255,0)) 0 100% / 120px 2px no-repeat,
linear-gradient(180deg, rgba(66,245,255,0), rgba(66,245,255,.35)) 0 100% / 2px 120px no-repeat,
linear-gradient(90deg, rgba(66,245,255,0), rgba(66,245,255,.35)) 100% 100% / 120px 2px no-repeat,
linear-gradient(180deg, rgba(66,245,255,0), rgba(66,245,255,.35)) 100% 100% / 2px 120px no-repeat,
repeating-linear-gradient(90deg, rgba(66,245,255,.04) 0px, rgba(66,245,255,.04) 1px, transparent 1px, transparent 60px),
repeating-linear-gradient(180deg, rgba(66,245,255,.03) 0px, rgba(66,245,255,.03) 1px, transparent 1px, transparent 60px),
radial-gradient(900px 300px at 20% 0%, rgba(255,255,255,.06), transparent 55%);
opacity: .55;
mix-blend-mode: screen;
}
/* Timeline + Queue */
.grid-2{ display:grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.inner.card{ background: rgba(0,0,0,.10); }
.timeline{ display:grid; gap: 10px; margin-top: 10px; }
.tl-item{
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(145,220,255,.14);
background: rgba(0,0,0,.18);
}
.tl-dot{ display:inline-block; width: 8px; height: 8px; border-radius: 999px; margin-right: 10px; vertical-align: middle; }
.tl-cyan{ background: var(--neon-cyan); box-shadow: var(--glow-cyan); }
.tl-pink{ background: var(--neon-pink); box-shadow: var(--glow-pink); }
.tl-green{ background: var(--neon-green); box-shadow: 0 0 18px rgba(61,255,181,.24); }
.queue{ display:grid; gap: 12px; margin-top: 10px; }
.q-card{
border: 1px solid rgba(145,220,255,.14);
background: rgba(0,0,0,.18);
border-radius: 14px;
padding: 12px;
}
.q-head{ display:flex; gap: 10px; align-items:center; }
.q-bar{
margin-top: 10px;
height: 8px;
border-radius: 999px;
background: rgba(145,220,255,.10);
overflow: hidden;
}
.q-bar > span{
display:block;
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, rgba(66,245,255,.55), rgba(255,61,242,.35));
}
.q-meta{
display:flex;
align-items:center;
justify-content:space-between;
gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
color: var(--muted);
}
/* Settings segments */
.settings-row{ display:flex; gap: 14px; flex-wrap: wrap; align-items:flex-end; }
.settings-block{ min-width: 240px; }
.seg{ display:flex; gap: 10px; flex-wrap: wrap; }
.seg-btn{
border: 1px solid rgba(145,220,255,.18);
background: rgba(0,0,0,.18);
color: var(--text);
border-radius: 999px;
padding: 9px 12px;
cursor:pointer;
}
.seg-btn:hover{ box-shadow: var(--glow-cyan); border-color: rgba(66,245,255,.45); transform: translateY(-1px); }
.seg-btn.is-selected{
border-color: rgba(66,245,255,.55);
box-shadow: var(--glow-cyan);
background: linear-gradient(180deg, rgba(66,245,255,.18), rgba(91,124,255,.10));
}
.check{ display:flex; gap: 10px; align-items:center; margin-top: 6px; }
.check input{ accent-color: var(--neon-cyan); }
/* Alert pulse modes via html[data-alertpulse] */
html[data-alertpulse="burst"] { --alert-iter: 3; }
html[data-alertpulse="loop"] { --alert-iter: infinite; }
.alert.alert-pro{
position: relative;
display: flex;
gap: 12px;
align-items: flex-start;
padding: 14px 14px;
border-radius: 16px;
border: 1px solid rgba(255,59,106,.42);
background:
linear-gradient(180deg, rgba(255,59,106,.16), rgba(0,0,0,.18)),
repeating-linear-gradient(135deg, rgba(255,59,106,.06) 0px, rgba(255,59,106,.06) 10px, rgba(0,0,0,0) 10px, rgba(0,0,0,0) 22px);
box-shadow: 0 0 18px rgba(255,59,106,.22), 0 0 52px rgba(255,59,106,.10);
}
.alert.alert-pro::before{
content:"";
position:absolute;
inset:-10px;
border-radius: 18px;
pointer-events:none;
background: radial-gradient(closest-side at 20% 30%, rgba(255,59,106,.30), rgba(255,59,106,.10) 45%, rgba(0,0,0,0) 70%);
opacity: .55;
transform: scale(1);
animation: alertPulse 1.25s ease-in-out 0s var(--alert-iter) both;
}
.alert-led{
flex: 0 0 auto;
width: 12px;
height: 12px;
border-radius: 999px;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.30), rgba(255,59,106,.95));
box-shadow: 0 0 10px rgba(255,59,106,.35), 0 0 24px rgba(255,59,106,.18);
margin-top: 2px;
animation: ledBreathe 1.25s ease-in-out 0s var(--alert-iter) both;
}
.alert-title{ text-transform: uppercase; letter-spacing: .8px; margin-bottom: 6px; }
.alert-text{ color: rgba(240,247,255,.92); }
@keyframes alertPulse{
0% { opacity: .45; transform: scale(1); }
45% { opacity: .95; transform: scale(1.01); }
100% { opacity: .55; transform: scale(1); }
}
@keyframes ledBreathe{
0% { transform: scale(1); opacity: .75; }
45% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(1); opacity: .85; }
}
/* Toasts */
.toast-host{
position: fixed;
right: 16px;
bottom: 16px;
display: grid;
gap: 10px;
z-index: 9999;
width: min(360px, calc(100vw - 32px));
}
.toast{
display:flex;
gap: 12px;
align-items:flex-start;
padding: 12px 12px;
border-radius: 16px;
border: 1px solid rgba(145,220,255,.18);
background: linear-gradient(180deg, rgba(10, 14, 40, .82), rgba(16, 22, 60, .65));
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
overflow: hidden;
animation: toastIn .18s ease-out;
position: relative;
}
.toast::before{
content:"";
position:absolute;
inset:0;
pointer-events:none;
background: repeating-linear-gradient(180deg, rgba(66,245,255,.00) 0px, rgba(66,245,255,.00) 3px, rgba(66,245,255,.05) 4px);
opacity: .18;
mix-blend-mode: screen;
}
.toast-icon{ font-size: 18px; line-height: 1; margin-top: 2px; filter: drop-shadow(0 0 12px rgba(66,245,255,.22)); }
.toast-title{ text-transform: uppercase; font-size: .92rem; }
.toast-msg{ color: rgba(240,247,255,.85); font-size: .95rem; margin-top: 2px; }
.toast-x{
margin-left: auto;
border: 1px solid rgba(145,220,255,.18);
background: rgba(0,0,0,.18);
color: var(--text);
border-radius: 12px;
padding: 6px 9px;
}
.toast-success{ border-color: rgba(61,255,181,.25); }
.toast-error{ border-color: rgba(255,59,106,.30); }
.toast-info{ border-color: rgba(66,245,255,.25); }
@keyframes toastIn{ from{ transform: translateY(8px); opacity: 0; } to{ transform: translateY(0); opacity: 1; } }
.toast.out{ animation: toastOut .18s ease-in forwards; }
@keyframes toastOut{ to{ transform: translateY(8px); opacity: 0; } }
/* Scrollbar */
.sidebar::-webkit-scrollbar{ width: 10px; }
.sidebar::-webkit-scrollbar-track{ background: rgba(0,0,0,.12); border-radius: 999px; }
.sidebar::-webkit-scrollbar-thumb{
background: linear-gradient(180deg, rgba(66,245,255,.35), rgba(255,61,242,.25));
border-radius: 999px;
border: 2px solid rgba(0,0,0,.18);
}
/* Responsive */
@media (max-width: 980px){
:root{ --sidebarW: 1fr; }
.app{
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto 1fr auto;
grid-template-areas:
"topbar"
"alertbar"
"subnav"
"resourcebar"
"content"
"sidebar"
"footer";
}
.sidebar{ position: static; max-height: none; overflow: visible; padding-right: 0; }
.grid-2{ grid-template-columns: 1fr; }
.resourcebar{ position: static; }
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce){
.starfield{ display:none; }
.space-bg::after{ animation: none !important; }
.alert.alert-pro::before, .alert-led{ animation: none !important; }
}

View File

@@ -0,0 +1,121 @@
(function(){
const host = document.getElementById("toastHost");
function iconFor(type){
if(type === "success") return "✅";
if(type === "error") return "⛔";
if(type === "info") return "🛰️";
return "🔔";
}
window.toast = function(type="info", title="Notice", message="", ttl=3200){
if(!host) return;
const el = document.createElement("div");
el.className = `toast toast-${type}`;
el.innerHTML = `
<div class="toast-icon">${iconFor(type)}</div>
<div class="toast-body">
<div class="toast-title">${escapeHtml(title)}</div>
<div class="toast-msg">${escapeHtml(message)}</div>
</div>
<button class="toast-x" aria-label="Close">✕</button>
`;
const remove = () => {
el.classList.add("out");
el.addEventListener("animationend", () => el.remove(), { once:true });
};
el.querySelector(".toast-x").addEventListener("click", remove);
host.appendChild(el);
if (ttl > 0) setTimeout(remove, ttl);
};
function escapeHtml(s){
return String(s ?? "")
.replaceAll("&","&amp;")
.replaceAll("<","&lt;")
.replaceAll(">","&gt;")
.replaceAll('"',"&quot;")
.replaceAll("'","&#039;");
}
// Notifications dropdown (tiny JS)
const notif = document.getElementById("notifPanel");
window.toggleNotif = function(){
if(!notif) return;
const isHidden = notif.hasAttribute("hidden");
if(isHidden) notif.removeAttribute("hidden");
else notif.setAttribute("hidden","");
};
document.addEventListener("click", (e)=>{
if(!notif) return;
const btn = e.target.closest(".iconbtn");
const inside = e.target.closest("#notifPanel");
if(inside || btn) return;
notif.setAttribute("hidden","");
});
// Settings helpers: perf + alertpulse (no cookies)
function setStorage(key, val, remember){
try{
if (remember) {
localStorage.setItem(key, val);
sessionStorage.removeItem(key);
} else {
sessionStorage.setItem(key, val);
localStorage.removeItem(key);
}
}catch(e){}
}
function applyDataset(key, val){
document.documentElement.dataset[key] = val;
if (key === "perf") {
const label = document.getElementById("perfLabel");
if(label) label.textContent = val.toUpperCase();
}
}
window.applyPerfFromUI = function(){
const chosen = document.querySelector(".seg-btn.is-selected[data-perf]")?.dataset.perf;
const remember = document.getElementById("rememberPerf")?.checked;
const val = chosen || "auto";
setStorage("perf", val, remember);
applyDataset("perf", val);
toast("success","Performance", `Profil: ${val.toUpperCase()}`, 2200);
if (val === "low") toast("info","Hint","Low deaktiviert Starfield", 1800);
};
window.applyPulseFromUI = function(){
const chosen = document.querySelector(".seg-btn.is-selected[data-alertpulse]")?.dataset.alertpulse;
const remember = document.getElementById("rememberPulse")?.checked;
const val = chosen || "burst";
setStorage("alertpulse", val, remember);
applyDataset("alertpulse", val);
toast("success","Alerts", `Pulse: ${val.toUpperCase()}`, 2200);
};
// Segment button selection behavior
document.addEventListener("click", (e)=>{
const b = e.target.closest(".seg-btn");
if(!b) return;
const parent = b.closest(".seg");
if(parent){
parent.querySelectorAll(".seg-btn").forEach(x=>x.classList.remove("is-selected"));
b.classList.add("is-selected");
}
});
// Pre-select in settings pages
document.addEventListener("DOMContentLoaded", ()=>{
const perf = document.documentElement.dataset.perf || "auto";
document.querySelectorAll('.seg-btn[data-perf]').forEach(b=>{
if(b.dataset.perf === perf) b.classList.add("is-selected");
});
const pulse = document.documentElement.dataset.alertpulse || "burst";
document.querySelectorAll('.seg-btn[data-alertpulse]').forEach(b=>{
if(b.dataset.alertpulse === pulse) b.classList.add("is-selected");
});
});
})();

244
web/mobile/public/index.php Normal file
View File

@@ -0,0 +1,244 @@
<?php
session_start();
/**
* Routing (Demo)
* s = section (Hauptmenü)
* p = page/sub (Kontextmenü)
* planet = aktive Kolonie
*/
$section = $_GET['s'] ?? 'overview';
$sub = $_GET['p'] ?? null;
$planet = $_GET['planet'] ?? 'earth';
// Demo Alert/Toast triggers
if (($_GET['toast'] ?? '') === 'ok') {
$_SESSION['flash_toast'][] = ['type' => 'success', 'title' => 'Mission Update', 'message' => 'Daten erfolgreich gespeichert.'];
header("Location: index.php?s=$section&p=" . urlencode((string)$sub) . "&planet=$planet");
exit;
}
if (($_GET['toast'] ?? '') === 'err') {
$_SESSION['flash_error'] = "Bitte alle Pflichtfelder ausfüllen (Hyperdrive-Parameter fehlen).";
$_SESSION['flash_toast'][] = ['type' => 'error', 'title' => 'System Alert', 'message' => 'Eingabe unvollständig überprüfe die Felder.'];
header("Location: index.php?s=$section&p=" . urlencode((string)$sub) . "&planet=$planet");
exit;
}
// Alertbar nur wenn Fehler vorhanden
$errorMessage = $_SESSION['flash_error'] ?? null;
unset($_SESSION['flash_error']);
// Toasts (Flash)
$toasts = $_SESSION['flash_toast'] ?? [];
unset($_SESSION['flash_toast']);
// Kontextmenü-Definitionen (Subnav in der Mitte)
$subnav = [
'overview' => [['key'=>'dashboard','label'=>'Übersicht'], ['key'=>'events','label'=>'Events'], ['key'=>'queues','label'=>'Queues']],
'build' => [['key'=>'build','label'=>'Bauen'], ['key'=>'demolish','label'=>'Abreißen']],
'research' => [['key'=>'list','label'=>'Forschungen'], ['key'=>'tree','label'=>'Forschungstree']],
'shipyard' => [['key'=>'small','label'=>'Klein'], ['key'=>'medium','label'=>'Mittel'], ['key'=>'large','label'=>'Groß']],
'fleet' => [['key'=>'send','label'=>'Versenden'], ['key'=>'missions','label'=>'Missionen'], ['key'=>'scrap','label'=>'Verschrotten']],
'messages' => [['key'=>'inbox','label'=>'Posteingang'], ['key'=>'outbox','label'=>'Ausgang'], ['key'=>'addressbook','label'=>'Adressbuch']],
'reports' => [['key'=>'combat','label'=>'Kampfberichte'], ['key'=>'spy','label'=>'Spionage'], ['key'=>'archive','label'=>'Archiv']],
'galaxy' => [['key'=>'view','label'=>'Galaxy View'], ['key'=>'bookmark','label'=>'Lesezeichen'], ['key'=>'scan','label'=>'Scan']],
'stargate' => [['key'=>'overview','label'=>'Übersicht'], ['key'=>'links','label'=>'Verbindungen'], ['key'=>'log','label'=>'Protokoll']],
'pod' => [['key'=>'pad','label'=>'Abschussrampe'], ['key'=>'production','label'=>'Produktion']],
'trade' => [['key'=>'hub','label'=>'Handelszentrum'], ['key'=>'market','label'=>'Börsenkurse']],
'blackmarket' => [['key'=>'overview','label'=>'Übersicht'], ['key'=>'create','label'=>'Inserieren'], ['key'=>'mine','label'=>'Meine Inserate']],
'bank' => [['key'=>'overview','label'=>'Übersicht'], ['key'=>'transfer','label'=>'Überweisung'], ['key'=>'accounts','label'=>'Konten hinzufügen']],
'terraformer' => [['key'=>'overview','label'=>'Übersicht']],
'settings' => [['key'=>'ui','label'=>'UI'], ['key'=>'performance','label'=>'Performance'], ['key'=>'alerts','label'=>'Alerts'], ['key'=>'account','label'=>'Account']],
];
if (!isset($subnav[$section])) $section = 'overview';
$items = $subnav[$section];
// Default sub page
if ($sub === null && !empty($items)) $sub = $items[0]['key'];
$validSub = array_column($items, 'key');
if (!in_array($sub, $validSub, true) && !empty($items)) $sub = $items[0]['key'];
// Page title
$pageTitle = "Space UI $section / $sub";
// Admin demo (set to true to see footer admin link)
$isAdmin = false;
$partialsPath = __DIR__ . '/../src/partials';
?>
<!doctype html>
<html lang="de"
data-perf="auto"
data-alertpulse="burst">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($pageTitle) ?></title>
<!-- Apply per-browser settings early (no cookies; storage only) -->
<script>
(function(){
try{
const perf = localStorage.getItem('perf') || sessionStorage.getItem('perf');
const pulse = localStorage.getItem('alertpulse') || sessionStorage.getItem('alertpulse');
if (perf) document.documentElement.dataset.perf = perf;
if (pulse) document.documentElement.dataset.alertpulse = pulse;
}catch(e){}
})();
</script>
<!-- Orbitron -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<!-- Starfield Canvas (perf profile can disable it) -->
<canvas class="starfield" id="starfield" aria-hidden="true"></canvas>
<!-- Nebula/Planet Overlay -->
<div class="space-bg" aria-hidden="true"></div>
<div class="container">
<div class="app">
<!-- LINKS: Sidebar -->
<aside class="sidebar" aria-label="Seitenleiste">
<div class="card panel">
<?php include $partialsPath . '/menue.php'; ?>
</div>
<div class="card panel">
<?php include $partialsPath . '/menue2.php'; ?>
</div>
</aside>
<!-- RECHTS: Topbar -->
<header class="topbar card panel">
<div class="topbar-inner">
<div class="brand">
<span class="brand-dot"></span>
<div>
<div class="brand-title">ORBIT STATION</div>
<div class="muted">HUD Navigation • Planet: <?= htmlspecialchars(strtoupper($planet)) ?></div>
</div>
</div>
<div class="top-actions">
<button class="iconbtn" type="button" onclick="toggleNotif()" aria-label="Notifications">
🔔 <span class="badge" id="notifBadge">3</span>
</button>
<button class="btn btn-primary" type="button" onclick="toast('success','Beacon','Signal empfangen ✅')">Test Toast</button>
</div>
</div>
<!-- Notification dropdown -->
<div class="notif" id="notifPanel" hidden>
<div class="notif-head">
<div class="panel-title">NOTIFICATIONS</div>
<button class="iconbtn" type="button" onclick="toggleNotif()" aria-label="Close">✕</button>
</div>
<div class="notif-list">
<button class="notif-item" type="button" onclick="toast('info','Report','Spionagebericht empfangen',2200)">
<span class="notif-ico">📄</span>
<span>
<span class="notif-title">Spy Report</span>
<span class="notif-meta">vor 2 min</span>
</span>
</button>
<button class="notif-item" type="button" onclick="toast('success','Queue','Forschung abgeschlossen',2200)">
<span class="notif-ico">🧪</span>
<span>
<span class="notif-title">Research done</span>
<span class="notif-meta">vor 7 min</span>
</span>
</button>
<button class="notif-item" type="button" onclick="toast('error','Alarm','Flotte entdeckt!',2200)">
<span class="notif-ico">🚨</span>
<span>
<span class="notif-title">Hostile ping</span>
<span class="notif-meta">gerade eben</span>
</span>
</button>
</div>
</div>
</header>
<!-- OPTIONAL: Alert Banner (über Subnav) -->
<?php if (!empty($errorMessage)): ?>
<header class="alertbar card panel" role="alert" aria-live="polite">
<?php include $partialsPath . '/menue-top.php'; ?>
</header>
<?php endif; ?>
<!-- KONTEXTMENÜ (Subnav) mittig über Content -->
<nav class="subnav card panel" aria-label="Kontextmenü">
<div class="subnav-left">
<div class="panel-title"><?= htmlspecialchars(strtoupper($section)) ?></div>
<div class="muted">Kontextmenü</div>
</div>
<div class="subnav-tabs" role="tablist" aria-label="Sub Navigation Tabs">
<?php foreach ($items as $it): ?>
<?php
$active = ($it['key'] === $sub);
$href = "index.php?s=" . urlencode($section) . "&p=" . urlencode($it['key']) . "&planet=" . urlencode($planet);
?>
<a class="tab <?= $active ? 'is-active' : '' ?>" href="<?= htmlspecialchars($href) ?>" role="tab" aria-selected="<?= $active ? 'true' : 'false' ?>">
<?= htmlspecialchars($it['label']) ?>
</a>
<?php endforeach; ?>
</div>
</nav>
<!-- Ressourcen (sticky) -->
<div class="resourcebar card panel">
<?php include $partialsPath . '/ressourcen.php'; ?>
</div>
<!-- Content -->
<main class="content card panel cockpit" id="content">
<div class="cockpit-hud">
<div class="hud-left">SECTOR: ORION • DOCK-PORT: A3</div>
<div class="hud-right">
<span class="hud-pill">SHIELD 87%</span>
<span class="hud-pill">PING 14ms</span>
<span class="hud-pill">PERF <span id="perfLabel">AUTO</span></span>
</div>
</div>
<?php include $partialsPath . '/site.php'; ?>
</main>
<!-- Footer -->
<footer class="footer card panel">
<?php include $partialsPath . '/menue-foot.php'; ?>
<?php if ($isAdmin): ?>
<div class="divider"></div>
<a class="chip" href="index.php?s=admin&planet=<?= urlencode($planet) ?>">Admin</a>
<?php endif; ?>
</footer>
</div>
</div>
<!-- Toast Host -->
<div class="toast-host" id="toastHost" aria-live="polite" aria-atomic="true"></div>
<!-- JS -->
<script src="assets/ui.js"></script>
<script src="assets/starfield.js"></script>
<!-- PHP Flash Toasts -> JS -->
<script>
(function(){
const toasts = <?= json_encode($toasts, JSON_UNESCAPED_UNICODE) ?>;
if (Array.isArray(toasts)) toasts.forEach(t => toast(t.type, t.title, t.message));
document.getElementById('perfLabel').textContent = (document.documentElement.dataset.perf || 'auto').toUpperCase();
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<div class="footer-row">
<div>
<div class="panel-title">FOOTER</div>
<div class="muted">menue-foot.php</div>
</div>
<div class="footer-actions">
<a class="chip" href="#">Imprint</a>
<a class="chip" href="#">Privacy</a>
<a class="chip" href="#">Status</a>
</div>
</div>

View File

@@ -0,0 +1,10 @@
<?php
// Erwartet: $errorMessage
?>
<div class="alert alert-pro">
<span class="alert-led" aria-hidden="true"></span>
<div class="alert-body">
<div class="alert-title">SYSTEM WARNING</div>
<div class="alert-text"><?= htmlspecialchars($errorMessage) ?></div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<?php
// erwartet: $section, $sub, $planet
function menu_link($key, $label, $icon, $planet, $section){
$href = "index.php?s=" . urlencode($key) . "&planet=" . urlencode($planet);
$active = ($section === $key) ? 'is-active' : '';
echo '<li><a class="'.$active.'" href="'.htmlspecialchars($href).'"><span class="mi">'.$icon.'</span><span>'.$label.'</span></a></li>';
}
?>
<nav aria-label="Hauptmenü">
<div class="panel-title">HAUPTMENÜ</div>
<div class="muted">Nur Hauptbegriffe</div>
<ul class="navlist navlist-main">
<?php
menu_link('overview', 'Übersicht', '🧭', $planet, $section);
menu_link('build', 'Baumenü', '🏗️', $planet, $section);
menu_link('research', 'Forschung', '🧪', $planet, $section);
menu_link('shipyard', 'Werft', '🛠️', $planet, $section);
menu_link('fleet', 'Flotte', '🚀', $planet, $section);
menu_link('galaxy', 'Galaxie', '🌌', $planet, $section);
menu_link('reports', 'Berichte', '📑', $planet, $section);
menu_link('messages', 'Nachrichten', '✉️', $planet, $section);
menu_link('stargate', 'Sternentor', '🌀', $planet, $section);
menu_link('pod', 'Pod Launcher', '🎯', $planet, $section);
menu_link('trade', 'Handel', '💱', $planet, $section);
menu_link('blackmarket', 'Schwarzmarkt', '🕶️', $planet, $section);
menu_link('bank', 'Bank', '🏦', $planet, $section);
menu_link('terraformer', 'Terraformer', '🪐', $planet, $section);
menu_link('settings', 'Einstellungen', '⚙️', $planet, $section);
?>
</ul>
<div class="divider"></div>
<a class="btn w-full" href="index.php?s=<?= urlencode($section) ?>&p=<?= urlencode($sub ?? '') ?>&planet=<?= urlencode($planet) ?>&toast=ok">Demo: Success</a>
<a class="btn w-full" style="margin-top:10px" href="index.php?s=<?= urlencode($section) ?>&p=<?= urlencode($sub ?? '') ?>&planet=<?= urlencode($planet) ?>&toast=err">Demo: Warning</a>
</nav>

View File

@@ -0,0 +1,35 @@
<?php
// Planetenliste (unteres Menü links)
$planets = [
['id'=>'earth', 'name'=>'Earth Prime', 'hint'=>'L1'],
['id'=>'mars', 'name'=>'Mars Outpost', 'hint'=>'L2'],
['id'=>'io', 'name'=>'Io Refinery', 'hint'=>'L3'],
['id'=>'vega', 'name'=>'Vega Station', 'hint'=>'L4'],
];
?>
<nav aria-label="Planetenliste">
<div class="panel-title">KOLONIEN</div>
<div class="muted">Schnellwechsel (Planeten)</div>
<ul class="planetlist">
<?php foreach($planets as $p): ?>
<?php
$active = ($p['id'] === $planet);
$href = "index.php?s=" . urlencode($section) . "&p=" . urlencode($sub ?? '') . "&planet=" . urlencode($p['id']);
?>
<li>
<a class="<?= $active ? 'is-active' : '' ?>" href="<?= htmlspecialchars($href) ?>">
<span class="pl-dot <?= $active ? 'on' : '' ?>"></span>
<span class="pl-name"><?= htmlspecialchars($p['name']) ?></span>
<span class="navhint"><?= htmlspecialchars($p['hint']) ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
<div class="divider"></div>
<div class="muted" style="display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap;">
<span>Aktiv: <strong><?= htmlspecialchars($planet) ?></strong></span>
<span class="tiny">Tip: Shortcuts later</span>
</div>
</nav>

View File

@@ -0,0 +1,29 @@
<div class="resource-row">
<div>
<div class="panel-title">RESOURCES</div>
<div class="muted">sticky bar</div>
</div>
<div class="stats">
<div class="stat">
<div class="stat-k">Metall</div>
<div class="stat-v"><span class="dot dot-cyan"></span> 12.340</div>
<div class="stat-bar"><span style="width:72%"></span></div>
</div>
<div class="stat">
<div class="stat-k">Kristall</div>
<div class="stat-v"><span class="dot dot-pink"></span> 6.120</div>
<div class="stat-bar"><span style="width:44%"></span></div>
</div>
<div class="stat">
<div class="stat-k">Deuterium</div>
<div class="stat-v"><span class="dot dot-green"></span> 3.880</div>
<div class="stat-bar"><span style="width:18%"></span></div>
</div>
<div class="stat">
<div class="stat-k">Energie</div>
<div class="stat-v"><span class="dot dot-warn"></span> +120</div>
<div class="stat-bar"><span style="width:60%"></span></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,100 @@
<?php
// erwartet: $section, $sub, $planet
?>
<h1 class="hud-title"><?= htmlspecialchars($section) ?> / <?= htmlspecialchars($sub) ?></h1>
<p class="muted">Demo-Seite: Hier renderst du später die echten Inhalte pro Bereich.</p>
<?php if ($section === 'overview'): ?>
<div class="grid-2" style="margin-top:14px;">
<section class="card inner panel">
<h2 class="h2">Event Timeline</h2>
<div class="timeline">
<div class="tl-item"><span class="tl-dot tl-cyan"></span> Bau fertig in <strong>00:12:31</strong></div>
<div class="tl-item"><span class="tl-dot tl-pink"></span> Forschung fertig in <strong>01:02:10</strong></div>
<div class="tl-item"><span class="tl-dot tl-green"></span> Flotte ankommend in <strong>00:07:55</strong></div>
</div>
</section>
<section class="card inner panel">
<h2 class="h2">Queues</h2>
<div class="queue">
<div class="q-card">
<div class="q-head"><span class="tag info">BUILD</span><span>Metallmine Stufe 12</span></div>
<div class="q-bar"><span style="width:38%"></span></div>
<div class="q-meta"><span>ETA 00:12:31</span><button class="btn btn-mini" type="button" onclick="toast('info','Queue','Abbrechen (Demo)')">Abbrechen</button></div>
</div>
<div class="q-card">
<div class="q-head"><span class="tag info">RESEARCH</span><span>Antriebstechnik Stufe 5</span></div>
<div class="q-bar"><span style="width:66%"></span></div>
<div class="q-meta"><span>ETA 01:02:10</span><button class="btn btn-mini" type="button" onclick="toast('info','Queue','Priorisieren (Demo)')">Priorisieren</button></div>
</div>
</div>
</section>
</div>
<?php elseif ($section === 'settings' && $sub === 'performance'): ?>
<div class="card inner panel" style="margin-top:14px;">
<h2 class="h2">Performance Profile</h2>
<p class="muted">Speicherung ohne Cookies: sessionStorage (temporär) oder localStorage (merken).</p>
<div class="settings-row">
<div class="settings-block">
<div class="label">Profil</div>
<div class="seg">
<button type="button" class="seg-btn" data-perf="auto">Auto</button>
<button type="button" class="seg-btn" data-perf="low">Low</button>
<button type="button" class="seg-btn" data-perf="medium">Medium</button>
<button type="button" class="seg-btn" data-perf="high">High</button>
</div>
</div>
<div class="settings-block">
<label class="check">
<input id="rememberPerf" type="checkbox">
<span>Merken auf diesem Gerät (localStorage)</span>
</label>
<div class="muted tiny">Wenn aus: nur sessionStorage.</div>
</div>
</div>
<div class="actions">
<button class="btn btn-primary" type="button" onclick="applyPerfFromUI()">Übernehmen</button>
<button class="btn" type="button" onclick="toast('info','Info','Tipp: Low deaktiviert Starfield')">Info</button>
</div>
</div>
<?php elseif ($section === 'settings' && $sub === 'alerts'): ?>
<div class="card inner panel" style="margin-top:14px;">
<h2 class="h2">Alert Pulse</h2>
<p class="muted">Wähle: 3× Puls (Burst) oder dauerhaft (Loop).</p>
<div class="settings-row">
<div class="seg">
<button type="button" class="seg-btn" data-alertpulse="burst">Burst (3×)</button>
<button type="button" class="seg-btn" data-alertpulse="loop">Loop</button>
</div>
<div class="settings-block">
<label class="check">
<input id="rememberPulse" type="checkbox">
<span>Merken auf diesem Gerät (localStorage)</span>
</label>
<div class="muted tiny">Wenn aus: nur sessionStorage.</div>
</div>
</div>
<div class="actions">
<button class="btn btn-primary" type="button" onclick="applyPulseFromUI()">Übernehmen</button>
<a class="btn" href="index.php?s=<?= urlencode($section) ?>&p=<?= urlencode($sub) ?>&planet=<?= urlencode($planet) ?>&toast=err">Demo Warning</a>
</div>
</div>
<?php else: ?>
<div class="card inner panel" style="margin-top:14px;">
<h2 class="h2">Platzhalter</h2>
<p>Hier kommt später die Seite für <strong><?= htmlspecialchars($section) ?></strong> / <strong><?= htmlspecialchars($sub) ?></strong>.</p>
<div class="actions">
<button class="btn btn-primary" type="button" onclick="toast('success','UI','Alles bereit ✅')">Toast testen</button>
</div>
</div>
<?php endif; ?>