Enhance Services Homepage with galaxy background animation and canvas integration

This commit is contained in:
MayaChat
2025-11-24 00:55:02 -05:00
parent 9e034fcfa2
commit d054a2396b
2 changed files with 123 additions and 5 deletions

View File

@@ -5,8 +5,10 @@
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Services Homepage</title> <title>Services Homepage</title>
<link rel="stylesheet" href="/styles.css" /> <link rel="stylesheet" href="/styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head> </head>
<body> <body>
<canvas id="galaxy-canvas"></canvas>
<header> <header>
<h1>My Services</h1> <h1>My Services</h1>
<p class="subtitle">Quick links to the commonly used containers on this host</p> <p class="subtitle">Quick links to the commonly used containers on this host</p>
@@ -32,6 +34,120 @@
<footer> <footer>
<small>Generated: static homepage — served by nginx in a container. Edit and rebuild to update.</small> <small>Generated: static homepage — served by nginx in a container. Edit and rebuild to update.</small>
</footer> </footer>
<script>
// Galaxy background animation with mountains
(function initGalaxy(){
var ww = window.innerWidth;
var wh = window.innerHeight;
var renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: document.querySelector('#galaxy-canvas'),
alpha: false
});
renderer.setSize(ww, wh);
renderer.setClearColor(0x001a2d, 1);
var scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x001a2d, 80, 140);
var camera = new THREE.PerspectiveCamera(45, ww/wh, 0.1, 200);
camera.position.x = 70;
camera.position.y = 30;
camera.position.z = 5;
camera.lookAt(new THREE.Vector3());
// Create galaxy particles
var particles = [];
for(var i = 0; i < 20000; i++){
var particle = new Particle();
particles.push(particle);
scene.add(particle.object);
}
function Particle(){
var radius = Math.random() * 120 + 5;
var height = (Math.random() - 0.5) * 10;
var angle = Math.random() * Math.PI * 2;
this.object = new THREE.Object3D();
this.object.position.y = height;
var geometry = new THREE.SphereGeometry(0.1, 4, 4);
var material = new THREE.MeshBasicMaterial({
color: 0xffffff
});
var mesh = new THREE.Mesh(geometry, material);
mesh.position.x = radius;
this.object.add(mesh);
this.object.rotation.z = angle;
this.update = function(){
this.object.rotation.z += 0.0002 + (120 - radius) * 0.00002;
};
}
// Create terrain/mountains
var terrain = new THREE.Object3D();
var terrainGeometry = new THREE.PlaneGeometry(400, 400, 100, 100);
// Modify vertices to create mountain landscape
var vertices = terrainGeometry.attributes.position.array;
for(var i = 0; i < vertices.length; i += 3){
var x = vertices[i];
var y = vertices[i + 1];
var distance = Math.sqrt(x * x + y * y);
var height = (Math.random() - 0.5) * 12;
height *= Math.max(0, 1 - distance / 180); // Fade height at edges
vertices[i + 2] = height;
}
terrainGeometry.attributes.position.needsUpdate = true;
terrainGeometry.computeVertexNormals();
var terrainMaterial = new THREE.MeshLambertMaterial({
color: 0x0a2540,
wireframe: true,
transparent: true,
opacity: 0.7
});
var terrainMesh = new THREE.Mesh(terrainGeometry, terrainMaterial);
terrainMesh.rotation.x = -Math.PI / 2;
terrainMesh.position.y = -20;
terrain.add(terrainMesh);
scene.add(terrain);
// Add ambient light
var ambientLight = new THREE.AmbientLight(0x4488ff, 0.4);
scene.add(ambientLight);
// Add directional light for terrain
var directionalLight = new THREE.DirectionalLight(0x88ccff, 0.6);
directionalLight.position.set(50, 50, 50);
scene.add(directionalLight);
function render(){
requestAnimationFrame(render);
particles.forEach(function(p){
p.update();
});
terrain.rotation.z += 0.0001;
renderer.render(scene, camera);
}
window.addEventListener('resize', function(){
ww = window.innerWidth;
wh = window.innerHeight;
camera.aspect = ww / wh;
camera.updateProjectionMatrix();
renderer.setSize(ww, wh);
});
render();
})();
</script>
<script> <script>
// Fetch services.xml and render the service cards with logos. // Fetch services.xml and render the service cards with logos.
(async function(){ (async function(){

View File

@@ -3,20 +3,22 @@
--bg:#0f1720;--card:#0b1220;--accent:#4f46e5;--muted:#94a3b8;color-scheme: dark; --bg:#0f1720;--card:#0b1220;--accent:#4f46e5;--muted:#94a3b8;color-scheme: dark;
} }
*{box-sizing:border-box} *{box-sizing:border-box}
html,body{height:100%;margin:0;font-family:Inter,Segoe UI,Roboto,Arial,sans-serif;background:linear-gradient(180deg,#071020 0%,#0b1220 100%);color:#e6eef8} html,body{height:100%;margin:0;font-family:Inter,Segoe UI,Roboto,Arial,sans-serif;background:#001a2d;color:#e6eef8;overflow-x:hidden}
header{padding:24px 20px;text-align:center} #galaxy-canvas{position:fixed;top:0;left:0;width:100%;height:100%;z-index:0}
header,main,footer{position:relative;z-index:10}
header{padding:24px 20px;text-align:center;background:rgba(0,26,45,0.7);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
header h1{margin:0;font-size:28px} header h1{margin:0;font-size:28px}
.subtitle{color:var(--muted);margin-top:6px} .subtitle{color:var(--muted);margin-top:6px}
.search-container{margin-top:16px;max-width:400px;margin-left:auto;margin-right:auto} .search-container{margin-top:16px;max-width:400px;margin-left:auto;margin-right:auto}
#search-input{width:100%;padding:10px 16px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.05);color:#e6eef8;font-size:14px;outline:none;transition:all .3s ease;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)} #search-input{width:100%;padding:10px 16px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(0,15,30,0.8);color:#e6eef8;font-size:14px;outline:none;transition:all .3s ease;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
#search-input::placeholder{color:var(--muted)} #search-input::placeholder{color:var(--muted)}
#search-input:focus{border-color:rgba(79,70,229,0.5);box-shadow:0 0 0 3px rgba(79,70,229,0.2);background:rgba(255,255,255,0.08)} #search-input:focus{border-color:rgba(79,70,229,0.5);box-shadow:0 0 0 3px rgba(79,70,229,0.2);background:rgba(0,15,30,0.9)}
main{max-width:1100px;margin:18px auto;padding:12px} main{max-width:1100px;margin:18px auto;padding:12px}
.service-group{margin-bottom:32px} .service-group{margin-bottom:32px}
.group-header{font-size:18px;font-weight:600;color:#e6eef8;margin:0 0 12px 0;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.1);position:relative} .group-header{font-size:18px;font-weight:600;color:#e6eef8;margin:0 0 12px 0;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.1);position:relative}
.group-header::before{content:'';position:absolute;bottom:-1px;left:0;width:60px;height:2px;background:linear-gradient(90deg,rgba(79,70,229,0.8),rgba(139,92,246,0.5));border-radius:2px} .group-header::before{content:'';position:absolute;bottom:-1px;left:0;width:60px;height:2px;background:linear-gradient(90deg,rgba(79,70,229,0.8),rgba(139,92,246,0.5));border-radius:2px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px} .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}
.card{display:flex;align-items:center;justify-content:center;padding:18px;border-radius:10px;background:linear-gradient(135deg, rgba(79,70,229,0.1), rgba(139,92,246,0.05), rgba(236,72,153,0.1));text-decoration:none;color:inherit;border:1px solid rgba(255,255,255,0.15);font-weight:600;transition:transform .3s ease,box-shadow .3s ease,border-color .3s ease;position:relative;overflow:hidden;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)} .card{display:flex;align-items:center;justify-content:center;padding:18px;border-radius:10px;background:linear-gradient(135deg, rgba(10,37,64,0.85), rgba(15,50,85,0.85), rgba(20,45,75,0.85));text-decoration:none;color:inherit;border:1px solid rgba(255,255,255,0.2);font-weight:600;transition:transform .3s ease,box-shadow .3s ease,border-color .3s ease;position:relative;overflow:hidden;backdrop-filter:blur(15px);-webkit-backdrop-filter:blur(15px)}
.card::before{content:'';position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(45deg,transparent 30%,rgba(255,255,255,0.08) 50%,transparent 70%);transform:rotate(45deg);animation:shimmer 8s infinite linear;pointer-events:none} .card::before{content:'';position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(45deg,transparent 30%,rgba(255,255,255,0.08) 50%,transparent 70%);transform:rotate(45deg);animation:shimmer 8s infinite linear;pointer-events:none}
@keyframes shimmer{0%{left:-100%}100%{left:100%}} @keyframes shimmer{0%{left:-100%}100%{left:100%}}
.card:hover::before{animation-duration:3s} .card:hover::before{animation-duration:3s}