Initial commit
This commit is contained in:
18
README.md
Normal file
18
README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Space UI Demo v2 (PHP + CSS + JS)
|
||||
|
||||
## Start
|
||||
- Built-in PHP server:
|
||||
php -S localhost:8000
|
||||
then open: http://localhost:8000/index.php
|
||||
|
||||
## Layout (wie besprochen)
|
||||
- Links: Hauptmenü (statisch) + unten Planetenliste
|
||||
- Rechts: Topbar + Alert (optional) + Kontextmenü (Subnav in der Mitte) + Sticky Resources + Content + Footer
|
||||
- Kontextmenü wechselt je nach Hauptmenüpunkt
|
||||
|
||||
## Settings (ohne Cookies)
|
||||
- Einstellungen -> Performance: Auto/Low/Medium/High (sessionStorage oder localStorage)
|
||||
- Einstellungen -> Alerts: Burst (3x) oder Loop (sessionStorage oder localStorage)
|
||||
|
||||
## Demos
|
||||
- In Sidebar: "Demo: Success" / "Demo: Warning"
|
||||
114
assets/starfield.js
Normal file
114
assets/starfield.js
Normal 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);
|
||||
})();
|
||||
733
assets/style.css
Normal file
733
assets/style.css
Normal 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; }
|
||||
}
|
||||
121
assets/ui.js
Normal file
121
assets/ui.js
Normal 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("&","&")
|
||||
.replaceAll("<","<")
|
||||
.replaceAll(">",">")
|
||||
.replaceAll('"',""")
|
||||
.replaceAll("'","'");
|
||||
}
|
||||
|
||||
// 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");
|
||||
});
|
||||
});
|
||||
})();
|
||||
243
index.php
Normal file
243
index.php
Normal file
@@ -0,0 +1,243 @@
|
||||
<?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;
|
||||
?>
|
||||
<!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 __DIR__ . "/menue.php"; ?>
|
||||
</div>
|
||||
|
||||
<div class="card panel">
|
||||
<?php include __DIR__ . "/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 __DIR__ . "/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 __DIR__ . "/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 __DIR__ . "/site.php"; ?>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer card panel">
|
||||
<?php include __DIR__ . "/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>
|
||||
12
menue-foot.php
Normal file
12
menue-foot.php
Normal 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>
|
||||
10
menue-top.php
Normal file
10
menue-top.php
Normal 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>
|
||||
36
menue.php
Normal file
36
menue.php
Normal 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>
|
||||
35
menue2.php
Normal file
35
menue2.php
Normal 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>
|
||||
29
ressourcen.php
Normal file
29
ressourcen.php
Normal 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>
|
||||
100
site.php
Normal file
100
site.php
Normal 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; ?>
|
||||
Reference in New Issue
Block a user