stable intro

This commit is contained in:
STEINNI
2025-11-11 08:08:52 +00:00
parent e9e4169c37
commit b5a23217c6
5 changed files with 204 additions and 406 deletions
+191 -178
View File
@@ -3,211 +3,224 @@ import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
// === Scene & Camera ===
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100)
camera.position.set(0.1, 0.02, 4)
//camera.position.z = 4
class Intro{
// === Renderer (transparent) ===
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
canvas: document.querySelector('.intro3d')
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setClearColor(0x000000, 0)
renderer.outputColorSpace = THREE.SRGBColorSpace
#lettersZ=-1
#onResizeCallbacks = []
constructor(){
this.canvas = document.querySelector('.intro3d')
const loadHDR = () =>
new Promise((resolve, reject) => {
new RGBELoader()
.setPath('/app/assets/images/')
.load('kloofendal_48d_partly_cloudy_puresky_2k.hdr', resolve, this.hdrProgress, reject)
})
const loadTexture = () =>
new Promise((resolve, reject) => {
new THREE.TextureLoader()
.setPath('/app/assets/images/')
.load('logop42-intro_three.png', resolve, undefined, reject)
})
Promise.allSettled([loadHDR(), loadTexture()]).then(this.initScene.bind(this))
this.Xcorrection = -0.02
}
const rgbeLoader = new RGBELoader()
rgbeLoader.load('/app/assets/images/satara_night_no_lamps_2k.hdr', hdrTexture => {
hdrTexture.mapping = THREE.EquirectangularReflectionMapping
scene.environment = hdrTexture
scene.background = hdrTexture
})
hdrProgress(xhr){
const pb = document.querySelector('div.progress')
if (xhr.lengthComputable) {
const percent = (xhr.loaded / xhr.total) * 100
pb.style.width = percent.toFixed(0)
if(percent.toFixed(0)>=99) pb.style.display = 'none'
}
}
initScene(LoadersResults){
let hdrTexture
const [hdrResult, texResult] = LoadersResults
if (hdrResult.status === 'fulfilled' && texResult.status === 'fulfilled') {
hdrTexture = hdrResult.value
this.backgroundTexture = texResult.value
// === Load background as a plane that fits the viewport ===
const loader = new THREE.TextureLoader()
let plane
let planeDistance = 4.4
hdrTexture.mapping = THREE.EquirectangularReflectionMapping
this.backgroundTexture.colorSpace = THREE.SRGBColorSpace
} else {
console.error('Loading error:', hdrResult, texResult)
return
}
loader.load('/app/assets/images/logop42-intro_three.png', texture => {
this.scene = new THREE.Scene()
texture.colorSpace = THREE.SRGBColorSpace
texture.wrapS = THREE.ClampToEdgeWrapping
texture.wrapT = THREE.ClampToEdgeWrapping
texture.minFilter = THREE.LinearFilter
texture.magFilter = THREE.LinearFilter
const imageAspect = texture.image.width / texture.image.height
// Create plane sized to exactly fill the camera frustum at Z = -planeDistance
function makeOrResizePlane() {
const fovRad = THREE.MathUtils.degToRad(camera.fov)
this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100)
this.camera.position.set(0.1, 0.02, 4)
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
canvas: this.canvas
})
hdrTexture.mapping = THREE.EquirectangularReflectionMapping
this.scene.environment = hdrTexture
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
this.renderer.setSize(window.innerWidth, window.innerHeight)
this.renderer.setClearColor(0x000000, 0)
this.renderer.outputColorSpace = THREE.SRGBColorSpace
this.backgroundTexture.colorSpace = THREE.SRGBColorSpace
this.backgroundTexture.wrapS = THREE.ClampToEdgeWrapping
this.backgroundTexture.wrapT = THREE.ClampToEdgeWrapping
this.backgroundTexture.generateMipmaps = false
this.backgroundTexture.minFilter = THREE.LinearFilter
this.backgroundTexture.magFilter = THREE.LinearFilter
this.imageAspect = this.backgroundTexture.image.width / this.backgroundTexture.image.height
this.makeOrResizePlane()
this.#onResizeCallbacks.push(this.makeOrResizePlane.bind(this))
// === Lights ===
const l1 = new THREE.DirectionalLight(0xffbb33, 1)
l1.position.set(-1, 1.5, 6)
this.scene.add(l1)
const l2 = new THREE.DirectionalLight(0xffbb33, 1)
l2.position.set(2, -1.5, 6)
this.scene.add(l2)
this.lettersPosScale =[
{ position: { x: -0.98, y: 0.02, z: -1 }, scale: { x: 1, y: 1.12, z: 1 } },
{ position: { x: -0.63, y: -0.04, z: -1.05 }, scale: { x: 1, y: 1.05, z: 1 } },
{ position: { x: -0.28, y: -0.05, z: -1.1 }, scale: { x: 0.9, y: 1.15, z: 1, X: 0.8 } },
{ position: { x: -0.1, y: -0.04, z: -1.15 }, scale: { x: 1, y: 1, z: 1 } },
{ position: { x: 0.12, y: -0.06, z: -1.2 }, scale: { x: 0.65, y: 1, z: 1 } },
{ position: { x: 0.355, y: -0.055, z: -1.25 }, scale: { x: 0.5, y: 0.9, z: 1, Y: 0.9 } },
{ position: { x: 0.53, y: -0.01, z: -1.3 }, scale: { x: 1, y: 0.94, z: 1 } },
{ position: { x: 0.84, y: -0.05, z: -1.35 }, scale: { x: 0.95, y: 1.04, z: 1 } },
{ position: { x: 1.17, y: -0.05, z: -1.4 }, scale: { x: 0.7, y: 1.02, z: 1 } },
]
this.glassLetters = []
const text = 'Project42'
const fontLoader = new FontLoader()
fontLoader.load('/app/thirdparty/Three/fonts/helvetiker_regular.typeface.json', font => {
const mat = new THREE.MeshPhysicalMaterial({
metalness: 0.05,
roughness: 0.02,
transmission: 1,
thickness: 0.8,
ior: 1.45,
envMapIntensity: 1.5,
attenuationColor: new THREE.Color(0x88ffcc),
attenuationDistance: 2,
color: 0x88ffcc,
})
for (let i = 0; i < text.length; i++) {
const char = text[i]
const geo = new TextGeometry(char, {
font,
size: .5,
height: 0.15,
curveSegments: 32,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.03,
bevelSegments: 32
})
geo.center()
const mesh = new THREE.Mesh(geo, mat)
mesh.position.set(this.lettersPosScale[i].position.x + this.Xcorrection, this.lettersPosScale[i].position.y, this.lettersPosScale[i].position.z)
mesh.scale.set(this.lettersPosScale[i].scale.x, this.lettersPosScale[i].scale.y, this.lettersPosScale[i].scale.z)
this.scene.add(mesh)
this.glassLetters.push(mesh)
}
})
// === Resize handling ===
window.addEventListener('resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight
this.camera.updateProjectionMatrix()
this.renderer.setSize(window.innerWidth, window.innerHeight)
this.#onResizeCallbacks.forEach(fn => fn())
})
this.canvas.classList.add('ready')
this.nextAnim = Date.now()+2000
this.animate()
}
makeOrResizePlane() {
const planeDistance = 4.4
const fovRad = THREE.MathUtils.degToRad(this.camera.fov)
const viewHeight = 2 * Math.tan(fovRad / 2) * planeDistance
const viewWidth = viewHeight * camera.aspect
const viewWidth = viewHeight * this.camera.aspect
let planeWidth, planeHeight
if (viewWidth / viewHeight > imageAspect) {
if (viewWidth / viewHeight > this.imageAspect) {
planeHeight = viewHeight
planeWidth = planeHeight * imageAspect
planeWidth = planeHeight * this.imageAspect
} else {
planeWidth = viewWidth
planeHeight = planeWidth / imageAspect
planeHeight = planeWidth / this.imageAspect
}
if (!plane) {
if (!this.plane) {
const geo = new THREE.PlaneGeometry(planeWidth, planeHeight)
const mat = new THREE.MeshBasicMaterial({ map: texture })
plane = new THREE.Mesh(geo, mat)
plane.scale.set(1.44, 1.5, 1)
plane.position.z = 0
plane.renderOrder = 0
scene.add(plane)
const mat = new THREE.MeshBasicMaterial({ map: this.backgroundTexture })
this.plane = new THREE.Mesh(geo, mat)
this.plane.scale.set(1.44, 1.5, 1)
this.plane.position.x = this.Xcorrection
this.plane.position.z = 0
this.plane.renderOrder = 0
this.scene.add(this.plane)
} else {
plane.geometry.dispose()
plane.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight)
this.plane.geometry.dispose()
this.plane.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight)
}
}
makeOrResizePlane()
onResizeCallbacks.push(makeOrResizePlane)
})
const glassMat = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: .05,
roughness: 0.05, //0.02,
transmission: 1, //1, // true glass
thickness: 1,//0.6,
ior: 1.45, //1.5,
envMapIntensity: 1.2,
attenuationColor: new THREE.Color(0x88ffcc),
attenuationDistance: 2,
envMapIntensity: 2.5
})
animate() {
requestAnimationFrame(this.animate.bind(this))
if(Date.now()>this.nextAnim){
this.#lettersZ += .005
for(const letter of this.glassLetters){
letter.position.z += 0.005
}
// === Lights ===
const l1 = new THREE.DirectionalLight(0xffbb33, 1)
l1.position.set(-1, 1.5, 6)
scene.add(l1)
const l2 = new THREE.DirectionalLight(0xffbb33, 1)
l2.position.set(2, -1.5, 6)
scene.add(l2)
if(this.#lettersZ>1.5) {
for(const [i, letter] of this.glassLetters.entries()){
letter.rotation.x += Math.abs(i-4)/-1000
letter.rotation.y += (i-4)/2000
letter.position.x += (i-4)/4000
}
}
const lettersPosScale =[
{ position: { x: -0.98, y: 0.02, z: -1 }, scale: { x: 1, y: 1.12, z: 1 } },
{ position: { x: -0.63, y: -0.04, z: -1.05 }, scale: { x: 1, y: 1.05, z: 1 } },
{ position: { x: -0.28, y: -0.05, z: -1.1 }, scale: { x: 0.9, y: 1.15, z: 1, X: 0.8 } },
{ position: { x: -0.1, y: -0.04, z: -1.15 }, scale: { x: 1, y: 1, z: 1 } },
{ position: { x: 0.12, y: -0.06, z: -1.2 }, scale: { x: 0.65, y: 1, z: 1 } },
{ position: { x: 0.355, y: -0.055, z: -1.25 }, scale: { x: 0.5, y: 0.9, z: 1, Y: 0.9 } },
{ position: { x: 0.53, y: -0.01, z: -1.3 }, scale: { x: 1, y: 0.94, z: 1 } },
{ position: { x: 0.84, y: -0.05, z: -1.35 }, scale: { x: 0.95, y: 1.04, z: 1 } },
{ position: { x: 1.17, y: -0.05, z: -1.4 }, scale: { x: 0.7, y: 1.02, z: 1 } },
]
const glassLetters = []
const text = 'Project42'
const fontLoader = new FontLoader()
fontLoader.load('/app/thirdparty/Three/fonts/helvetiker_regular.typeface.json', font => {
const mat = new THREE.MeshPhysicalMaterial({
metalness: 0.2,
roughness: 0.04,
transmission: 1,
thickness: 0.8,
ior: 1.45,
envMapIntensity: 1.2,
attenuationColor: new THREE.Color(0x88ffcc),
attenuationDistance: 2,
color: 0x88ffcc,
emissive: 0x00CCFF,
emissiveIntensity: 0.05,
})
for (let i = 0; i < text.length; i++) {
const char = text[i]
const geo = new TextGeometry(char, {
font,
size: .5,
height: 0.15,
curveSegments: 32,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.03,
bevelSegments: 32
})
geo.center()
const mesh = new THREE.Mesh(geo, mat)
mesh.position.set(lettersPosScale[i].position.x, lettersPosScale[i].position.y, lettersPosScale[i].position.z)
mesh.scale.set(lettersPosScale[i].scale.x, lettersPosScale[i].scale.y, lettersPosScale[i].scale.z)
scene.add(mesh)
glassLetters.push(mesh)
}
})
// Helper: update envMap (hide cube → capture → show cube)
function updateEnvMap() {
const wasVisible = cube.visible
cube.visible = false
cubeCam.update(renderer, scene)
cube.visible = wasVisible
glassMat.envMap = cubeRT.texture
glassMat.needsUpdate = true
}
let t = 0
let lettersZ=-1
function animate() {
requestAnimationFrame(animate)
lettersZ += .005
for(const letter of glassLetters){
letter.position.z += 0.005 //= lettersZ
}
if(lettersZ>1.5) {
for(const [i, letter] of glassLetters.entries()){
letter.rotation.x += Math.abs(i-4)/-1000
letter.rotation.y += (i-4)/2000
letter.position.x += (i-4)/4000
if(this.#lettersZ>4.5) {
this.#lettersZ = -1
for(const [i, letter] of this.glassLetters.entries()){
letter.rotation.y = 0
letter.rotation.x = 0
letter.position.x = this.lettersPosScale[i].position.x + this.Xcorrection
letter.position.z = this.lettersPosScale[i].position.z
}
this.nextAnim = Date.now()+10000
}
}
}
if(lettersZ>4.5) {
lettersZ = -1
for(const [i, letter] of glassLetters.entries()){
letter.rotation.y = 0
letter.rotation.x = 0
letter.position.x = lettersPosScale[i].position.x
letter.position.z = lettersPosScale[i].position.z
}
}
// refresh envmap if anything moves around glass to update reflections
//if ((t++ % 2) === 0) updateEnvMap()
renderer.render(scene, camera)
this.renderer.render(this.scene, this.camera)
}
}
animate()
// === Resize handling ===
const onResizeCallbacks = []
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
onResizeCallbacks.forEach(fn => fn())
})
new Intro()
-228
View File
@@ -1,228 +0,0 @@
body {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100vh;
background: #161616;
overflow: hidden;
font-family: sans;
}
canvas.intro3d{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
background:transparent;
pointer-events: none;
z-index:99;
}
.glowing {
position: absolute;
width: 60vw;
height: 100vh;
transform-origin: right;
animation: colorChange 5s linear infinite;
animation: fadeaway 30s forwards;
transform: translateX(-50%);
}
.glowing.lt {
left: 0%;
transform: rotate(-15deg);
top: -15%;
}
.glowing.ct {
left: 50%;
}
.glowing.rt {
left: 50%;
transform: rotate(15deg)
}
.glowing:nth-child(even) {
transform-origin: left;
}
@keyframes colorChange {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(360deg); }
}
.glowing span {
position: absolute;
top: calc(80px * var(--i));
left: calc(80px * var(--i));
bottom: calc(80px * var(--i));
right: calc(80px * var(--i));
filter: blur(2px);
}
.glowing span::before {
content: "";
position: absolute;
top: 50%;
left: -8px;
width: 10px;
height: 10px;
background: #f00;
border-radius: 50%;
}
.glowing span:nth-child(3n + 1)::before {
background: rgba(134,255,0,1);
box-shadow: 0 0 20px rgba(134,255,0,1),
0 0 40px rgba(134,255,0,1),
0 0 60px rgba(134,255,0,1),
0 0 80px rgba(134,255,0,1),
0 0 0 8px rgba(134,255,0,.1);
}
.glowing span:nth-child(3n + 2)::before {
background: rgba(0,255,183,1);
box-shadow: 0 0 20px rgba(0,255,183,1),
0 0 40px rgba(0,255,183,1),
0 0 60px rgba(0,255,183,1),
0 0 80px rgba(0,255,183,1),
0 0 0 8px rgba(0,255,183,.1);
}
.glowing span:nth-child(3n + 3)::before {
background: rgba(0,226,255,1);
box-shadow: 0 0 20px rgba(0,226,255,1),
0 0 40px rgba(0,226,255,1),
0 0 60px rgba(0,226,255,1),
0 0 80px rgba(0,226,255,1),
0 0 0 8px rgba(0,226,255,.1);
}
.glowing span:nth-child(3n + 1) {
animation: rotatedots1 15s alternate infinite;
}
.glowing span:nth-child(3n + 2) {
animation: rotatedots-reverse 9s alternate infinite;
}
.glowing span:nth-child(3n + 3) {
animation: rotatedots1 15s alternate infinite;
}
@keyframes fadeaway {
0% { opacity: 1; }
15% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes rotatedots1 {
0% { transform: rotate(calc(180deg * var(--i))); scale:20%;}
25% { transform: rotate(calc(90deg * var(--i))); scale:100%;}
50% { transform: rotate(calc(0deg * var(--i))); scale:80%;}
100% { transform: rotate(calc(360deg * var(--i))); scale:10%;}
}
@keyframes rotatedots-reverse {
0% { transform: rotate(calc(135deg * var(--i))); scale:20%;}
25% { transform: rotate(calc(45deg * var(--i))); scale:100%;}
50% { transform: rotate(calc(-45deg * var(--i))); scale:80%;}
100% { transform: rotate(calc(315deg * var(--i))); scale:10%;}
}
.logointro{
filter: blur(14px);
opacity: 0;
animation: logoanim 5s ease-out forwards; /* runs once */
will-change: filter, opacity;
color: #FFF;
position: absolute;
text-shadow: 3px -3px 5px #C5F7FF;
}
@keyframes logoanim{
0% { filter: blur(14px); opacity: 0; transform: scale(0.1) }
100% { filter: blur(0); opacity: 1; transform: scale(0.7) }
}
@keyframes startbtnanim{
0% { opacity: 0; }
40% { opacity: 0.8; }
100% { opacity: 1; }
}
#startbtn{
cursor: default;
position: absolute;
bottom: 15vh;
color: aliceblue;
font-style: italic;
font-size: 1.5rem;
border-radius: 100px;
padding: 5px 40px;
background: radial-gradient(#82cc50, #0a8200);
box-shadow: 2px 2px 10px #BAFFEF, -2px -2px 10px #BAFFEF, -2px 0 10px #BAFFEF, 0 -2px 10px #BAFFEF;
animation: startbtnanim 5s ease-out forwards;
z-index: 99;
}
#startbtn[disabled]{
filter: brightness(.3);
}
#login-dialog{
width: 22rem;
z-index: 99;
border-radius: 1rem;
box-shadow: 2px 2px 10px #BAFFEF, -2px -2px 10px #BAFFEF, -2px 0 10px #BAFFEF, 0 -2px 10px #BAFFEF;
background: radial-gradient(#82cc50, #0a8200);
padding: 2rem 3rem 1rem 3rem;
margin-top: -68vh;
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease;
font-size: 1.3rem;
transform: perspective(200px) rotateY(0deg) rotateX(6deg) rotateZ(0deg);
transform-style: preserve-3d;
}
#login-dialog.show {
opacity: 1;
visibility: visible;
}
#login-dialog > div.cols2{
display: grid !important;
grid-gap: 10px;
grid-template-columns: 7rem 15rem;
}
#login-dialog > div{
margin-bottom: 1rem;
}
#login-dialog input{
line-height: 2rem;
width: 15rem;
border-radius: 5px;
border: none;
font-size: 1rem;
}
#login-dialog button{
justify-self: end;
font-size: 1rem;
float: right;
}
#login-dialog div.loginerr{
background-color: #326A1E;
font-size: 1rem;
color: #FFF;
padding: .1rem .5rem;
border-radius: 5px;
text-align: center;
display:none;
}
#login-dialog div.loginerr.show{
display:block!important;
}
+12
View File
@@ -19,6 +19,9 @@ canvas.intro3d{
background:transparent;
pointer-events: none;
z-index:99;
}
canvas.intro3d.ready{
animation: logoanim 3.5s ease-out forwards;
}
@@ -32,6 +35,15 @@ canvas.intro3d{
40% { opacity: 0.8; }
100% { opacity: 1; }
}
.progress{
position: absolute;
width: 100px;
height: 2px;
background: white;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#startbtn{
cursor: default;