<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>All Galaxy โ 3D Real Galaxy Showcase</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
<style>
:root{
--bg-1:#05030a; --bg-2:#0f1226; --glass: rgba(255,255,255,0.04);
--accent:#9b8cff; --muted: rgba(255,255,255,0.65);
}
\*{box-sizing:border-box}
html,body{height:100%;margin:0;font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,'Helvetica Neue',Arial}
body{background:linear-gradient(180deg,var(--bg-1),var(--bg-2));color:#fff;overflow:hidden}
#app{position:relative;height:100vh;width:100vw;display:flex}canvas{display:block}
/* Top-left control panel */
.panel{
position:absolute;left:24px;top:24px;backdrop-filter: blur(8px);background:linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.02));
border-radius:14px;padding:14px;box-shadow:0 6px 30px rgba(0,0,0,0.6);min-width:280px;border:1px solid rgba(255,255,255,0.04)
}
.brand{display:flex;gap:12px;align-items:center}
.logo{height:44px;width:44px;border-radius:10px;background:linear-gradient(135deg,var(--accent),#44e1f8);display:flex;align-items:center;justify-content:center;font-weight:800}
.brand h1{font-size:16px;margin:0}
.brand p{margin:0;font-size:12px;color:var(--muted)}
.row{display:flex;gap:8px;align-items:center;margin-top:12px}
label{font-size:12px;color:var(--muted);width:110px}
input[type=range]{flex:1}
.small{font-size:12px;color:var(--muted);margin-top:8px}
/* Bottom-right info */
.footer{
position:absolute;right:24px;bottom:24px;background:var(--glass);padding:12px 14px;border-radius:10px;border:1px solid rgba(255,255,255,0.03);
box-shadow:0 6px 20px rgba(0,0,0,0.5);
}
.btn{background:linear-gradient(90deg,var(--accent),#44e1f8);padding:8px 12px;border-radius:8px;border:none;color:#051036;font-weight:700;cursor:pointer}
/* Responsive tweaks */
@media (max-width:520px){.panel{left:12px;top:12px;min-width:210px;padding:10px}.brand h1{font-size:14px}}
</style>
</head>
<body>
<div id="app">
\<div id="bg"\>\</div\>
\<div class="panel"\>
\<div class="brand"\>
\<div class="logo"\>G\</div\>
\<div\>
\<h1\>All Galaxy โ 3D\</h1\>
\<p\>Real-time 3D galaxy demo ยท Premium look\</p\>
\</div\>
\</div\>\<div class="row"\>
\<label\>Star count\</label\>
\<input id="stars" type="range" min="1000" max="200000" step="1000" value="50000"\>
</div>
<div class="row">
\<label\>Arms\</label\>
\<input id="arms" type="range" min="1" max="6" step="1" value="3"\>
</div>
<div class="row">
\<label\>Spin\</label\>
\<input id="spin" type="range" min="0" max="3" step="0.01" value="0.8"\>
</div>
<div class="row">
\<label\>Radius\</label\>
\<input id="radius" type="range" min="2" max="20" step="0.5" value="8"\>
</div>
<div class="row">
\<label\>Brightness\</label\>
\<input id="brightness" type="range" min="0.1" max="4" step="0.1" value="1.2"\>
</div>
<div class="small">Drag to tweak the galaxy. Use mouse/touch to orbit. Double-click to toggle fullscreen.</div>
</div>
<div class="footer">
<button id="regen" class="btn">Regenerate</button>
<span style="margin-left:10px;color:var(--muted);font-size:13px">FPS: <span id="fps">0</span></span>
</div>
</div> <script type="module">
import \* as THREE from 'https://unpkg.com/[email protected]/build/three.module.js';
import { OrbitControls } from 'https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js';
// Basic scene + renderer
const container = document.getElementById('app');
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 6, 18);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
// subtle environment
const hemi = new THREE.HemisphereLight(0xffffff, 0x080820, 0.2);
scene.add(hemi);
// galaxy group
let galaxy, points;
// helpers
const params = {
count: 50000,
size: 0.04,
radius: 8,
branches: 3,
spin: 0.8,
randomness: 0.6,
randomnessPower: 2,
insideColor: '#ffb86b',
outsideColor: '#6b8cff',
brightness: 1.2
};
function generateGalaxy() {
if (galaxy !== undefined) {
points.geometry.dispose();
points.material.dispose();
scene.remove(galaxy);
}
galaxy = new THREE.Group();
const positions = new Float32Array(params.count \* 3);
const colors = new Float32Array(params.count \* 3);
const scales = new Float32Array(params.count);
const insideColor = new THREE.Color(params.insideColor);
const outsideColor = new THREE.Color(params.outsideColor);
for (let i = 0; i \< params.count; i++) {
const i3 = i \* 3;
const radius = Math.random() \* params.radius;
const branchAngle = ((i % params.branches) / params.branches) \* Math.PI \* 2;
const spinAngle = radius \* params.spin;
const randomX = Math.pow(Math.random(), params.randomnessPower) \* (Math.random() \< 0.5 ? 1 : -1) \* params.randomness \* radius;
const randomY = Math.pow(Math.random(), params.randomnessPower) \* (Math.random() \< 0.5 ? 1 : -1) \* params.randomness \* radius \* 0.2;
const randomZ = Math.pow(Math.random(), params.randomnessPower) \* (Math.random() \< 0.5 ? 1 : -1) \* params.randomness \* radius;
positions\[i3 + 0\] = Math.cos(branchAngle + spinAngle) \* radius + randomX;
positions\[i3 + 1\] = randomY \* 0.3;
positions\[i3 + 2\] = Math.sin(branchAngle + spinAngle) \* radius + randomZ;
const mixedColor = insideColor.clone();
mixedColor.lerp(outsideColor, radius / params.radius);
colors\[i3 + 0\] = mixedColor.r;
colors\[i3 + 1\] = mixedColor.g;
colors\[i3 + 2\] = mixedColor.b;
scales\[i\] = Math.random() \* 1.5 + 0.5;
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.setAttribute('aScale', new THREE.BufferAttribute(scales, 1));
const material = new THREE.ShaderMaterial({
depthWrite: false,
blending: THREE.AdditiveBlending,
vertexColors: true,
transparent: true,
uniforms: {
uSize: { value: params.size \* 100.0 },
uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
uTime: { value: 0 },
uBrightness: { value: params.brightness }
},
vertexShader: \`
attribute float aScale;
varying vec3 vColor;
uniform float uSize;
uniform float uTime;
void main(){
vColor = color;
vec4 modelPos = modelMatrix \* vec4(position, 1.0);
// slight pulsation
float scale = aScale \* (1.0 + 0.2 \* sin(uTime + position.x \* 0.1));
vec4 viewPos = viewMatrix \* modelPos;
gl_Position = projectionMatrix \* viewPos;
gl_PointSize = uSize \* scale / - viewPos.z;
}
\`,
fragmentShader: \`
varying vec3 vColor;
uniform float uBrightness;
void main(){
float dist = distance(gl_PointCoord, vec2(0.5));
float alpha = 1.0 - smoothstep(0.35, 0.5, dist);
vec3 col = vColor \* uBrightness;
gl_FragColor = vec4(col, alpha);
}
\`
});
points = new THREE.Points(geometry, material);
galaxy.add(points);
// subtle core glow
const coreGeo = new THREE.SphereGeometry(params.radius \* 0.12, 32, 32);
const coreMat = new THREE.MeshBasicMaterial({ color: 0xffe9c0, transparent: true, opacity: 0.18 });
const core = new THREE.Mesh(coreGeo, coreMat);
galaxy.add(core);
scene.add(galaxy);
}
// initial generate with UI values
function refreshFromUI(){
params.count = Number(document.getElementById('stars').value);
params.branches = Number(document.getElementById('arms').value);
params.spin = Number(document.getElementById('spin').value);
params.radius = Number(document.getElementById('radius').value);
params.brightness = Number(document.getElementById('brightness').value);
generateGalaxy();
}
document.getElementById('stars').addEventListener('input', ()=\>{document.getElementById('fps').textContent = 'โ';});
document.getElementById('regen').addEventListener('click', refreshFromUI);
// double click fullscreen
window.addEventListener('dblclick', ()=\>{
const el = renderer.domElement;
if (!document.fullscreenElement) el.requestFullscreen?.(); else document.exitFullscreen?.();
});
let lastTime = performance.now();
let frames = 0;
let fpsUpdate = 0;
// animate
function animate(time){
const elapsed = time \* 0.001;
requestAnimationFrame(animate);
controls.update();
if (points && points.material && points.material.uniforms) {
points.material.uniforms.uTime.value = elapsed \* 1.2;
points.material.uniforms.uBrightness.value = params.brightness;
}
// slow rotation
if (galaxy) galaxy.rotation.y = elapsed \* 0.08 \* params.spin;
renderer.render(scene, camera);
// fps counter
frames++;
if (time - fpsUpdate \> 500) {
const fps = Math.round((frames \* 1000) / (time - fpsUpdate));
document.getElementById('fps').textContent = fps;
frames = 0; fpsUpdate = time;
}
}
// responsive
window.addEventListener('resize', ()=\>{
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
if (points && points.material && points.material.uniforms) points.material.uniforms.uPixelRatio.value = Math.min(window.devicePixelRatio, 2);
});
// initial kick
refreshFromUI();
| header 1 | header 2 |
|---|---|
| cell 1 | cell 2 |
| cell 3 | cell 4 |
| header 1 | header 2 |
|---|---|
| cell 1 | cell 2 |
| cell 3 | cell 4 |
animate(performance.now());
// optional: keyboard shortcuts
window.addEventListener('keydown', (e)=\>{
if (e.key === 'r') refreshFromUI();
});
// touch hint
renderer.domElement.style.touchAction = 'none';
</script></body>
</html>