This commit is contained in:
2025-09-01 19:46:58 +00:00
commit 286b224f05
+523
View File
@@ -0,0 +1,523 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>NeonVerse — 1file Spectacle</title>
<meta name="description" content="Neon glass UI over a Three.js galaxy background. One single HTML file with embedded CSS & JS." />
<!-- Google Font -->
<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=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">
<style>
/* =============================
Modern, clean, and neon style
============================= */
:root {
/* Theme variables */
--bg: #0a0b10;
--text: #eaf3ff;
--muted: #9bb3ff;
--accent: #6cf9ff;
--accent-2: #9b5cff;
--glass: rgba(255, 255, 255, 0.08);
--glass-2: rgba(255, 255, 255, 0.14);
--blur: 18px;
--card-radius: 20px;
--shadow: 0 10px 40px rgba(0,0,0,.35);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
font-family: 'Outfit', system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji';
color: var(--text);
background: radial-gradient(1200px 600px at 20% 20%, #10122b 0%, #090a12 60%), var(--bg);
overflow-x: hidden;
}
/* Fullscreen WebGL background */
#bg-canvas {
position: fixed;
inset: 0;
width: 100vw; height: 100vh;
z-index: -2; /* Behind everything */
display: block;
}
/* Subtle scanline overlay for a cyber feel */
.scanlines {
pointer-events: none;
position: fixed; inset: 0; z-index: -1;
background-image: repeating-linear-gradient(
to bottom,
rgba(255,255,255,0.04),
rgba(255,255,255,0.04) 1px,
transparent 1px,
transparent 3px
);
mix-blend-mode: soft-light;
}
header {
max-width: 1200px; margin: 0 auto; padding: 56px 24px 20px;
display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.brand {
display: flex; align-items: center; gap: 14px;
text-decoration: none; color: var(--text);
}
.brand .logo {
width: 44px; height: 44px; border-radius: 12px;
background: conic-gradient(from 210deg, var(--accent), var(--accent-2), #40f0b5, var(--accent));
box-shadow: 0 0 30px rgba(108, 249, 255, .45);
position: relative; isolation: isolate;
}
.brand .logo::after {
content: ""; position: absolute; inset: 2px; border-radius: 10px;
background: radial-gradient(120px 120px at 30% 30%, rgba(255,255,255,.35), transparent 60%);
filter: blur(8px);
z-index: -1;
}
.brand h1 { font-size: clamp(20px, 2.4vw, 28px); margin: 0; letter-spacing: 0.4px; }
.brand span { display: block; font-weight: 300; font-size: 12px; color: var(--muted); margin-top: -4px; }
.controls {
display: flex; gap: 12px; align-items: center;
}
.btn {
--glow: var(--accent);
border: 1px solid rgba(255,255,255,.15);
color: var(--text);
background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
padding: 12px 16px; border-radius: 12px; cursor: pointer;
backdrop-filter: blur(var(--blur));
box-shadow: var(--shadow), 0 0 0 0 rgba(108,249,255,.45) inset;
transition: box-shadow .3s ease, transform .15s ease;
font-weight: 600; letter-spacing: .3px;
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow), 0 0 0 2px rgba(108,249,255,.45) inset; }
.btn:active { transform: translateY(0); }
.toggle {
display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
user-select: none; padding: 10px 12px; border-radius: 12px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.04);
backdrop-filter: blur(12px);
}
.toggle input { display: none; }
.toggle .dot {
width: 24px; height: 24px; border-radius: 999px; position: relative;
background: radial-gradient(circle at 30% 30%, #fff, #cde 35%, #89f 70%, #48f);
box-shadow: 0 0 16px rgba(108,249,255,.45);
transition: transform .3s ease;
}
.toggle input:checked + .dot { transform: translateX(4px) rotate(16deg) scale(1.02); }
main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 80px; }
.hero {
display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(18px, 4vw, 40px); align-items: center;
}
@media (max-width: 900px) { .hero { grid-template-columns: 1fr; } }
.hero h2 {
font-size: clamp(32px, 6vw, 68px);
line-height: 1.02; margin: 8px 0 10px; font-weight: 800;
text-shadow: 0 10px 50px rgba(108,249,255,.25);
}
.hero p { color: var(--muted); font-size: clamp(14px, 1.8vw, 18px); margin: 0 0 20px; }
.cta {
display: flex; gap: 12px; flex-wrap: wrap;
}
.btn-primary {
--glow: var(--accent-2);
border-color: rgba(155,92,255,.35);
box-shadow: var(--shadow), 0 0 0 0 rgba(155,92,255,.45) inset;
}
.btn-primary:hover { box-shadow: var(--shadow), 0 0 0 2px rgba(155,92,255,.45) inset; }
.card {
border: 1px solid rgba(255,255,255,.12);
background: linear-gradient(180deg, var(--glass), rgba(255,255,255,0.05));
backdrop-filter: blur(var(--blur));
border-radius: var(--card-radius);
box-shadow: var(--shadow);
padding: clamp(14px, 2.6vw, 24px);
transform-style: preserve-3d; perspective: 1200px;
transition: transform .2s ease, box-shadow .35s ease;
}
.card:hover { transform: translateY(-2px) rotateX(1deg) rotateY(-1deg); box-shadow: 0 20px 60px rgba(0,0,0,.45); }
.card h3 { margin: 0 0 8px; font-size: clamp(18px, 2.4vw, 22px); }
.card p { margin: 0; color: var(--muted); }
.grid { margin-top: 28px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 1000px) { .grid { grid-template-columns: repeat(2,1fr); } }
@media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }
.icon {
width: 42px; height: 42px; border-radius: 12px;
display: grid; place-items: center; margin-bottom: 10px;
background: radial-gradient(120px 60px at 30% 20%, rgba(255,255,255,.35), transparent 60%),
linear-gradient(180deg, rgba(108,249,255,.25), rgba(155,92,255,.25));
border: 1px solid rgba(255,255,255,.18);
box-shadow: 0 6px 22px rgba(108,249,255,.3);
}
footer { opacity: .75; text-align: center; padding: 30px 0 40px; font-size: 13px; color: var(--muted); }
/* Decorative blurred blobs */
.blob {
position: fixed; filter: blur(60px); opacity: .35; z-index: -1; pointer-events: none;
width: 45vmax; height: 45vmax; border-radius: 999px;
background: radial-gradient(circle at 30% 30%, rgba(108,249,255,.6), transparent 60%),
radial-gradient(circle at 70% 70%, rgba(155,92,255,.6), transparent 60%);
mix-blend-mode: screen;
}
.blob.one { top: -10vmax; left: -10vmax; }
.blob.two { bottom: -15vmax; right: -10vmax; }
/* "Hyperdrive" visual feedback */
.hyper-outline { outline: 2px solid rgba(108,249,255,.0); outline-offset: 0; transition: outline-color .25s ease, outline-offset .25s ease; }
.hyper-outline.active { outline-color: rgba(108,249,255,.6); outline-offset: 8px; }
/* Hide the noscript hint unless JS is disabled */
noscript { position: fixed; inset: 0; background: #02030a; color: #fff; display: grid; place-items: center; z-index: 999; padding: 32px; text-align: center; }
</style>
</head>
<body>
<canvas id="bg-canvas"></canvas>
<div class="scanlines"></div>
<div class="blob one"></div>
<div class="blob two"></div>
<header>
<a class="brand" href="#">
<div class="logo" aria-hidden="true"></div>
<div>
<h1>NeonVerse</h1>
<span>Un archivo · Todo el espectáculo</span>
</div>
</a>
<div class="controls">
<label class="toggle" title="Modo Calma / Hiper">
<input id="modeToggle" type="checkbox" />
<div class="dot" aria-hidden="true"></div>
<span>Calma</span>
</label>
<button id="btnHyper" class="btn btn-primary">Lanzar Hiperimpulso ⚡️</button>
<button id="btnReset" class="btn">Reiniciar</button>
</div>
</header>
<main>
<section class="hero card hyper-outline" id="hero">
<div>
<h2>Futuro en una sola página</h2>
<p>Fondo 3D reactivo con <strong>Three.js</strong>, animaciones fluidas con <strong>GSAP</strong> y una UI de cristal. Todo en este único archivo HTML.</p>
<div class="cta">
<button class="btn btn-primary" id="btnPulse">Pulso Neón</button>
<button class="btn" id="btnParticles">Explosión de Estrellas</button>
</div>
</div>
<div class="card" style="min-height: 220px; display:flex; align-items:center; justify-content:center;">
<svg width="180" height="160" viewBox="0 0 180 160" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<defs>
<linearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#6cf9ff"/>
<stop offset="100%" stop-color="#9b5cff"/>
</linearGradient>
</defs>
<g opacity="0.95" stroke="url(#grad)" stroke-width="2">
<path d="M20 120 C 60 40, 120 40, 160 120"/>
<circle cx="90" cy="80" r="36"/>
<circle cx="90" cy="80" r="56" opacity=".45"/>
<circle cx="90" cy="80" r="70" opacity=".2"/>
</g>
</svg>
</div>
</section>
<section class="grid">
<article class="card">
<div class="icon">⚙️</div>
<h3>Interacción en tiempo real</h3>
<p>El campo de estrellas responde al ratón y a la velocidad del modo <em>Hiper</em>.</p>
</article>
<article class="card">
<div class="icon">🧪</div>
<h3>Una sola dependencia</h3>
<p>Three.js para el 3D y GSAP para animar. Nada más. Ligero y espectacular.</p>
</article>
<article class="card">
<div class="icon">🎛️</div>
<h3>Controles divertidos</h3>
<p>Activa el pulso, explota partículas o enciende el hiperimpulso y siente la aceleración.</p>
</article>
</section>
<footer>
Hecho con ♥︎ · Three.js · GSAP · HTML/CSS · En un solo archivo
</footer>
</main>
<noscript>
<div>
<h2>Necesitas JavaScript</h2>
<p>Esta demo usa WebGL y animaciones. Activa JavaScript para verla en acción ✨</p>
</div>
</noscript>
<!-- External JS libraries (CDN). Still a single HTML file; no separate JS/CSS files used. -->
<script src="https://unpkg.com/three@0.160.0/build/three.min.js"></script>
<script src="https://unpkg.com/gsap@3.12.5/dist/gsap.min.js"></script>
<script>
// =============================
// NeonVerse — Single-file demo
// (c) You — Free to tweak
// Comments in EN as requested
// =============================
// ---- Three.js setup ----
const canvas = document.getElementById('bg-canvas');
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(0, 0, 140);
// Starfield parameters
const STAR_COUNT = 3000; // adjust for performance
const galaxy = new THREE.Group();
// Create a spiral galaxy-like distribution
const positions = new Float32Array(STAR_COUNT * 3);
const colors = new Float32Array(STAR_COUNT * 3);
// Helper to set HSL into rgb Float32 array
const color = new THREE.Color();
for (let i = 0; i < STAR_COUNT; i++) {
// Spiral params
const radius = Math.pow(Math.random(), 0.6) * 380 + 20; // denser near center
const arms = 3;
const branchAngle = ((i % arms) / arms) * Math.PI * 2;
const spin = radius * 0.035;
const randomness = (Math.random() - 0.5) * 28;
const angle = branchAngle + spin;
const x = Math.cos(angle) * radius + randomness;
const y = (Math.random() - 0.5) * 24 + Math.sin(angle * 3) * 3; // slight vertical waves
const z = Math.sin(angle) * radius + randomness;
const idx = i * 3;
positions[idx] = x; positions[idx + 1] = y; positions[idx + 2] = z;
// Color gradient across radius (neon cyan -> magenta)
const t = radius / 420;
color.setHSL(0.55 + 0.3 * (1 - t), 0.9, 0.6); // 0.55≈cyan to 0.85≈magenta
colors[idx] = color.r; colors[idx + 1] = color.g; colors[idx + 2] = color.b;
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 1.7,
vertexColors: true,
transparent: true,
opacity: 0.95,
depthWrite: false,
blending: THREE.AdditiveBlending
});
const points = new THREE.Points(geometry, material);
galaxy.add(points);
scene.add(galaxy);
// Subtle core glow (sprite)
const coreTexture = new THREE.CanvasTexture(generateRadialGlow(256));
const coreMaterial = new THREE.SpriteMaterial({ map: coreTexture, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, opacity: 0.8 });
const core = new THREE.Sprite(coreMaterial);
core.scale.set(120, 120, 1);
scene.add(core);
function generateRadialGlow(size) {
// Create a radial gradient canvas for the galaxy core
const c = document.createElement('canvas');
c.width = c.height = size;
const ctx = c.getContext('2d');
const g = ctx.createRadialGradient(size/2, size/2, 0, size/2, size/2, size/2);
g.addColorStop(0, 'rgba(255,255,255,0.95)');
g.addColorStop(0.25, 'rgba(108,249,255,0.85)');
g.addColorStop(0.55, 'rgba(155,92,255,0.55)');
g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = g; ctx.fillRect(0,0,size,size);
return c;
}
// Interaction state
const state = {
rotSpeed: 0.0026, // base rotation speed
zoom: 0,
hyper: false,
parallaxX: 0,
parallaxY: 0
};
// Parallax by mouse/touch
const lerp = (a, b, t) => a + (b - a) * t;
let targetX = 0, targetY = 0;
window.addEventListener('pointermove', (e) => {
const nx = (e.clientX / window.innerWidth) * 2 - 1;
const ny = (e.clientY / window.innerHeight) * 2 - 1;
targetX = nx * 0.6;
targetY = ny * 0.4;
}, { passive: true });
// Animation loop
const clock = new THREE.Clock();
function animate() {
const dt = clock.getDelta();
// Smooth parallax
state.parallaxX = lerp(state.parallaxX, targetX, 0.05);
state.parallaxY = lerp(state.parallaxY, targetY, 0.05);
camera.position.x = state.parallaxX * 20;
camera.position.y = -state.parallaxY * 12;
camera.lookAt(0,0,0);
// Galaxy rotation
galaxy.rotation.y += state.rotSpeed;
galaxy.rotation.x = lerp(galaxy.rotation.x, state.parallaxY * 0.25, 0.02);
// Subtle material twinkle
const time = performance.now() * 0.001;
material.size = 1.5 + Math.sin(time * 1.7) * 0.2 + (state.hyper ? 0.6 : 0);
material.opacity = 0.9 + Math.sin(time * 0.9) * 0.08 + (state.hyper ? 0.05 : 0);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
// Handle resize
addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
// ---- GSAP UI animations ----
const hero = document.getElementById('hero');
const btnHyper = document.getElementById('btnHyper');
const btnReset = document.getElementById('btnReset');
const btnPulse = document.getElementById('btnPulse');
const btnParticles = document.getElementById('btnParticles');
const modeToggle = document.getElementById('modeToggle');
// Intro animation
gsap.from(['.brand', '.controls'], { y: -16, opacity: 0, duration: 0.9, ease: 'power2.out', stagger: 0.08 });
gsap.from('.hero .card', { y: 20, opacity: 0, duration: 1, ease: 'power3.out', stagger: 0.14, delay: 0.1 });
gsap.from('.grid .card', { y: 24, opacity: 0, duration: 0.9, ease: 'power3.out', stagger: 0.08, delay: 0.25 });
// Helper: pulse outline on hero card
function pulseOutline() {
hero.classList.add('active');
gsap.fromTo(hero, { outlineOffset: 0 }, { outlineOffset: 8, duration: 0.3, yoyo: true, repeat: 1, onComplete: () => hero.classList.remove('active') });
}
// Hyperdrive action — accelerate, recolor subtly, add camera shake
btnHyper.addEventListener('click', () => {
state.hyper = true;
pulseOutline();
gsap.to(state, { rotSpeed: 0.020, duration: 0.8, ease: 'power3.out' });
// Quick camera kick
gsap.fromTo(camera.position, { z: 160 }, { z: 120, duration: 0.8, ease: 'power2.out', yoyo: true, repeat: 1 });
// Screen pulse via blobs
gsap.to('.blob', { opacity: 0.55, scale: 1.08, duration: 0.6, yoyo: true, repeat: 1, ease: 'sine.inOut' });
});
// Reset to calm
btnReset.addEventListener('click', () => {
state.hyper = false;
gsap.to(state, { rotSpeed: 0.0026, duration: 1.2, ease: 'power3.inOut' });
gsap.to(camera.position, { x: 0, y: 0, z: 140, duration: 0.9, ease: 'power2.inOut' });
gsap.to('.blob', { opacity: 0.35, duration: 0.8 });
});
// Neon pulse — quick UI and core glow amp
btnPulse.addEventListener('click', () => {
pulseOutline();
gsap.fromTo(core.material, { opacity: 0.6 }, { opacity: 1, duration: 0.35, yoyo: true, repeat: 1, ease: 'sine.inOut' });
gsap.to('.brand .logo', { boxShadow: '0 0 60px rgba(108,249,255,.8)', duration: 0.35, yoyo: true, repeat: 1, ease: 'sine.inOut' });
});
// Particles burst — radial push on positions for a brief spark
btnParticles.addEventListener('click', () => {
const pos = geometry.attributes.position;
const burst = [];
for (let i = 0; i < STAR_COUNT; i += Math.floor(Math.random()*30)+15) {
const idx = i * 3;
const x = pos.array[idx], y = pos.array[idx+1], z = pos.array[idx+2];
const len = Math.max(60, Math.hypot(x, y, z));
const nx = x/len, ny = y/len, nz = z/len;
burst.push({ idx, dx: nx * 20, dy: ny * 20, dz: nz * 20 });
}
// Animate selected vertices
gsap.to(burst, {
duration: 0.5,
ease: 'power3.out',
onUpdate: () => {
burst.forEach(b => {
pos.array[b.idx] += b.dx * 0.35;
pos.array[b.idx+1] += b.dy * 0.35;
pos.array[b.idx+2] += b.dz * 0.35;
});
pos.needsUpdate = true;
}
});
// spring back
gsap.to({}, {
duration: 0.7,
delay: 0.35,
ease: 'power2.in',
onUpdate: () => { galaxy.rotation.y += 0.01; }
});
pulseOutline();
});
// Mode toggle: calm vs hyper bias
modeToggle.addEventListener('change', (e) => {
const on = e.target.checked;
state.hyper = on;
gsap.to(state, { rotSpeed: on ? 0.010 : 0.003, duration: 0.8, ease: 'power2.inOut' });
gsap.to('.blob', { opacity: on ? 0.5 : 0.35, duration: 0.6 });
});
// Accessibility: key shortcuts
addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'h') btnHyper.click();
if (e.key.toLowerCase() === 'r') btnReset.click();
if (e.key.toLowerCase() === 'p') btnPulse.click();
});
// Optional: reduce motion respect
const mq = matchMedia('(prefers-reduced-motion: reduce)');
if (mq.matches) { state.rotSpeed = 0.001; }
</script>
</body>
</html>