Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">

<div id="container" role="img" aria-label="Ghost flying around a tree">
</div>

<div id="loader" >
  <div id="loader-content" class="border-gradient border-gradient-purple">
    <p>Loading 3D scene... ✨</p>
  </div>
</div>

<button id="btn-freeze">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99.799 42.739"><defs><filter height="1.248" y="-.124" width="1.092" x="-.046" id="a" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="1.807"/></filter></defs><g 
id="freeze-text-blur" transform="translate(-1.18 -1.623)" filter="url(#a)" fill="none" stroke="#b0b0b0"><path d="M25.636 10.552c-1.843 7.337-4.62 16.543-8.126 22.679M8.816 19.624c9.974-.372 30.667-3.216 33.64-14.363M15.053 24.726c5.363 1.364 13.934-2.697 16.442-4.535M31.022 27.845c1.609-1.694 3.847-2.611 5.576-4.158 6.37-5.7 1.953-7.319-1.418-1.701-3.897 6.496 7.813-.468 9.45-2.268M44.63 19.718c-4.05.891-13.429 16.197-2.74 9.072"/><path d="M42.078 26.805c2.818-.666 4.975-1.844 7.37-3.213 10.356-5.917 6.352-12.377-3.118-.756-8.242 10.116 2.227 8.924 8.316 2.835"/><path d="M52.19 25.766c2.445-1.012 4.868-2.107 7.18-3.402 9.618-5.386 3.297-8.388-2.55.189-5.712 8.377 3.361 8.05 8.598 3.685"/><path d="M64.284 23.498c1.292-.693 2.596-1.363 3.875-2.079 1.033-.578 1.86-1.403 2.645-2.268 4.807-5.287-4.933-.681-2.551 1.701 2.696 2.696 11.116-6.284 3.969 1.512-.322.35-5.82 5.654-6.142 5.008-.408-.816 3.932-2.844 4.724-2.646.867.217-.713 2.941 1.04 2.552 1.237-.275 2.219-1.27 3.118-2.08"/><path d="M73.828 23.498c3.162-.749 5.092-1.59 7.654-3.213 9.24-5.851-4.396-5.169-5.102 3.307-.416 4.986 10.12 2.11 12-.189M20.061 11.97c-5.567-2.034-10.471.077-14.552 4.157-2.638 2.639-2.336 5.286 1.7 5.67 1.577.15 6.195-1.06 4.726-.472-4.195 1.677-.003 1.448 2.173.756"/><path d="M79.309 28.128c2.9.636 5.15.384 7.843-.472 5.975-1.902 9.454-9.107 5.575-6.52-3.217 2.144 3.86 1.413 5.197.661M18.41 26.488c-3.197 8.188-9.445 15.54-11.16 5.856"/><g><path d="M95.263 5.625c-2.024 3.49-7.167 6.601-13.293 5.438M90.832 4.215c-.295 3.694-2.873 8.353-5.304 10.54M82.172 6.43c3.077 3.028 9.31 5.886 12.554 5.707"/></g><g><path d="M28.935 28.72c-1.436 3.613-5.137 8.71-8.66 10.472M22.557 29.793c2.541 3.398 4.076 6.935 2.685 9.4M19.267 35.164c5.351.644 10.22-1.22 13.293-4.565"/></g></g><g id="freeze-text-top" fill="none" stroke="#b0b0b0"><g stroke-width=".5"><path d="M24.457 8.93c-1.843 7.336-4.62 16.543-8.126 22.678M7.637 18c9.974-.371 30.667-3.215 33.64-14.362M13.874 23.104c5.363 1.363 13.934-2.697 16.442-4.536M29.843 26.222c1.608-1.694 3.847-2.611 5.575-4.158 6.37-5.7 1.954-7.319-1.417-1.7-3.898 6.495 7.813-.468 9.45-2.269M43.45 18.095c-4.049.892-13.428 16.197-2.74 9.072"/><path d="M40.899 25.182c2.818-.665 4.975-1.843 7.37-3.212 10.356-5.918 6.352-12.378-3.118-.756-8.242 10.115 2.227 8.923 8.316 2.834"/><path d="M51.01 24.143c2.446-1.012 4.87-2.107 7.181-3.402 9.618-5.385 3.297-8.388-2.55.19-5.713 8.376 3.361 8.048 8.598 3.684"/><path d="M63.105 21.875c1.292-.693 2.596-1.363 3.874-2.079 1.033-.578 1.86-1.403 2.646-2.268 4.807-5.287-4.933-.681-2.551 1.701 2.696 2.696 11.115-6.284 3.969 1.512-.322.351-5.82 5.655-6.142 5.008-.409-.816 3.932-2.843 4.724-2.645.867.216-.713 2.94 1.04 2.55 1.236-.274 2.218-1.268 3.118-2.078"/><path  d="M72.649 21.875c3.162-.749 5.092-1.59 7.654-3.213 9.24-5.851-4.396-5.168-5.103 3.308-.415 4.985 10.12 2.11 12.001-.19M18.882 10.347c-5.568-2.034-10.472.077-14.552 4.158-2.638 2.638-2.336 5.285 1.7 5.67 1.576.15 6.195-1.061 4.725-.473-4.194 1.677-.002 1.448 2.174.756"/><path d="M78.13 26.505c2.9.636 5.15.384 7.843-.472 5.975-1.901 9.454-9.106 5.575-6.52-3.217 2.144 3.86 1.414 5.197.661"/></g><path d="M17.23 24.865c-3.196 8.189-9.444 15.54-11.159 5.857" stroke-width=".5"/><g stroke-width=".3" stroke="#cb02ea"><path d="M94.084 4.002c-2.024 3.49-7.167 6.601-13.293 5.438M89.653 2.593c-.295 3.693-2.873 8.352-5.304 10.54M80.993 4.808c3.077 3.028 9.31 5.885 12.554 5.706"/></g><g stroke-width=".3" stroke="#cb02ea"><path d="M27.755 27.096c-1.435 3.614-5.136 8.712-8.66 10.473M21.378 28.17c2.541 3.399 4.076 6.935 2.685 9.4M18.088 33.541c5.351.644 10.22-1.219 13.293-4.565"/></g></g></svg>
</button>

<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.158.0/build/three.module.js",
      "three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/"
    }
  }
</script>

<!-- Matcap VS -->
<script type="x-shader/x-vertex" id="matcap-vs">

  varying vec2 vN;

	void main() {

		vec3 e = normalize( vec3( modelViewMatrix * vec4( position, 1.0 ) ) );
		vec3 n = normalize( normalMatrix * normal );

		vec3 r = reflect( e, n );
		float m = 2. * sqrt( pow( r.x, 2. ) + pow( r.y, 2. ) + pow( r.z + 1., 2. ) );
		vN = r.xy / m + .5;

    gl_Position = projectionMatrix * modelViewMatrix * vec4( vec3(position), 1. );

	}

</script>

<!-- Matcap FS -->
<script type="x-shader/x-fragment" id="matcap-fs">

  uniform sampler2D tMatCap;
	varying vec2 vN;

	void main() {
		
		vec3 base = texture2D( tMatCap, vN ).rgb;
		gl_FragColor = vec4( base, 1.0 );

	}
	
</script>

<!-- Flaky VS  -->
<script type="x-shader/x-vertex" id="flaky-vs">
//	Simplex 4D Noise 
//	by Ian McEwan, Ashima Arts
//
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
float permute(float x){return floor(mod(((x*34.0)+1.0)*x, 289.0));}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}

vec4 grad4(float j, vec4 ip){
  const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
  vec4 p,s;

  p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
  p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
  s = vec4(lessThan(p, vec4(0.0)));
  p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www; 

  return p;
}

float snoise(vec4 v){
  const vec2  C = vec2( 0.138196601125010504,  // (5 - sqrt(5))/20  G4
                        0.309016994374947451); // (sqrt(5) - 1)/4   F4
// First corner
  vec4 i  = floor(v + dot(v, C.yyyy) );
  vec4 x0 = v -   i + dot(i, C.xxxx);

// Other corners

// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
  vec4 i0;

  vec3 isX = step( x0.yzw, x0.xxx );
  vec3 isYZ = step( x0.zww, x0.yyz );
//  i0.x = dot( isX, vec3( 1.0 ) );
  i0.x = isX.x + isX.y + isX.z;
  i0.yzw = 1.0 - isX;

//  i0.y += dot( isYZ.xy, vec2( 1.0 ) );
  i0.y += isYZ.x + isYZ.y;
  i0.zw += 1.0 - isYZ.xy;

  i0.z += isYZ.z;
  i0.w += 1.0 - isYZ.z;

  // i0 now contains the unique values 0,1,2,3 in each channel
  vec4 i3 = clamp( i0, 0.0, 1.0 );
  vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
  vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );

  //  x0 = x0 - 0.0 + 0.0 * C 
  vec4 x1 = x0 - i1 + 1.0 * C.xxxx;
  vec4 x2 = x0 - i2 + 2.0 * C.xxxx;
  vec4 x3 = x0 - i3 + 3.0 * C.xxxx;
  vec4 x4 = x0 - 1.0 + 4.0 * C.xxxx;

// Permutations
  i = mod(i, 289.0); 
  float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
  vec4 j1 = permute( permute( permute( permute (
             i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
           + i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
           + i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
           + i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
// Gradients
// ( 7*7*6 points uniformly over a cube, mapped onto a 4-octahedron.)
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.

  vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;

  vec4 p0 = grad4(j0,   ip);
  vec4 p1 = grad4(j1.x, ip);
  vec4 p2 = grad4(j1.y, ip);
  vec4 p3 = grad4(j1.z, ip);
  vec4 p4 = grad4(j1.w, ip);

// Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;
  p4 *= taylorInvSqrt(dot(p4,p4));

// Mix contributions from the five corners
  vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
  vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4)            ), 0.0);
  m0 = m0 * m0;
  m1 = m1 * m1;
  return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
               + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;

}
	
#define NUM_OCTAVES 3
	
float fbm(vec4 x) {
	float v = 0.0;
	float a = 0.5;
	vec4 shift = vec4(100.);
	for (int i = 0; i < NUM_OCTAVES; ++i) {
		v += a * snoise(x);
		x = x * 2.0 + shift;
		a *= 0.5;
	}
	return v;
}

float map(float value, float min1, float max1, float min2, float max2) {
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}

uniform float uTime;
uniform float uEdgeWidth;
uniform float uFrequency;
varying float vNoise;
varying float vEdge;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec2 vUv;
	
void main() {
  
	vUv = uv;
	vNoise = fbm(vec4(position*uFrequency, 0.));
	vNoise = map(vNoise,-0.6,0.6,0.,1. - uEdgeWidth);
	vec4 modelViewPosition = modelViewMatrix * vec4( position, 1.0 );
	vViewPosition = -modelViewPosition.xyz;
	gl_Position = projectionMatrix * modelViewPosition;
	vNormal = normalMatrix * normal;
  
}
</script>

<!-- Flaky FS before main -->
<script type="x-shader/x-fragment" id="flaky-fs-beforeMain">

  uniform float uThreshold;
  uniform float uEdgeWidth;
  uniform vec3 uEdgeColor;
  uniform vec3 uColor;
  varying float vNoise;

</script>

<!-- Flaky FS main -->
<script type="x-shader/x-fragment" id="flaky-fs">
	
  float alpha = 1.0;
	
  if( vNoise > uThreshold) {
		alpha = 0.0;
	}
	
	vec3 materialColor = uColor / 255.0;
	vec3 edgeColor = uEdgeColor / 255.0;
	
	vec3 color = materialColor;
	
	if( vNoise + uEdgeWidth > uThreshold ) {
		color = edgeColor;
	}
	
  vec4 diffuseColor = vec4( color, alpha );

</script>

<!-- Glow VS  -->
<script type="x-shader/x-vertex" id="glow-vs">
  
varying vec3 vVertexWorldPosition;
varying vec3 vVertexNormal;

varying vec4 vFragColor;

void main() {

  vVertexNormal	= normalize(normalMatrix * normal);
  vVertexWorldPosition	= (modelMatrix * vec4(position, 1.0)).xyz;

  gl_Position	= projectionMatrix * modelViewMatrix * vec4(position, 1.0);

}
  
</script>

<!-- Glow FS  -->
<script type="x-shader/x-fragment" id="glow-fs">
  
uniform vec3	glowColor;
uniform float	coeficient;
uniform float	power;

varying vec3	vVertexNormal;
varying vec3	vVertexWorldPosition;

varying vec4	vFragColor;

void main() {

  vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;
  vec3 viewCameraToVertex	= (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;
  viewCameraToVertex = normalize(viewCameraToVertex);
  float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);
  gl_FragColor = vec4(glowColor, intensity);

}
</script>

<!-- Radial gradient VS  -->
<script type="x-shader/x-vertex" id="radialGradient-vs">
  
varying vec2 vUv;

 void main() {
 
  vUv = uv;
  gl_Position = vec4(position, 1.0);

 }
  
</script>

<!-- Radial gradient FS  -->
<script type="x-shader/x-fragment" id="radialGradient-fs">
  
varying vec2 vUv;
uniform vec3 color1;
uniform vec3 color2;
uniform float ratio;
  
void main() {
  
  vec2 uv = (vUv - 0.5) * vec2(ratio, 1.0);
  gl_FragColor = vec4(mix(color1, color2, length(uv)), 1.0);

}
  
</script>
              
            
!

CSS

              
                body {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: rgb(82,81,79);
  background: radial-gradient(circle, rgba(82,81,79,1) 0%, rgba(36,35,33,1) 32%, rgba(0,0,0,1) 100%);
  color: #adadad;
  font-family: Arial;
  font-size: 16px;
  -ms-touch-action: none;
  touch-action: none;
}

#container {
  position: absolute;
  opacity: 0.25;
}

#loader {
  width: inherit;
  height: inherit;
  margin: 0 auto;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
}

#loader-content {
  background: none;
}

button {
  position: absolute;
  background: none;
  border: none;
  outline: none;
  transition: transform 0.2s ease-in-out;
  opacity: 1.0;
}

#btn-freeze {
  position: absolute;
  width: 145px;
  height: auto;
  cursor: pointer;
  top: 0;
  right: 0;
  transform: rotate(3deg);
  padding: 1.25em 1.0em;
  opacity: 0.85;
}

#btn-freeze:hover {
  animation: rotateMe2 0.35s ease-in-out forwards;
}

#btn-credits {
  position: absolute;
  width: 100px;
  height: auto;
  background: none;
  bottom: 0;
  right: 0;
  opacity: 0.75;
  padding: 0.5em 0.25em;
  transform: rotate(-20deg);
  display: none;
  opacity: 0.7;
  text-decoration: none;
}

a:active, a:focus {
  text-decoration: none;
  outline: 0;
  border: none;
  -moz-outline-style: none;
}

#btn-credits:hover {
  animation: rotateMe 0.25s ease-in-out forwards;
}

@keyframes fadeOut {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

@-moz-keyframes fadeOut {
  0%   { opacity: 1; }
  100%  { opacity: 0; }
}

@keyframes fadeIn {
  0% { opacity: 0.25; }
  100% { opacity: 1; }
}

@-moz-keyframes fadeIn {
  0%   { opacity: 0.25; }
  100%  { opacity: 1; }
}

@keyframes rotateMe {
  0% { transform: rotate(-20deg);
  opacity: 0.7;}
  100% { transform: rotate(0deg);
  opacity: 1;}
}

@-moz-keyframes rotateMe {
  0% { transform: rotate(-20deg);
  opacity: 0.7;}
  100% { transform: rotate(0deg); opacity: 1.0; }
}

@keyframes rotateMe2 {
  0% { 
    transform: rotate(3deg) scale(1.0);
    opacity: 0.7;
  }
  100% { 
    transform: rotate(15deg) scale(1.05);
    opacity: 1.0;
  }
}

@-moz-keyframes rotateMe2 {
  0% { 
    transform: rotate(3deg);
    transform: scale(1.0);
    opacity: 0.7;
  }
  100% { 
    transform: rotate(15deg);
    transform: scale(1.1);
    opacity: 1.0;
  }
}

@media ( max-width: 800px ) {
  
  #btn-freeze {
    width: 120px;
    position: fixed;
    top: 0;
    left: inherit;
    right: 0;
    padding: 1em;
    transform: rotate(15deg);
  }
  
  #btn-freeze:hover {
    animation: none;
  }
  
  #btn-credits {
    display: none;
  }
  
}
              
            
!

JS

              
                /*
  UPDATED VERSION: NOVEMBER 2023, threejs v0.158.0
  
  It's disco time!  
  And this ghost wants to party all night long 👻
  But you can freeze him anytime you wish to!
  
  ★ Scene designed & coded by Anna Scavenger in May 2020 ★
  https://twitter.com/ouchpixels
  
  License: You can remix, adapt, and build upon my code non-commercially.
  
  Assets credits, special thanks to:
  Cubemap by Emil Persson aka Humus (@_Humus_)
  Simplex noise by Stefan Gustavson (@stegu)
  Tree gtlf model - by Helindu, modified by me in Blender:
  https://sketchfab.com/3d-models/tree-without-leaves-1-336d3bc197ce4618ab325e7a6dfa0e7a
  
*/

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { Flow } from 'three/addons/modifiers/CurveModifier.js';
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';

// ASSETS

let texChromeURL = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_chromeEye_128.jpg";
let texSteelURL = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_steel_256.jpg";
let ghostURL = "https://assets.codepen.io/911157/ghost1.glb";
let treeURL = "https://assets.codepen.io/911157/tree_ghost_opt.glb";

let scene, camera, renderer;

// Through these points flies the ghost
// Also points for placing the tori

let controlPoints = [

  new THREE.Vector3(420, 120, 0),	// letter D
  new THREE.Vector3(-70, 200, 750), // additional point
  new THREE.Vector3(-440, 100, 150), // letter G	
  new THREE.Vector3(-250, 440, -395),	// letter O
  new THREE.Vector3(-20, 600, -590), // additional point
  new THREE.Vector3(200, 500, -380) // letter O2

];

let materials;
let geometries;

// FLOW + GLOW

let ghost;
let flow;

let glowMeshes = [];
let currentGlowMesh;

// GLOBAL MESHES

let discoBall, discoBallN, ballCover, ballCover2, discoBallG, discoBallT;
let letterHLeft, letterHRight, cylinderT;
let lettersNightGroup;
let hookG, hookD, starD, starG;

let pointLight, pointLight2;

// 'FREEZING' THE SCENE EFFECT

let quaternion = new THREE.Quaternion();
let isSceneFrozen = false;
let rotSpeed = 0.25;

const btnFreeze = document.querySelector('#btn-freeze');
const freezeTextBlur = document.querySelector('#freeze-text-blur');
const freezeTextTop = document.querySelector('#freeze-text-top');

// LANDSCAPE / PORTRAIT

let isMobile = /(Android|iPhone|iOS|iPod|iPad)/i.test(navigator.userAgent);
let windowRatio = window.innerWidth / window.innerHeight;
let isLandscape = (windowRatio > 1) ? true : false;

let mouseX = 0;
let mouseY = 0;

let params = {
  
  terrainWidth: 2000,
  terrainLength: isLandscape ? 3000 : (2500 / windowRatio),
  terrainPosY: -250,
  treeMainPos: new THREE.Vector3(0, -250, 0),
  treesBackgroundPos: new THREE.Vector3(0, -250, 0),
  numTrees: 7,
  terrainLettersPosZ: isLandscape ? 850 : 950,
  ghostColor: new THREE.Color(0xffffff),
  glowOffColor: new THREE.Color(0x8D8D8D),
  glowOnColor: new THREE.Color(0xca00e5),
  pointLightColor: new THREE.Color(0x0fc4b8), // dark green
  play: false,
  
}

init();

function init() {
  
  geometries = initGeometries();
  materials = initMaterials();
  
  // CAMERA, LIGHTS, RENDERER
  
  initBasicSetup();
  
  // MESHES
  
  initTerrain();
  initTerrainLetters();
  initAirLetters();
  loadTree();
  loadGhost();
  
  render();
  
  window.addEventListener("resize", onWindowResize, false);
  document.addEventListener("mousemove", onMouseMove, false);
  btnFreeze.addEventListener("click", freezeScene, false);
  
}

function initBasicSetup() {
  
  initScene();
  initCamera();
  initLights();
  initRenderer();

}

function initScene() {
  
  scene = new THREE.Scene();
  const fogNear = isLandscape ? 1200 : 2200;
  const fogFar = isLandscape ? 4000 : 5500;
  scene.fog = new THREE.Fog(0x998a7d, fogNear, fogFar);
  
}

function initCamera() {
  
  camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);
  const cameraY = isLandscape ? 170 : 400;
  const cameraZ = isLandscape ? 1850 : (1800 / windowRatio);
  camera.position.set(0, cameraY, cameraZ);
  camera.lookAt(0, 0, 0);
  scene.add(camera);
  
}

function initLights() {
  
  const backLight = new THREE.DirectionalLight(0xff00ff, 1.75 * Math.PI);
  backLight.position.set(200, 0, -55);
  backLight.target.position.set(300, 50, -100);
  
  const fillLight = new THREE.DirectionalLight(0xffffff, 0.65 * Math.PI);
  fillLight.position.set(80, 10, 200);
  fillLight.target.position.set(50, 50, -50);
  
  let posZ = isLandscape ? 750 : 850;
  
  pointLight = new THREE.PointLight(params.pointLightColor, 8.5 * Math.PI, 200, 2);
  pointLight.position.set(-300, 0, posZ);
  pointLight2 = new THREE.PointLight(params.pointLightColor, 8.5 * Math.PI, 200, 2);
  pointLight2.position.set(300, 0, posZ);

  scene.add( fillLight, backLight, pointLight, pointLight2 );
    
}

function initRenderer() {
  
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio > 1.5 ? Math.min(window.devicePixelRatio, 1.4) : Math.min(window.devicePixelRatio, 1.25));
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  renderer.gammaFactor = 2.2;
  container.appendChild(renderer.domElement);
  
}

function initGeometries() {
  
  let torus = new THREE.TorusGeometry(140, 40, 16, 46);
  let halfTorus = new THREE.TorusGeometry(70, 25, 10, 20, Math.PI);
  
  let cylinderThin = new THREE.CylinderGeometry(10, 10, 190, 8);
  let cylinderThick = new THREE.CylinderGeometry(30, 30, 150, 22, 1);
  let cylinderThick2 = new THREE.CylinderGeometry(30, 30, 150, 22, 22);
  
  let sphere = new THREE.SphereGeometry(50, 24, 12);
  let halfSphere = new THREE.SphereGeometry(50, 28, 8, 0, Math.PI);
  let halfSphere2 = new THREE.SphereGeometry(55, 28, 8, 0, Math.PI);
  let cone = new THREE.ConeGeometry(40, 110, 20); 
  
  // HOOK 1 - MERGED GEOMS
  
  let halfTorusThin = new THREE.TorusGeometry( 85, 10, 12, 50, Math.PI);
  halfTorusThin.rotateX(Math.PI);
  let radius = halfTorusThin.parameters.radius;
  let cylinderHeight = cylinderThin.parameters.height;
  halfTorusThin.translate(- radius, - cylinderHeight / 2, 0);
  let hook = mergeGeometries([halfTorusThin, cylinderThin], true);
  
  // HOOK 2 - MERGED GEOMS
  
  let cylinderD = new THREE.CylinderGeometry(10, 10, 350, 10);
  let halfTorusD = new THREE.TorusGeometry(70, 10, 12, 50, Math.PI / 2);
  halfTorusD.rotateX(Math.PI);
  halfTorusD.rotateZ( - Math.PI / 2);
  let radiusD = halfTorusD.parameters.radius;
  let heightCylinderD = cylinderD.parameters.height;
  halfTorusD.translate( radiusD, - heightCylinderD / 2, 0 );
  let hook2 = mergeGeometries([cylinderD, halfTorusD], true);
  
  return {
    
    torus,
    halfTorus,
    cylinderThin,
    cylinderThick,
    cylinderThick2,
    sphere,
    halfSphere,
    halfSphere2,
    cone,
    hook,
    hook2
    
  }
  
}

function initMaterials() {
  
  const grey = new THREE.MeshBasicMaterial({color: 0x222222});
  const blackBasic = new THREE.MeshBasicMaterial({color: 0x000000});
  const brownSimple = isMobile ? (new THREE.MeshBasicMaterial({color: 0x594a3e})) : (new THREE.MeshLambertMaterial({color: 0x5e4732}));
  const brown = new THREE.MeshLambertMaterial({color: 0x5e4732});
  const purpleFlat = new THREE.MeshLambertMaterial({color: 0xff00ff});

  const purple = new THREE.MeshStandardMaterial({
    
    color: 0xff00ff,
    roughness: 0.4,
    metalness: 0.9
    
  });
  purple.color.convertSRGBToLinear();
  
  const greyFlat = new THREE.MeshPhongMaterial({
    
    color: 0x200066,
    emissive: 0x54944,
    specular: 0x1d683f,
    shininess: 80,
    flatShading: true
    
  });
  greyFlat.color.convertSRGBToLinear();
  
  // MATCAPS

  const textureLoader = new THREE.TextureLoader();
  textureLoader.setCrossOrigin('');
  const textureChrome = textureLoader.load(texChromeURL);
  const textureSteel = textureLoader.load(texSteelURL);
  
  const matcapChrome = new THREE.ShaderMaterial({
    
    uniforms: {
      tMatCap: { type: "t", value: textureChrome }
    },
    vertexShader: document.querySelector("#matcap-vs").textContent,
    fragmentShader: document.querySelector("#matcap-fs").textContent,
    side: THREE.DoubleSide
    
  });
  
  const matcapSteel = new THREE.ShaderMaterial({
    
    uniforms: {
      tMatCap: { type: "t", value: textureSteel }
    },
    vertexShader: document.querySelector("#matcap-vs").textContent,
    fragmentShader: document.querySelector("#matcap-fs").textContent,
    side: THREE.DoubleSide
    
  });
  
  // DISCO REFLECTIONS
  
  const loader = new THREE.CubeTextureLoader();
  loader.setCrossOrigin('');
  loader.setPath('https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/');
 
  const textureCube = loader.load([
    
    'pisa1_px.jpg', 'pisa1_nx.jpg',
	  'pisa1_py.jpg', 'pisa1_ny.jpg',
	  'pisa1_pz.jpg', 'pisa1_nz.jpg'
    
  ]);
  
  const disco = new THREE.MeshPhongMaterial({
    
    envMap: textureCube,
    color: 0xff00ff,
    emissive: 0xaa00aa,
    specular: 0x1d683f,
    flatShading: true
    
  });
  
  // FLAKY MATERIAL
  
  let phongShader = THREE.ShaderLib.standard;
  let fragmentShader = phongShader.fragmentShader;
  fragmentShader = document.querySelector("#flaky-fs-beforeMain").textContent +
  fragmentShader.replace(
    "vec4 diffuseColor = vec4( diffuse, opacity );",
    document.querySelector("#flaky-fs").textContent
  );

  const flaky = new THREE.ShaderMaterial({
    
    uniforms: THREE.UniformsUtils.merge([phongShader.uniforms, {
      
      uThreshold: { value: 0.5 },
      uEdgeWidth: { value: 0.0 },
      uEdgeColor: { value: [255, 109, 203] },
      uColor: { value: [255, 0, 255] },
      uFrequency: { value: 0.95 }
    
    }]),
    vertexShader: document.querySelector("#flaky-vs").textContent,
    fragmentShader: fragmentShader,
    side: THREE.DoubleSide,
    lights: true,
    transparent: true

  });
  
  // GLOW MATERIAL
  
  let glowOffColor = params.glowOffColor;
  
  const glow	= new THREE.ShaderMaterial({
		
    uniforms: {
   
			coeficient: { type: "f", value: 1.1 },
			power: { type: "f", value: 1.4 },
			glowColor: { type: "c", value: glowOffColor },
		
    },
		vertexShader: document.querySelector("#glow-vs").textContent,
		fragmentShader: document.querySelector("#glow-fs").textContent,
		transparent: true,
	
  });
  
  return {
    
    blackBasic,
    grey, greyFlat,
    purple, purpleFlat,
    brown, brownSimple,
    matcapChrome, matcapSteel,
    disco,
    flaky,
    glow
    
  }
  
}

function initTerrain() {
  
  let terrainWidth = params.terrainWidth;
  let terrainLength = params.terrainLength;
  let terrainY = params.terrainPosY;
  
  let terrainGeom = new THREE.PlaneGeometry(terrainWidth, terrainLength, 30, 30);
  terrainGeom.rotateX(-0.5 * Math.PI);
  let positionAttribute = terrainGeom.attributes.position;
    
  for (let i = 0; i < positionAttribute.count; i++) {
    
    let x = positionAttribute.getX( i );
	  let y = positionAttribute.getY( i );
	  let z = positionAttribute.getZ( i );
    
    y += Math.random() * 40;
    
    positionAttribute.setXYZ(i, x, y, z);
  
  }
    
  const terrain = new THREE.Mesh(terrainGeom, materials.brown);
  terrain.position.y = terrainY;
  scene.add(terrain);
  
  // SPRINKLE TERRAIN WITH POINTS
  
  const positionArray = terrainGeom.attributes.position.array;
  const positionAttributeCount = terrainGeom.attributes.position.count;

  const pointsGeom = new THREE.BufferGeometry();
  pointsGeom.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
  const pointsMat = new THREE.PointsMaterial({ color: 0xff00ff, size: 6 });
  const terrainPoints = new THREE.Points(pointsGeom, pointsMat);
  terrainPoints.position.y = terrainY;
  scene.add(terrainPoints);
  
  // SPRINKLE TERRAIN WITH LITTLE BOXES
  
  const boxBufferGeom = new THREE.BoxGeometry(2.85, 2.85, 2.85);
  const instancedBoxGeom = new THREE.InstancedBufferGeometry().copy(boxBufferGeom);
  let numInstances = positionAttributeCount;
  const terrainBoxes = new THREE.InstancedMesh(instancedBoxGeom, materials.purpleFlat, numInstances);
  
  const dummy = new THREE.Object3D();

  for (let i = 0; i < numInstances; i++) {

    dummy.position.set(

      noise.simplex3(i * 0.2, i * 0.5, 0.5) * 900,
      Math.random() * (-220 - (-240)) + (-240),
      Math.random() * (1300 - (-850)) + (-850),
      
    );

    dummy.rotation.set(

      Math.random() * (Math.PI * 1.95 - (- Math.PI * 1.95)) + (-Math.PI * 1.95),
      Math.random() * (Math.PI * 1.95 - (- Math.PI * 1.95)) + (-Math.PI * 1.95),
      0.0

    );

    dummy.updateMatrix();
    terrainBoxes.setMatrixAt(i, dummy.matrix);

  }

  scene.add(terrainBoxes);
  
}

function initTerrainLetters() {
  
  let posZ = params.terrainLettersPosZ;

  // letter N
  
  const cylinderN = new THREE.Mesh(geometries.cylinderThick, materials.purple);
  cylinderN.scale.y = 0.85;
  cylinderN.position.set(-360, -150, posZ);
  
  const cylinderN2 = new THREE.Mesh(geometries.cylinderThick, materials.purple);
  cylinderN2.position.set(-230, -170, posZ);
  
  const halfSphereN = new THREE.Mesh(geometries.halfSphere, materials.matcapSteel);
  halfSphereN.position.set(-200.6, -97, posZ);
  halfSphereN.scale.set(0.593, 0.593, 0.593);
  halfSphereN.rotation.x =  - Math.PI / 2;
  scene.add(halfSphereN);
  
  discoBallN = new THREE.Mesh(geometries.sphere, materials.disco);
  discoBallN.position.set(-360, -50, posZ);
  discoBallN.scale.set(0.5, 0.5, 0.5);
  scene.add(discoBallN);
  
  const cylinderThinnerN = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
  cylinderThinnerN.position.set(-360, -150, posZ);
  cylinderThinnerN.scale.set(0.4, 0.9, 0.4);
  scene.add(cylinderThinnerN);
  
  const cylinderThinN = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
  cylinderThinN.position.set(-290, -170, posZ);
  cylinderThinN.scale.set(1.2, 0.8, 1.2);
  cylinderThinN.rotation.z = Math.PI / 4.5;
  
  const discN = new THREE.Mesh( geometries.cylinderThick, materials.matcapSteel );
  discN.position.set(-360, -223, posZ);
  discN.scale.set(1.28, 0.125, 1.28);
  
  const discN2 = discN.clone();
  discN.position.set(-230, -240, posZ);
  
  const letterN = new THREE.Group();
  letterN.add(cylinderN, cylinderN2, cylinderThinN, discN, discN2);
  
  scene.add(letterN);
  
  // letter I
  
  let letterIPosX = -110;
  
  discoBall = new THREE.Mesh(geometries.sphere, materials.disco);
  discoBall.position.set(letterIPosX, -70, posZ);
  
  ballCover = new THREE.Mesh(geometries.halfSphere, materials.purple);
  ballCover.material.side = THREE.DoubleSide;
  
  let halfRadius = ballCover.geometry.parameters.radius;
  ballCover.scale.set(1.15, 1.15, 1.15);
  ballCover.position.set(letterIPosX, - 74 - halfRadius, posZ);
  ballCover.rotation.set(Math.PI / 2, - Math.PI / 2, 0);
  ballCover.geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(- halfRadius, 0, 0));
  ballCover.updateMatrix();
  ballCover2 = ballCover.clone();
  ballCover2.position.copy(ballCover.position);
  ballCover2.rotation.z = Math.PI;
  ballCover2.rotation.y = Math.PI / 2;
  
  const cone = new THREE.Mesh(geometries.cone, materials.matcapSteel);
  cone.position.set(letterIPosX, -180, posZ);
  
  const letterI = new THREE.Group();
  letterI.add(cone, discoBall, ballCover, ballCover2);
  scene.add(letterI);
  
  // letter G
  
  const torusG = new THREE.Mesh(geometries.halfTorus, materials.purple);
  torusG.position.set(50, -160, posZ);
  torusG.rotation.z = Math.PI / 2;
  
  const hookG = new THREE.Mesh(geometries.hook, materials.matcapSteel);
  hookG.scale.set(0.3, 0.25, 0.8);
  hookG.position.set(70, -185, posZ);
  
  discoBallG = discoBallN.clone();
  discoBallG.position.set(50, -160, posZ);
  
  const letterG = new THREE.Group();
  letterG.add(torusG, hookG, discoBallG);
  scene.add(letterG);
  
  // letter H
  
  letterHLeft = new THREE.Group();
  letterHRight = new THREE.Group();
  
  const cylinderH = new THREE.Mesh(geometries.cylinderThick2, materials.flaky);
  // cylinderH.position.set(140, -145, 850);
  cylinderH.position.set(140, -160, posZ);
  cylinderH.scale.set(1.0, 0.9, 1.0);
  
  const cylinderH2 = new THREE.Mesh(geometries.cylinderThick, materials.matcapSteel);
  cylinderH2.scale.set(0.98, 0.88, 0.98);
  cylinderH2.position.copy(cylinderH.position);
 
  const discH = new THREE.Mesh(geometries.cylinderThick, materials.matcapSteel);
  discH.position.set(140, -87, posZ);
  discH.scale.set(1.275, 0.08, 1.275);
  const discH2 = discN.clone();
  discH2.position.set(140, -225, posZ);
  
  letterHLeft.add(cylinderH, cylinderH2, discH, discH2);
  
  letterHRight = letterHLeft.clone();
  letterHRight.position.set(120, 0, 0);
  
  const horizontalH = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
  horizontalH.scale.set(1.2, 0.5, 1.2);
  horizontalH.rotation.z = Math.PI / 2;
  horizontalH.position.set(200, -155, posZ);
  
  const letterH = new THREE.Group();
  letterH.add(letterHLeft, letterHRight, horizontalH);
  
  scene.add(letterH);

  // letter T 
  
  cylinderT = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
  cylinderT.scale.set(0.55, 0.725, 0.55);
  cylinderT.position.set(370, -40, posZ);
  cylinderT.rotation.z = Math.PI / 2;
  
  const cylinderT2 = cylinderN.clone();
  cylinderT2.position.set(370, -170, posZ);
  cylinderT2.scale.set(0.9, 0.75, 0.9);
  
  discoBallT = discoBallN.clone();
  discoBallT.position.set(370, -70, posZ);  
  
  const letterT = new THREE.Group();
  letterT.add(cylinderT, cylinderT2, discoBallT);
  scene.add(letterT);
  
  lettersNightGroup = new THREE.Group();
  
}

function loadTree() {
  
  const loader = new GLTFLoader();

  loader.load(treeURL,
    
    function (gltf) {
      
      let tree = gltf.scene.children[0].children[0].children[0].children[0].children[0];
      tree.geometry.computeVertexNormals();
      let treeGeom = tree.geometry;
      treeGeom.scale(55, 55, 55);
      treeGeom.rotateX(-0.5 * Math.PI);
      tree.position.set(50, -150, -1050);
      tree.material = new THREE.MeshBasicMaterial({color: 0x000000, fog: false});
    
      initTrees(treeGeom);
      scene.add(tree);
      
    },
    
    function (xhr) { console.log((xhr.loaded / xhr.total * 100 ) + '% loaded'); },
    
    function (error) { console.log( 'An error happened' ); }
    
  );
  
}

function initTrees(tGeom) {
  
  let treeMesh = new THREE.Mesh(tGeom, materials.matcapSteel); 
      
  let treesCoords = [
    
    -350, -200, -1400,
    -200, -270, -1300,
    250, -250, -1300
    
  ];
  
  for (let i = 0; i < (treesCoords.length / 3); i++) {
    
    let t = treeMesh.clone();
    let x = treesCoords[3 * i];
    let y = treesCoords[3 * i + 1];
    let z = treesCoords[3 * i + 2];
    t.position.set(x, y, z);
    t.rotation.set(0, i * 0.2 * Math.PI, 0);
    scene.add(t);
    
  }
  
}

function loadGhost() {
    
  const loader = new GLTFLoader();

  loader.load(ghostURL,
    
    function (gltf) {
      
      ghost = gltf.scene.children[2].parent.children[2];
      ghost.geometry.computeVertexNormals();
      initFlow();
      removeLoadingScreen();
      
    },
    
    function (xhr) { console.log((xhr.loaded / xhr.total * 100 ) + '% loaded'); },
    
    function (error) { console.log( 'An error happened' ); }
    
  );
  
}

function initFlow() {
  
  let ghostColor = params.ghostColor || 0xff3da4;
  
  let purple = new THREE.MeshStandardMaterial({

    color: ghostColor,
    roughness: 0.95,
    metalness: 0.05,
    side: THREE.DoubleSide
  
  });
  purple.color.convertSRGBToLinear();
  
  let curvePoints = controlPoints;
  
  let curve = new THREE.CatmullRomCurve3(curvePoints);
  curve.curveType = "centripetal";
  curve.closed = true;
  
  let ghostScale = 0.375;
  const mesh = ghost;
  mesh.geometry.scale(ghostScale, ghostScale, ghostScale);
  mesh.geometry.rotateY(-Math.PI / 2);
  mesh.geometry.rotateX(Math.PI / 2);

  let objectToCurve = new THREE.Mesh(mesh.geometry, purple);
  flow = new Flow(objectToCurve);
  flow.updateCurve(0, curve);
  scene.add(flow.object3D);
  
}

// "GOOD" LETTERS IN THE AIR

function initAirLetters() {
  
  // 4 TORI - AIR LETTERS
  
  let letterPositions = [controlPoints[0], controlPoints[2], controlPoints[3], controlPoints[5]];
    
  let torus = new THREE.Mesh(geometries.torus, materials.glow);
    
  for (let i = 0; i < letterPositions.length; i++) {
    
    let t = torus.clone();
    // IMPORTANT! When cloning a ShaderMaterial, the attributes and vertex/fragment programs are copied by reference. 
    // The uniforms are copied by value, which is what we need. / turning on and off glow for each mesh /
    t.material = torus.material.clone();
    t.material.isGlowing = false;
    t.position.copy(letterPositions[i]);
    glowMeshes.push(t);
    scene.add(t);
  
  }
    
  // letter G
    
  hookG = new THREE.Mesh(geometries.hook, materials.matcapChrome);
  hookG.position.set(-310, -40, 150);
  hookG.matrixAutoUpdate = false;
  hookG.updateMatrix();
  scene.add( hookG );
  
  let starGeom = generateStarGeometry();
  starG = new THREE.Mesh(starGeom, materials.matcapSteel);
  starG.position.set(-480, -124, 150);
  starG.rotation.z = Math.PI / 5;
  starG.scale.set(2.4, 2.4, 2.4);
  scene.add(starG);
  
  // letter D
  
  hookD = new THREE.Mesh(geometries.hook2, materials.matcapChrome);
  hookD.position.set(550, 210, 0);
  hookD.matrixAutoUpdate = false;
  hookD.updateMatrix();
  scene.add(hookD);
  
  starD = starG.clone();
  starD.position.set(550, 398, 0);
  starD.rotation.z = Math.PI / 5;
  scene.add(starD);
  
}

function generateStarGeometry() {
  
  let numPoints = 7;
  let minLength = 4;
  let maxLength = 15;
  
  let extrudeSettings = {
    
    steps: 2,
    depth: 2.5,
    bevelEnabled: true,
    bevelThickness: 0,
    bevelSize: 2,
    bevelOffset: 0,
    bevelSegments: 1
    
  };
  
  let points = []; 
  
	for (let i = 0; i < numPoints * 2; i++) {

		let l = i % 2 == 1 ? minLength : maxLength;
		let step = i / numPoints * Math.PI;
		points.push(new THREE.Vector2(Math.cos(step) * l, Math.sin(step) * l));

	}

	let starShape = new THREE.Shape(points);
	let starGeom = new THREE.ExtrudeGeometry(starShape, extrudeSettings);
  
  return starGeom;
  
}

function render() {
  
  renderer.render(scene, camera);
  requestAnimationFrame(render);
  
  if (flow && isSceneFrozen) {
    
    // STOP THE FLYING GHOST AND ROTATE THE SCENE
    flow.moveAlongCurve(0.000);
    rotateScene();
    
  } else if (flow && !isSceneFrozen ) {

    animateTerrainLetters();
    updatePointLights();
    
    flow.moveAlongCurve(0.005);
    
    let offset = flow.uniforms.pathOffset.value.toFixed(3);
    let progress = (offset - Math.floor(offset)).toFixed(3);
        
    if (progress == 0.25) {
      
      currentGlowMesh = glowMeshes[1];
      updateMaterial(currentGlowMesh);

    } else if (progress == 0.55) {
      
      currentGlowMesh = glowMeshes[2];
      updateMaterial(currentGlowMesh);
      
    } else if (progress == 0.7) {
      
      currentGlowMesh = glowMeshes[3];
      updateMaterial(currentGlowMesh);

    } else if (progress == 0.92) {
      
      currentGlowMesh = glowMeshes[0];
      updateMaterial(currentGlowMesh);

    } else {
      
      return;
    
    }

  }
  
}

function rotateScene() {
  
  camera.lookAt(scene.position);
  let angle = 0.001 * mouseX;
  camera.position.applyQuaternion(quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle));

}

function updateMaterial(mesh) {
  
  let glowColor = mesh.material.uniforms.glowColor.value;
  let coeff = mesh.material.uniforms.coeficient;
  
  mesh.material.isGlowing = !mesh.material.isGlowing;
    
  mesh.material.isGlowing ? glowColor.set(params.glowOnColor) : glowColor.set(params.glowOffColor);
  mesh.material.isGlowing ? coeff.value = 1.4 : coeff.value = 1.0;
  
  if (currentGlowMesh == glowMeshes[1]) {
    
    mesh.material.isGlowing ? (hookG.material = materials.purple) : (hookG.material = materials.matcapChrome);
    
  }  else if (currentGlowMesh == glowMeshes[0]) {
    
    mesh.material.isGlowing ? (hookD.material = materials.purple) : (hookD.material = materials.matcapChrome);
    
  }
  
}

function updatePointLights() {
  
  pointLight.intensity = 3.25 + Math.abs(mouseX) * 4.75;
  pointLight2.intensity = 3.25 + Math.abs(mouseX) * 4.75;

}

function animateTerrainLetters() {
  
  starG.rotation.y = mouseX * Math.PI;
  starD.rotation.y = - mouseX * Math.PI;
  
  // letter N
  discoBallN.rotation.y =  - (mouseX * mouseX) * Math.PI * 0.5;
  
  // letter I
  discoBall.rotation.y = mouseX * Math.PI * 0.25;
  discoBall.material.emissive.r = 0.8 - Math.abs(mouseX * 0.78);
  ballCover.rotation.y = - Math.PI / 2 + Math.PI / 1.85 * Math.abs(mouseX * mouseX);
  ballCover.scale.set(1.15 - 0.6 * Math.abs(mouseX*mouseX), 1.15 - 0.85 * Math.abs(mouseX * mouseX), 1.15 - 0.75 * Math.abs(mouseX * mouseX));
  ballCover2.rotation.y = Math.PI / 2 - Math.PI / 1.85 * Math.abs(mouseX * mouseX);
  ballCover2.scale.set(1.15 - 0.6 * Math.abs(mouseX*mouseX), 1.15 - 0.85 * Math.abs(mouseX*mouseX), 1.15 - 0.75 * Math.abs(mouseX*mouseX));
  
  // letter G
  discoBallG.rotation.y = -mouseX * Math.PI;
  discoBallG.scale.set(0.5, 0.2 + 0.3 * Math.abs(mouseX), 0.5);
  
  // letter H
  letterHLeft.children[0].rotation.y = (mouseX) * Math.PI;
  letterHRight.children[0].rotation.y = - (mouseX) * Math.PI;

  // letter T
  discoBallT.rotation.y = -mouseX * Math.PI * 0.5;
  cylinderT.rotation.y = -mouseX * Math.PI * 0.25;
  
}

// *** EVENTS ***

function onWindowResize() {
  
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  
}

function onMouseMove(e) {
  
  e.preventDefault();
  mouseX = (event.clientX / window.innerWidth) * 2 - 1;
  mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
  
}

function freezeScene() {
  
  params.play = false;
    
  if (!isSceneFrozen) {
    
    params.play = false;
    isSceneFrozen = !isSceneFrozen;
    
    freezeTextTop.style.stroke = "#63639f";
    freezeTextBlur.style.stroke = "#5959ff";
    
  } else {
    
    params.play = true;
    isSceneFrozen = !isSceneFrozen;
    
    freezeTextBlur.style.stroke = "#b0b0b0";
    freezeTextTop.style.stroke = "#b0b0b0";

  }
  
}

function removeLoadingScreen() {
  
  const loaderDiv = document.querySelector("#loader");
  const sceneDiv = document.querySelector("#container");
  loaderDiv.style.animation = "fadeOut 0.375s ease-in-out forwards";
  sceneDiv.style.animation = "fadeIn 0.75s ease-in-out forwards";
  loaderDiv.style.display = "none";

}
              
            
!
999px

Console