677 lines
19 KiB
HTML
677 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>SysAdmin Arcade</title>
|
|
<link rel="icon" href="favicon/arcade-favicon.ico" sizes="any">
|
|
<link rel="icon" href="favicon/arcade-favicon-16.png" type="image/png" sizes="16x16">
|
|
<link rel="icon" href="favicon/arcade-favicon-32.png" type="image/png" sizes="32x32">
|
|
<link rel="icon" href="favicon/arcade-favicon-192.png" type="image/png" sizes="192x192">
|
|
<link rel="apple-touch-icon" href="favicon/arcade-favicon-180.png" sizes="180x180">
|
|
<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=JetBrains+Mono:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root{
|
|
--bg:#030712;
|
|
--panel:#08111f;
|
|
--panel-hi:#101d33;
|
|
--line:#203450;
|
|
--line-hot:#3ce7d8;
|
|
--text:#d7e8ff;
|
|
--muted:#7890b5;
|
|
--cyan:#45f0dd;
|
|
--blue:#62a8ff;
|
|
--green:#58f29a;
|
|
--amber:#ffbd5a;
|
|
--red:#ff5c72;
|
|
--shadow:rgba(0,0,0,0.45);
|
|
}
|
|
|
|
*{box-sizing:border-box;margin:0;padding:0;}
|
|
html{scroll-behavior:smooth;}
|
|
body{
|
|
min-height:100vh;
|
|
color:var(--text);
|
|
font-family:'JetBrains Mono', monospace;
|
|
background:
|
|
radial-gradient(circle at 20% 10%, rgba(69,240,221,0.18), transparent 28rem),
|
|
radial-gradient(circle at 82% 0%, rgba(98,168,255,0.16), transparent 30rem),
|
|
linear-gradient(180deg, #06101f 0%, var(--bg) 56%, #02040a 100%);
|
|
overflow-x:hidden;
|
|
}
|
|
|
|
body::before{
|
|
content:"";
|
|
position:fixed;
|
|
inset:0;
|
|
pointer-events:none;
|
|
background:
|
|
linear-gradient(rgba(69,240,221,0.035) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(69,240,221,0.03) 1px, transparent 1px);
|
|
background-size:44px 44px;
|
|
mask-image:linear-gradient(to bottom, rgba(0,0,0,0.8), transparent 82%);
|
|
}
|
|
|
|
body::after{
|
|
content:"";
|
|
position:fixed;
|
|
inset:0;
|
|
pointer-events:none;
|
|
background:repeating-linear-gradient(180deg, rgba(255,255,255,0.028) 0 1px, transparent 1px 4px);
|
|
opacity:0.28;
|
|
mix-blend-mode:screen;
|
|
}
|
|
|
|
a{color:inherit;text-decoration:none;}
|
|
|
|
.shell{
|
|
width:min(1180px, calc(100% - 32px));
|
|
margin:0 auto;
|
|
padding:30px 0 42px;
|
|
position:relative;
|
|
z-index:1;
|
|
}
|
|
|
|
.topbar{
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:space-between;
|
|
gap:18px;
|
|
margin-bottom:48px;
|
|
color:var(--muted);
|
|
font-size:12px;
|
|
letter-spacing:0.16em;
|
|
text-transform:uppercase;
|
|
}
|
|
|
|
.brand{
|
|
display:flex;
|
|
align-items:center;
|
|
gap:10px;
|
|
color:#fff;
|
|
font-weight:800;
|
|
}
|
|
|
|
.brand-mark{
|
|
width:28px;
|
|
height:28px;
|
|
border:1px solid rgba(69,240,221,0.7);
|
|
border-radius:7px;
|
|
background:
|
|
linear-gradient(135deg, rgba(69,240,221,0.28), transparent),
|
|
var(--panel);
|
|
box-shadow:0 0 22px rgba(69,240,221,0.32), inset 0 0 16px rgba(69,240,221,0.14);
|
|
position:relative;
|
|
}
|
|
|
|
.brand-mark::before,
|
|
.brand-mark::after{
|
|
content:"";
|
|
position:absolute;
|
|
left:6px;
|
|
right:6px;
|
|
height:3px;
|
|
background:var(--cyan);
|
|
box-shadow:0 0 10px var(--cyan);
|
|
}
|
|
|
|
.brand-mark::before{top:7px;}
|
|
.brand-mark::after{bottom:7px;background:var(--green);box-shadow:0 0 10px var(--green);}
|
|
|
|
.status{
|
|
display:flex;
|
|
align-items:center;
|
|
gap:9px;
|
|
white-space:nowrap;
|
|
}
|
|
|
|
.status-dot{
|
|
width:9px;
|
|
height:9px;
|
|
border-radius:50%;
|
|
background:var(--green);
|
|
box-shadow:0 0 16px var(--green);
|
|
}
|
|
|
|
.hero{
|
|
display:grid;
|
|
grid-template-columns:minmax(0, 1.02fr) minmax(320px, 0.78fr);
|
|
gap:26px;
|
|
align-items:stretch;
|
|
}
|
|
|
|
.terminal,
|
|
.ops-card,
|
|
.game-card{
|
|
border:1px solid rgba(92,126,174,0.34);
|
|
background:linear-gradient(180deg, rgba(16,29,51,0.86), rgba(6,13,26,0.88));
|
|
box-shadow:0 28px 90px var(--shadow), inset 0 1px rgba(255,255,255,0.05);
|
|
backdrop-filter:blur(12px);
|
|
}
|
|
|
|
.terminal{
|
|
min-height:430px;
|
|
border-radius:22px;
|
|
overflow:hidden;
|
|
position:relative;
|
|
}
|
|
|
|
.terminal::before{
|
|
content:"";
|
|
position:absolute;
|
|
inset:auto -10% 0;
|
|
height:160px;
|
|
background:radial-gradient(ellipse at center, rgba(69,240,221,0.18), transparent 62%);
|
|
filter:blur(4px);
|
|
}
|
|
|
|
.window-bar{
|
|
height:44px;
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:space-between;
|
|
padding:0 18px;
|
|
border-bottom:1px solid rgba(92,126,174,0.25);
|
|
background:rgba(3,7,18,0.42);
|
|
color:var(--muted);
|
|
font-size:11px;
|
|
letter-spacing:0.14em;
|
|
text-transform:uppercase;
|
|
}
|
|
|
|
.lights{display:flex;gap:7px;}
|
|
.light{width:10px;height:10px;border-radius:50%;background:var(--red);box-shadow:0 0 12px currentColor;}
|
|
.light:nth-child(2){background:var(--amber);}
|
|
.light:nth-child(3){background:var(--green);}
|
|
|
|
.terminal-body{
|
|
padding:34px;
|
|
position:relative;
|
|
}
|
|
|
|
.prompt{
|
|
color:var(--green);
|
|
font-weight:700;
|
|
margin-bottom:14px;
|
|
text-shadow:0 0 18px rgba(88,242,154,0.18);
|
|
}
|
|
|
|
h1{
|
|
font-family:'Space Grotesk', sans-serif;
|
|
font-size:clamp(48px, 8vw, 92px);
|
|
line-height:0.9;
|
|
letter-spacing:-0.06em;
|
|
max-width:720px;
|
|
color:#fff;
|
|
text-shadow:0 0 40px rgba(69,240,221,0.2);
|
|
}
|
|
|
|
.cursor{
|
|
color:var(--cyan);
|
|
animation:blink 1.05s steps(1) infinite;
|
|
}
|
|
|
|
@keyframes blink{50%{opacity:0;}}
|
|
|
|
.lead{
|
|
max-width:650px;
|
|
margin:24px 0 30px;
|
|
color:#a9bddb;
|
|
font-size:clamp(15px, 2vw, 18px);
|
|
line-height:1.75;
|
|
}
|
|
|
|
.cta-row{
|
|
display:flex;
|
|
flex-wrap:wrap;
|
|
gap:12px;
|
|
align-items:center;
|
|
}
|
|
|
|
.button{
|
|
display:inline-flex;
|
|
align-items:center;
|
|
justify-content:center;
|
|
min-height:50px;
|
|
padding:0 22px;
|
|
border-radius:12px;
|
|
border:1px solid rgba(69,240,221,0.68);
|
|
background:linear-gradient(135deg, rgba(69,240,221,0.22), rgba(98,168,255,0.12));
|
|
color:#fff;
|
|
font-weight:800;
|
|
letter-spacing:0.08em;
|
|
text-transform:uppercase;
|
|
box-shadow:0 0 34px rgba(69,240,221,0.18), inset 0 0 24px rgba(69,240,221,0.08);
|
|
transition:transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
|
|
}
|
|
|
|
.button:hover{
|
|
transform:translateY(-2px);
|
|
border-color:var(--cyan);
|
|
box-shadow:0 16px 42px rgba(69,240,221,0.24), inset 0 0 28px rgba(69,240,221,0.12);
|
|
}
|
|
|
|
.button.secondary{
|
|
border-color:rgba(120,144,181,0.38);
|
|
background:rgba(8,17,31,0.58);
|
|
color:#c9dcfb;
|
|
box-shadow:none;
|
|
}
|
|
|
|
.side-stack{
|
|
display:grid;
|
|
gap:16px;
|
|
}
|
|
|
|
.ops-card{
|
|
border-radius:20px;
|
|
padding:22px;
|
|
overflow:hidden;
|
|
position:relative;
|
|
}
|
|
|
|
.ops-card::after{
|
|
content:"";
|
|
position:absolute;
|
|
width:190px;
|
|
height:190px;
|
|
right:-72px;
|
|
bottom:-72px;
|
|
border-radius:50%;
|
|
background:radial-gradient(circle, rgba(69,240,221,0.18), transparent 68%);
|
|
}
|
|
|
|
.ops-label,
|
|
.section-label{
|
|
color:var(--cyan);
|
|
font-size:11px;
|
|
letter-spacing:0.22em;
|
|
text-transform:uppercase;
|
|
font-weight:800;
|
|
margin-bottom:14px;
|
|
}
|
|
|
|
.metric-grid{
|
|
display:grid;
|
|
grid-template-columns:repeat(2, 1fr);
|
|
gap:12px;
|
|
position:relative;
|
|
z-index:1;
|
|
}
|
|
|
|
.metric{
|
|
min-height:92px;
|
|
border:1px solid rgba(92,126,174,0.25);
|
|
border-radius:14px;
|
|
padding:14px;
|
|
background:rgba(3,7,18,0.38);
|
|
}
|
|
|
|
.metric strong{
|
|
display:block;
|
|
color:#fff;
|
|
font-family:'Space Grotesk', sans-serif;
|
|
font-size:28px;
|
|
line-height:1;
|
|
margin-bottom:8px;
|
|
}
|
|
|
|
.metric span{
|
|
color:var(--muted);
|
|
font-size:10px;
|
|
letter-spacing:0.14em;
|
|
text-transform:uppercase;
|
|
}
|
|
|
|
.rack{
|
|
display:grid;
|
|
gap:8px;
|
|
margin-top:8px;
|
|
}
|
|
|
|
.rack-row{
|
|
height:34px;
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:space-between;
|
|
padding:0 12px;
|
|
border:1px solid rgba(92,126,174,0.28);
|
|
border-radius:8px;
|
|
background:linear-gradient(90deg, rgba(69,240,221,0.1), rgba(98,168,255,0.04));
|
|
color:#a9bddb;
|
|
font-size:11px;
|
|
position:relative;
|
|
z-index:1;
|
|
}
|
|
|
|
.leds{display:flex;gap:5px;}
|
|
.led{width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 10px var(--green);}
|
|
.led.warn{background:var(--amber);box-shadow:0 0 10px var(--amber);}
|
|
.led.hot{background:var(--red);box-shadow:0 0 10px var(--red);}
|
|
|
|
.games{
|
|
margin-top:30px;
|
|
padding-top:28px;
|
|
}
|
|
|
|
.section-head{
|
|
display:flex;
|
|
align-items:end;
|
|
justify-content:space-between;
|
|
gap:16px;
|
|
margin-bottom:18px;
|
|
}
|
|
|
|
h2{
|
|
font-family:'Space Grotesk', sans-serif;
|
|
font-size:clamp(30px, 4vw, 48px);
|
|
letter-spacing:-0.04em;
|
|
color:#fff;
|
|
}
|
|
|
|
.section-copy{
|
|
max-width:520px;
|
|
color:var(--muted);
|
|
line-height:1.6;
|
|
font-size:14px;
|
|
text-align:right;
|
|
}
|
|
|
|
.game-grid{
|
|
display:grid;
|
|
grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));
|
|
gap:18px;
|
|
}
|
|
|
|
.empty-state{
|
|
grid-column:1 / -1;
|
|
border:1px dashed rgba(92,126,174,0.4);
|
|
border-radius:18px;
|
|
padding:28px;
|
|
color:var(--muted);
|
|
background:rgba(3,7,18,0.28);
|
|
line-height:1.7;
|
|
}
|
|
|
|
.game-card{
|
|
min-height:310px;
|
|
border-radius:22px;
|
|
padding:24px;
|
|
position:relative;
|
|
overflow:hidden;
|
|
transition:transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
|
|
}
|
|
|
|
.game-card:hover{
|
|
transform:translateY(-4px);
|
|
border-color:rgba(69,240,221,0.78);
|
|
box-shadow:0 30px 90px rgba(0,0,0,0.52), 0 0 42px rgba(69,240,221,0.16);
|
|
}
|
|
|
|
.game-card::before{
|
|
content:"";
|
|
position:absolute;
|
|
inset:0;
|
|
background:
|
|
linear-gradient(135deg, rgba(69,240,221,0.15), transparent 34%),
|
|
radial-gradient(circle at 88% 22%, rgba(255,189,90,0.17), transparent 16rem);
|
|
pointer-events:none;
|
|
}
|
|
|
|
.game-inner{position:relative;z-index:1;}
|
|
|
|
.game-top{
|
|
display:flex;
|
|
align-items:center;
|
|
justify-content:space-between;
|
|
gap:14px;
|
|
margin-bottom:36px;
|
|
}
|
|
|
|
.badge{
|
|
display:inline-flex;
|
|
align-items:center;
|
|
gap:8px;
|
|
padding:8px 10px;
|
|
border:1px solid rgba(69,240,221,0.44);
|
|
border-radius:999px;
|
|
background:rgba(69,240,221,0.08);
|
|
color:#bffcf4;
|
|
font-size:11px;
|
|
letter-spacing:0.14em;
|
|
text-transform:uppercase;
|
|
font-weight:800;
|
|
}
|
|
|
|
.badge::before{
|
|
content:"";
|
|
width:7px;
|
|
height:7px;
|
|
border-radius:50%;
|
|
background:var(--green);
|
|
box-shadow:0 0 12px var(--green);
|
|
}
|
|
|
|
.difficulty{color:var(--amber);font-size:12px;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;}
|
|
|
|
.game-title{
|
|
font-family:'Space Grotesk', sans-serif;
|
|
color:#fff;
|
|
font-size:42px;
|
|
line-height:0.95;
|
|
letter-spacing:-0.05em;
|
|
margin-bottom:14px;
|
|
}
|
|
|
|
.game-desc{
|
|
color:#a9bddb;
|
|
line-height:1.65;
|
|
max-width:620px;
|
|
margin-bottom:24px;
|
|
}
|
|
|
|
.tags{
|
|
display:flex;
|
|
flex-wrap:wrap;
|
|
gap:8px;
|
|
margin-bottom:26px;
|
|
}
|
|
|
|
.tag{
|
|
padding:7px 9px;
|
|
border-radius:8px;
|
|
background:rgba(3,7,18,0.48);
|
|
border:1px solid rgba(92,126,174,0.24);
|
|
color:var(--muted);
|
|
font-size:11px;
|
|
}
|
|
|
|
.play-link{
|
|
display:inline-flex;
|
|
align-items:center;
|
|
gap:10px;
|
|
color:#fff;
|
|
font-weight:800;
|
|
letter-spacing:0.1em;
|
|
text-transform:uppercase;
|
|
}
|
|
|
|
.play-link span{
|
|
width:34px;
|
|
height:34px;
|
|
display:inline-grid;
|
|
place-items:center;
|
|
border-radius:50%;
|
|
border:1px solid rgba(69,240,221,0.58);
|
|
color:var(--cyan);
|
|
box-shadow:0 0 22px rgba(69,240,221,0.14);
|
|
transition:transform 160ms ease;
|
|
}
|
|
|
|
.game-card:hover .play-link span{transform:translateX(3px);}
|
|
|
|
.footer{
|
|
display:flex;
|
|
justify-content:space-between;
|
|
gap:14px;
|
|
flex-wrap:wrap;
|
|
margin-top:36px;
|
|
color:var(--muted);
|
|
font-size:12px;
|
|
}
|
|
|
|
.footer a{
|
|
color:#fff;
|
|
font-weight:700;
|
|
}
|
|
|
|
code{
|
|
color:#c9fff8;
|
|
background:rgba(69,240,221,0.08);
|
|
border:1px solid rgba(69,240,221,0.18);
|
|
padding:2px 6px;
|
|
border-radius:6px;
|
|
}
|
|
|
|
@media (max-width:900px){
|
|
.hero{grid-template-columns:1fr;}
|
|
.section-head{align-items:start;flex-direction:column;}
|
|
.section-copy{text-align:left;}
|
|
.terminal-body{padding:26px;}
|
|
}
|
|
|
|
@media (max-width:560px){
|
|
.shell{width:min(100% - 22px, 1180px);padding-top:18px;}
|
|
.topbar{align-items:flex-start;flex-direction:column;margin-bottom:24px;}
|
|
.terminal{min-height:auto;border-radius:18px;}
|
|
.window-bar{padding:0 12px;font-size:9px;}
|
|
.terminal-body{padding:22px 18px;}
|
|
.lead{font-size:14px;}
|
|
.button{width:100%;}
|
|
.metric-grid{grid-template-columns:1fr;}
|
|
.game-card{padding:20px;}
|
|
.game-title{font-size:34px;}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="shell">
|
|
<nav class="topbar" aria-label="Site status">
|
|
<a class="brand" href="/" aria-label="games.sysmd.uk home">
|
|
<span class="brand-mark" aria-hidden="true"></span>
|
|
<span>games.sysmd.uk</span>
|
|
</a>
|
|
<div class="status"><span class="status-dot" aria-hidden="true"></span> Arcade service online</div>
|
|
</nav>
|
|
|
|
<section class="hero" aria-labelledby="page-title">
|
|
<div class="terminal">
|
|
<div class="window-bar">
|
|
<div class="lights" aria-hidden="true"><span class="light"></span><span class="light"></span><span class="light"></span></div>
|
|
<span>/srv/sysadmin-arcade</span>
|
|
</div>
|
|
<div class="terminal-body">
|
|
<p class="prompt">root@games:~# ./launch-game</p>
|
|
<h1 id="page-title">SysAdmin<br>Arcade<span class="cursor">_</span></h1>
|
|
<p class="lead">Even SysAdmins need somewhere to park the pager, stretch the brain, and have a little fun between deploys. Pick a browser game and enjoy a short maintenance window for yourself.</p>
|
|
<div class="cta-row">
|
|
<a class="button" href="#games">Browse Games</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="side-stack" aria-label="Operations dashboard">
|
|
<div class="ops-card">
|
|
<div class="ops-label">Arcade Status</div>
|
|
<div class="metric-grid">
|
|
<div class="metric"><strong>99.99%</strong><span>Arcade uptime</span></div>
|
|
<div class="metric"><strong id="game-count">...</strong><span>Games deployed</span></div>
|
|
<div class="metric"><strong>∞</strong><span>Fun budget</span></div>
|
|
<div class="metric"><strong>100%</strong><span>Free</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ops-card">
|
|
<div class="ops-label">Session Queue</div>
|
|
<div class="rack" aria-hidden="true">
|
|
<div class="rack-row"><span>coffee</span><span class="leds"><i class="led"></i><i class="led"></i><i class="led"></i></span></div>
|
|
<div class="rack-row"><span>break time</span><span class="leds"><i class="led"></i><i class="led"></i><i class="led warn"></i></span></div>
|
|
<div class="rack-row"><span>high score</span><span class="leds"><i class="led"></i><i class="led"></i><i class="led"></i></span></div>
|
|
<div class="rack-row"><span>pager mode</span><span class="leds"><i class="led"></i><i class="led warn"></i><i class="led"></i></span></div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</section>
|
|
|
|
<section class="games" id="games" aria-labelledby="games-title">
|
|
<div class="section-head">
|
|
<div>
|
|
<div class="section-label">Available Runbooks</div>
|
|
<h2 id="games-title">Choose Your Game</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="game-grid" id="game-grid" aria-live="polite">
|
|
<div class="empty-state">Loading deployed games from <code>games.json</code>...</div>
|
|
</div>
|
|
</section>
|
|
|
|
<footer class="footer">
|
|
<span>© 2026 <a href="https://www.sysmd.uk/">SysMD</a></span>
|
|
<span>Maintained by <a href="https://www.marcodaleo.com/">Marco D'Aleo</a></span>
|
|
</footer>
|
|
</main>
|
|
<script>
|
|
const gameGrid = document.querySelector('#game-grid');
|
|
const gameCount = document.querySelector('#game-count');
|
|
|
|
const escapeHtml = (value) => String(value ?? '').replace(/[&<>'"]/g, (char) => ({
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
"'": ''',
|
|
'"': '"'
|
|
})[char]);
|
|
|
|
const renderGame = (game) => {
|
|
const tags = Array.isArray(game.tags) ? game.tags : [];
|
|
return `
|
|
<article class="game-card">
|
|
<div class="game-inner">
|
|
<div class="game-top">
|
|
<span class="badge">${escapeHtml(game.status || 'Online')}</span>
|
|
<span class="difficulty">${escapeHtml(game.difficulty || 'Ready')}</span>
|
|
</div>
|
|
<h3 class="game-title">${escapeHtml(game.name)}</h3>
|
|
<p class="game-desc">${escapeHtml(game.description)}</p>
|
|
<div class="tags" aria-label="Game tags">
|
|
${tags.map((tag) => `<span class="tag">${escapeHtml(tag)}</span>`).join('')}
|
|
</div>
|
|
<a class="play-link" href="${escapeHtml(game.url)}">${escapeHtml(game.action || 'Play')} <span aria-hidden="true">></span></a>
|
|
</div>
|
|
</article>
|
|
`;
|
|
};
|
|
|
|
fetch('games.json')
|
|
.then((response) => {
|
|
if (!response.ok) throw new Error(`Unable to load games.json: ${response.status}`);
|
|
return response.json();
|
|
})
|
|
.then((games) => {
|
|
const deployedGames = Array.isArray(games) ? games : [];
|
|
gameCount.textContent = deployedGames.length;
|
|
gameGrid.innerHTML = deployedGames.length
|
|
? deployedGames.map(renderGame).join('')
|
|
: '<div class="empty-state">No games are currently listed in <code>games.json</code>.</div>';
|
|
})
|
|
.catch(() => {
|
|
gameCount.textContent = '?';
|
|
gameGrid.innerHTML = '<div class="empty-state">Could not load <code>games.json</code>. Serve this directory over HTTP and check the games list path.</div>';
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|