Add hyperspace starfield to landing page
Replaces the static Backstage template page with a canvas-based warp starfield and a glowing "SETH'S TEST" HUD overlay. Self-contained, no external assets; star count is fixed and resize-safe.
This commit is contained in:
@@ -13,7 +13,7 @@ def index():
|
||||
return render_template(
|
||||
"index.html",
|
||||
name="sethtest",
|
||||
description="Seth doing stuff",
|
||||
description="Warp drive engaged",
|
||||
)
|
||||
|
||||
|
||||
|
||||
+168
-30
@@ -3,50 +3,188 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{ name }}</title>
|
||||
<title>{{ name }} — warp drive</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #0f172a;
|
||||
background: #f8fafc;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
color: #cfe7ff;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 760px;
|
||||
margin: 6rem auto;
|
||||
padding: 0 1.5rem;
|
||||
canvas#stars {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: block;
|
||||
}
|
||||
|
||||
section {
|
||||
background: #ffffff;
|
||||
border: 1px solid #dbe3ee;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
.hud {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
.title {
|
||||
font-size: clamp(2.5rem, 8vw, 6rem);
|
||||
letter-spacing: 0.35em;
|
||||
font-weight: 700;
|
||||
color: #e8f6ff;
|
||||
text-shadow:
|
||||
0 0 8px #6cb9ff,
|
||||
0 0 24px #2b7bd6,
|
||||
0 0 64px #103a78;
|
||||
animation: pulse 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
line-height: 1.6;
|
||||
color: #334155;
|
||||
.subtitle {
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.4em;
|
||||
opacity: 0.7;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.meta {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.2em;
|
||||
opacity: 0.5;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
text-shadow:
|
||||
0 0 8px #6cb9ff,
|
||||
0 0 24px #2b7bd6,
|
||||
0 0 64px #103a78;
|
||||
}
|
||||
50% {
|
||||
text-shadow:
|
||||
0 0 12px #9fd2ff,
|
||||
0 0 36px #4aa0ff,
|
||||
0 0 96px #1d57b3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<section>
|
||||
<h1>{{ name }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
<ul>
|
||||
<li>Backed by Flask and Gunicorn</li>
|
||||
<li>Container listens on port 8080</li>
|
||||
<li>Health check is available at <code>/health</code></li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
<canvas id="stars"></canvas>
|
||||
<div class="hud">
|
||||
<div class="title">SETH'S TEST</div>
|
||||
<div class="subtitle">{{ description }}</div>
|
||||
</div>
|
||||
<div class="meta">{{ name }} · /health</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const canvas = document.getElementById("stars");
|
||||
if (!canvas || !canvas.getContext) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const STAR_COUNT = 400;
|
||||
const MAX_Z = 1.0;
|
||||
const SPEED = 0.006;
|
||||
const FOCAL = 320;
|
||||
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let cx = 0;
|
||||
let cy = 0;
|
||||
let stars = [];
|
||||
|
||||
function resize() {
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = Math.floor(width * dpr);
|
||||
canvas.height = Math.floor(height * dpr);
|
||||
canvas.style.width = width + "px";
|
||||
canvas.style.height = height + "px";
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
cx = width / 2;
|
||||
cy = height / 2;
|
||||
}
|
||||
|
||||
function spawn() {
|
||||
const z = Math.random() * (MAX_Z - 0.2) + 0.2;
|
||||
return {
|
||||
x: (Math.random() - 0.5) * 2,
|
||||
y: (Math.random() - 0.5) * 2,
|
||||
z: z,
|
||||
pz: z
|
||||
};
|
||||
}
|
||||
|
||||
function reset(s) {
|
||||
s.x = (Math.random() - 0.5) * 2;
|
||||
s.y = (Math.random() - 0.5) * 2;
|
||||
s.z = MAX_Z;
|
||||
s.pz = MAX_Z;
|
||||
}
|
||||
|
||||
function project(x, y, z) {
|
||||
return { sx: (x / z) * FOCAL + cx, sy: (y / z) * FOCAL + cy };
|
||||
}
|
||||
|
||||
function frame() {
|
||||
// Trail effect: fade the previous frame instead of clearing.
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.lineCap = "round";
|
||||
|
||||
for (let i = 0; i < stars.length; i++) {
|
||||
const s = stars[i];
|
||||
s.pz = s.z;
|
||||
s.z -= SPEED;
|
||||
if (s.z <= 0.02) {
|
||||
reset(s);
|
||||
continue;
|
||||
}
|
||||
const p = project(s.x, s.y, s.z);
|
||||
if (
|
||||
p.sx < -50 ||
|
||||
p.sx > width + 50 ||
|
||||
p.sy < -50 ||
|
||||
p.sy > height + 50
|
||||
) {
|
||||
reset(s);
|
||||
continue;
|
||||
}
|
||||
const pp = project(s.x, s.y, s.pz);
|
||||
const k = 1 - s.z / MAX_Z;
|
||||
const w = Math.max(0.4, k * 2.2);
|
||||
ctx.strokeStyle = "rgba(220, 240, 255, " + (0.3 + k * 0.7) + ")";
|
||||
ctx.lineWidth = w;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pp.sx, pp.sy);
|
||||
ctx.lineTo(p.sx, p.sy);
|
||||
ctx.stroke();
|
||||
}
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", resize);
|
||||
resize();
|
||||
stars = new Array(STAR_COUNT);
|
||||
for (let i = 0; i < STAR_COUNT; i++) stars[i] = spawn();
|
||||
requestAnimationFrame(frame);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user