Initial commit
This commit is contained in:
+671
@@ -0,0 +1,671 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>games.sysmd.uk // SysAdmin Arcade</title>
|
||||
<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 manifest path.</div>';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user