Requires membership to download. Join here 

Build it manually from scratch:

1. images:

2. Code:

				
					<style>

@import url('https://fonts.googleapis.com/css2?family=Anton&display=swap');

/* One hero: Elementor controls the background, foreground, headings and button. */
.bkn-slide {
  position: relative !important;
  min-height: 100vh;
  overflow: hidden !important;
  isolation: isolate;
  background-blend-mode: normal;
  --violet: var(--chapter-accent, #eb9af4)
}

.bkn-slide>.e-con-inner {
  position: static !important
}

.bkn-slide::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(3, 7, 22, .7), rgba(4, 7, 23, .49) 30%, transparent 68%)
}



/* Elementor foreground Image widget: just select the image and add world-foreground. */
.bkn-slide .world-foreground {
  position: absolute !important;
  inset: 0 !important;
  z-index: 2 !important;
  width: 100% !important;
  height: 100% !important;
  pointer-events: none
}

.bkn-slide .world-foreground>.elementor-widget-container {
  height: 100%
}

.bkn-slide .world-foreground img {
  width: 100% !important;
  height: 100% !important;
  max-width: none !important;
  object-fit: cover;
  display: block;
  }

.bkn-slide .world-copy {
  position: relative !important;
  z-index: 4 !important
}

.bkn-slide .world-title .elementor-heading-title {
  font-family: Anton, Impact, sans-serif !important;
  font-weight: 400 !important;
  line-height: .98 !important;
  letter-spacing: -.035em !important;
  background: linear-gradient(#fff, #d2cad8) !important;
  background-clip: text !important;
  -webkit-background-clip: text !important;
  color: transparent !important;
  -webkit-text-fill-color: transparent !important;
  filter: drop-shadow(0 12px 35px rgba(0, 0, 0, .38))
}

.bkn-slide .portal-heading .elementor-heading-title {
  font-family: Anton, Impact, sans-serif !important;
  font-weight: 400 !important;
  line-height: .98 !important;
  letter-spacing: -.035em !important;
  background: linear-gradient(180deg,
      rgba(245, 243, 248, .96) 0%,
      rgba(190, 186, 196, .88) 48%,
      rgba(67, 64, 76, .82) 100%) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  -webkit-text-fill-color: transparent !important;
  color: transparent !important;
}

.bkn-slide .world-copy .elementor-divider-separator {
  border-color: var(--violet) !important
}

.bkn-slide .world-copy .elementor-divider__text {
  letter-spacing: .24em !important
}

.bkn-slide .world-copy .elementor-button {
  background: rgba(5, 12, 30, .52) !important;
  color: white !important;
  border: 1px solid rgba(222, 167, 247, .7);
  border-radius: 100px !important;
  letter-spacing: .19em !important;
  backdrop-filter: blur(15px)
}

.bkn-slide .world-copy .elementor-button::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(90deg, #836391, #eeafff, #a5e7e8, #836391);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude
}

.bkn-slide .world-overlay {
  position: absolute !important;
  inset: 0 !important;
  z-index: 3 !important;
  pointer-events: none
}

.bkn-slide .world-overlay__eyebrow {
  position: absolute;
  left: 12%;
  top: 19%;
  display: flex !important;
  align-items: center;
  gap: 12px;
  width: max-content;
  max-width: 75%;
  padding: 11px 17px;
  border: 1px solid rgba(220, 190, 245, .4);
  border-radius: 100px;
  background: rgba(4, 11, 30, .5);
  color: #f0e9ff;
  font: 600 10px/1.4 Montserrat, Arial, sans-serif;
  letter-spacing: .21em;
  backdrop-filter: blur(12px)
}

.bkn-slide .world-overlay__eyebrow .elementor-heading-title,
.bkn-slide .world-overlay__counter .elementor-heading-title,
.bkn-slide .world-overlay__hint .elementor-heading-title {
  font: inherit !important;
  letter-spacing: inherit !important;
  color: inherit !important;
  line-height: inherit !important
}

.bkn-slide .world-overlay__eyebrow::before {
  content: "";
  display: block;
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--violet);
  box-shadow: 0 0 12px var(--violet)
}

.bkn-slide .world-overlay__counter {
  position: absolute;
  right: 6%;
  bottom: 13%;
  width: max-content;
  color: #e9f5ff;
  font: 500 27px/1.2 Montserrat, Arial, sans-serif;
  letter-spacing: .13em;
  text-shadow: 0 3px 16px #000
}

.bkn-slide .world-overlay__counter span {
  font-size: 11px;
  opacity: .65
}

.bkn-slide .world-overlay__hint {
  position: absolute;
  right: 6%;
  bottom: 20%;
  width: max-content;
  color: #e9f5ff;
  font: 500 11px/1.3 Montserrat, Arial, sans-serif;
  letter-spacing: .21em;
  text-shadow: 0 3px 16px #000
}

.bkn-slide .planet-code {
  position: absolute !important;
  width: 0 !important;
  height: 0 !important;
  overflow: hidden !important;
  z-index: -1 !important
}

@media(max-width:767px) {
  .bkn-slide::before {
    background: linear-gradient(0deg, rgba(3, 7, 22, .72), rgba(3, 7, 22, .1) 75%)
  }

  .bkn-slide .world-foreground img {
    object-position: 76% center
  }

  .bkn-slide .world-overlay__eyebrow {
    left: 6%;
    top: 16%;
    font-size: 8px
  }

  .bkn-slide .world-overlay__counter {
    bottom: 9%;
    font-size: 17px
  }

  .bkn-slide .world-overlay__hint {
    bottom: 14%;
    font-size: 9px
  }

  .bkn-slide .world-title .elementor-heading-title {
    font-size: clamp(62px, 13.4vw, 110px) !important
  }
}

/* Section-local 3D canvas and its Elementor foreground image. */
.bkn-slide>.planet3-layer{position:absolute!important;inset:0!important;z-index:0!important;pointer-events:none;overflow:hidden}
.planet3-layer>canvas{position:absolute;inset:0;display:block;width:100%!important;height:100%!important;max-width:none!important;opacity:0;transition:opacity .4s;outline:none!important}
.planet3-layer.is-ready>canvas{opacity:1}
.bkn-slide-3 .world-foreground img{filter:grayscale(.75) sepia(.68) hue-rotate(250deg) saturate(1.55) brightness(.92)}
.bkn-slide-2 .world-foreground img{filter:sepia(.15) hue-rotate(165deg) saturate(.75)}
.bkn-slide-4 .world-foreground img{filter:grayscale(.88) sepia(.85) hue-rotate(225deg) saturate(1.75) brightness(.82) contrast(1.1)}
.bkn-slide-1 .world-foreground{display:none!important}
.bkn-slide .world-scene-code{position:absolute!important;width:0!important;height:0!important;overflow:hidden!important;pointer-events:none!important}
@media(prefers-reduced-motion:reduce){.planet3-layer>canvas{transition:none}}

</style>
<script>


(function () {
  'use strict';

  const SETTINGS = {
    // Coordinates refer to the supplied 1672 x 941 image, not the viewport.
    width:1672, height:941, centerX:1135, centerY:464, radius:385,
    tilt:0.20, startRotation:-0.75, pitch:-0.65,
    autoSpeed:0.025,       // Radians per second; one turn takes about 4 minutes.
    dragSpeed:0.006,
    maxPixelRatio:2
  };
  // Change only this URL to replace the rotating image (a 2:1 texture).
  const PLANET_TEXTURE_URL = "https://royalblue-jellyfish-354834.hostingersite.com/wp-content/uploads/2026/09/The-Observer-iris-texture-v2.png";
  const hero = document.currentScript?.closest('.bkn-slide-2') || document.querySelector('.bkn-slide-2');
  if (!hero || hero.dataset.observerLoaded) return;
  hero.dataset.observerLoaded = '1';
  const urls = {
    texture: PLANET_TEXTURE_URL,
    original: hero.querySelector('.world-foreground img')?.currentSrc || hero.querySelector('.world-foreground img')?.src || '',
    kind: 'earth',
    eyeTexture: false
  };
  async function mount() {
    const state = { dead:false, dispose:() => {} };
    const earth = urls.kind === "earth" && !urls.eyeTexture;
    const mars = urls.kind === "mars";
    const violet = urls.kind === "violet";
    const eye = urls.eyeTexture;
    hero.classList.toggle("is-eye-texture", eye);
    // Only the original red source image needs the pink color treatment.
    const tintOriginalRed = violet && /(?:^|\/)world-violet-red-texture\.webp(?:[?#].*)?$/i.test(urls.texture);
    if (!urls.texture) return;
    const layer = document.createElement('div');
    layer.className = 'planet3-layer';
    hero.prepend(layer);
    const cleanups = [];
    const resources = [];
    let renderer, frame = 0;
    state.dispose = () => {
      state.dead = true;
      cancelAnimationFrame(frame);
      cleanups.forEach(fn => fn());
      resources.forEach(item => item.dispose());
      if (renderer) renderer.dispose();
      layer.remove(); hero.style.cursor = '';
      delete hero.dataset.observerLoaded;
    };
    function on(node, name, fn, options) {
      node.addEventListener(name, fn, options);
      cleanups.push(() => node.removeEventListener(name, fn, options));
    }
    try {
      const THREE = await import('https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js');
      if (state.dead) return;
      renderer = new THREE.WebGLRenderer({ alpha:true, antialias:true, powerPreference:'low-power' });
      renderer.setPixelRatio(Math.min(devicePixelRatio || 1, SETTINGS.maxPixelRatio));
      renderer.outputColorSpace = THREE.SRGBColorSpace;
      renderer.setClearColor(0x000000, 0);
      const canvas = renderer.domElement;
      canvas.tabIndex = 0;
      canvas.setAttribute('role', 'group');
      canvas.setAttribute('aria-label', 'Rotating planet. Drag with mouse, use left and right arrow keys, or press Space to pause.');
      layer.appendChild(canvas);

      const loader = new THREE.TextureLoader();
      loader.setCrossOrigin('anonymous');
      const load = async (url, color) => {
        const tex = await loader.loadAsync(url);
        if (state.dead) { tex.dispose(); throw new Error('Scene removed'); }
        tex.colorSpace = color ? THREE.SRGBColorSpace : THREE.NoColorSpace;
        tex.anisotropy = Math.min(8, renderer.capabilities.getMaxAnisotropy());
        resources.push(tex); return tex;
      };
      const loadImage=url=>new Promise((resolve,reject)=>{
        const image=new Image(); image.crossOrigin='anonymous';
        image.onload=()=>resolve(image);image.onerror=reject;image.src=url;
      });
      const [surface, originalImage] = await Promise.all([
        load(urls.texture,true),urls.original ? loadImage(urls.original) : null
      ]);
      if (state.dead) return;
      surface.wrapS = THREE.RepeatWrapping;
      const scene = new THREE.Scene();
      const camera = new THREE.OrthographicCamera(-836, 836, 470.5, -470.5, 1, 4000);
      camera.position.z = 2000;
      function addMesh(geometry, material, z) {
        resources.push(geometry, material);
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.z = z; scene.add(mesh); return mesh;
      }
      const planetMaterial = new THREE.ShaderMaterial({
        uniforms:{ violetScene:{value:tintOriginalRed ? 1 : 0}, eyeScene:{value:eye ? 1 : 0}, surface:{ value:surface }, rimBoost:{value:eye ? .20 : (violet ? 0.8 : 2.0)}, rimColor:{value:new THREE.Vector3(...(eye ? [.8,.24,.87] : (earth ? [.18,.5,1.] : (violet ? [1.,.20,.75] : [1.,.36,.26]))))} },
        vertexShader:`varying vec2 vUv; varying vec3 vNormal;
          void main(){ vUv=uv; vNormal=normalize(normalMatrix*normal);
          gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
        fragmentShader:`uniform sampler2D surface; uniform float violetScene; uniform float eyeScene; uniform float rimBoost; uniform vec3 rimColor; varying vec2 vUv; varying vec3 vNormal;
          void main(){
            vec3 n=normalize(vNormal);
            vec3 c=texture2D(surface,vUv).rgb;
            float seam=0.5*(1.0-smoothstep(0.0,0.018,min(vUv.x,1.0-vUv.x)));
            c=mix(c,texture2D(surface,vec2(1.0-vUv.x,vUv.y)).rgb,seam);
            float energy=max(c.r,max(c.g,c.b));
            float neutral=min(c.r,min(c.g,c.b));
            float bands=smoothstep(.04,.60,energy);
            vec3 amethyst=mix(vec3(.025,.002,.060),vec3(.72,.055,.55),bands);
            amethyst+=vec3(.35,.10,.24)*neutral;
            amethyst*=.60+energy*.80;
            c=mix(c,amethyst,violetScene);
            float light=max(0.0,dot(n,normalize(vec3(0.75,0.75,0.65))));
            float violetLight=max(0.0,dot(n,normalize(vec3(.90,.40,.28))));
            c*=mix(mix(.55+.8*light,.24+1.28*violetLight,violetScene),.83+.43*light,eyeScene);
            c+=violetScene*vec3(.58,.055,.30)*pow(violetLight,5.0)*(.20+energy*.70);
            float rim=pow(1.0-max(n.z,0.0),5.0);
            float rimLight=0.12+0.88*smoothstep(-0.55,0.8,dot(n,normalize(vec3(0.8,0.7,0.0))));
            c+=rimColor*rim*rimLight*0.5;
            c+=rimColor*rimBoost*pow(1.0-max(n.z,0.0),14.0)*rimLight;
            gl_FragColor=vec4(c,1.0);
            #include <colorspace_fragment>
          }`
      });
      const planet = addMesh(new THREE.SphereGeometry(SETTINGS.radius, 128, 80), planetMaterial, 0);
      planet.position.x = SETTINGS.centerX - SETTINGS.width/2;
      planet.position.y = SETTINGS.height/2 - SETTINGS.centerY;
      // A tilted parent preserves the rotation axis while the surface turns.
      const axis = new THREE.Group();
      axis.position.copy(planet.position); axis.rotation.z = SETTINGS.tilt;
      planet.position.set(0,0,0); scene.remove(planet); axis.add(planet); scene.add(axis);
      planet.rotation.y = eye ? 0 : SETTINGS.startRotation;
      planet.rotation.x = eye ? 0 : (earth ? -0.15 : SETTINGS.pitch);
      // The separate atmosphere shell gives Earth its blue edge. On Violet it caused a second visible ring.
      if(earth){
        const geometry=new THREE.SphereGeometry(SETTINGS.radius*1.018,128,80);
        const material=new THREE.ShaderMaterial({transparent:true,depthWrite:false,
          uniforms:{innerColor:{value:new THREE.Vector3(...(violet ? [.60,.035,.48] : [.035,.28,1.0]))},outerColor:{value:new THREE.Vector3(...(violet ? [1.0,.36,.82] : [.25,.8,1.0]))},opacity:{value:violet ? .72 : .85}},
          vertexShader:`varying vec3 vN; void main(){vN=normalize(normalMatrix*normal);
            gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`,
          fragmentShader:`uniform vec3 innerColor;uniform vec3 outerColor;uniform float opacity;varying vec3 vN; void main(){vec3 n=normalize(vN);
            float edge=pow(1.0-max(n.z,0.0),4.5);
            float lit=.28+.72*max(0.0,dot(n,normalize(vec3(.55,.85,.3))));
            gl_FragColor=vec4(mix(innerColor,outerColor,edge),edge*lit*opacity);
            #include <colorspace_fragment>
          }`});
        resources.push(geometry,material);axis.add(new THREE.Mesh(geometry,material));
      }

      if (!eye) {
      const haloSize=violet ? 2.8 : 2.24;
      const halo=addMesh(new THREE.PlaneGeometry(SETTINGS.radius*haloSize,SETTINGS.radius*haloSize),
        new THREE.ShaderMaterial({
          transparent:true, depthWrite:false,
          uniforms:{haloColor:{value:new THREE.Vector3(...(earth ? [.07,.48,1.0] : (violet ? [.85,.09,.65] : [.8,.12,.035])))},falloff:{value:earth ? 22.0 : (violet ? 9.0 : 45.0)},strength:{value:earth ? .95 : (violet ? .38 : (mars ? .25 : .45))},haloSize:{value:haloSize},fadeStart:{value:violet ? 1.12 : 1.075},fadeEnd:{value:violet ? 1.38 : 1.12}},
          vertexShader:`varying vec2 vUv; void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`,
          fragmentShader:`uniform vec3 haloColor; uniform float falloff; uniform float strength; uniform float haloSize; uniform float fadeStart; uniform float fadeEnd; varying vec2 vUv; void main(){
            vec2 p=(vUv-.5)*haloSize; float r=length(p);
            float a=exp(-max(r-1.0,0.0)*falloff)*strength;
            a*=1.0-smoothstep(fadeStart,fadeEnd,r);
            a*=0.25+0.75*smoothstep(-0.6,1.0,dot(normalize(p),normalize(vec2(0.8,0.7))));
            gl_FragColor=vec4(haloColor,a);
            #include <colorspace_fragment>
          }`
        }),-400);
      halo.position.x=axis.position.x; halo.position.y=axis.position.y;
      }

      // Sample the mask so hidden parts of the sphere cannot capture the mouse.
      const maskCanvas = document.createElement('canvas');
      let maskPixels = null;
      if (originalImage) {
        maskCanvas.width = originalImage.width; maskCanvas.height = originalImage.height;
        const maskContext = maskCanvas.getContext('2d', { willReadFrequently:true });
        maskContext.drawImage(originalImage,0,0);
        maskPixels = maskContext.getImageData(0,0,maskCanvas.width,maskCanvas.height).data;
      }
      let viewWidth = SETTINGS.width, viewHeight = SETTINGS.height;
      let visible = false, paused = matchMedia('(prefers-reduced-motion: reduce)').matches;
      let drag = null, speed = 0, lastTime = 0;
      const reduce = matchMedia('(prefers-reduced-motion: reduce)');
      const raycaster = new THREE.Raycaster();
      const pointer = new THREE.Vector2();

      function render() {
        renderer.render(scene,camera);
        layer.dataset.rotation = planet.rotation.y.toFixed(5);
      }
      function stop() { cancelAnimationFrame(frame); frame=0; lastTime=0; }
      function start() {
        if (!frame && visible && !document.hidden && !state.dead) frame=requestAnimationFrame(tick);
      }
      function tick(now) {
        frame=0;
        if (state.dead || !hero.isConnected) { state.dispose(); return; }
        const dt=lastTime ? Math.min((now-lastTime)/1000,0.05) : 0;
        lastTime=now;
        if (!drag) {
          const target=paused ? 0 : SETTINGS.autoSpeed;
          speed+=(target-speed)*(1-Math.exp(-3.0*dt));
          planet.rotation.y+=speed*dt;
        }
        render();
        if (drag || !paused || Math.abs(speed)>0.0001) start();
        else lastTime=0;
      }
      function resize() {
        const box=hero.getBoundingClientRect();
        if (!box.width || !box.height) return;
        const scale=Math.max(box.width/SETTINGS.width,box.height/SETTINGS.height);
        viewWidth=box.width/scale; viewHeight=box.height/scale;
        camera.position.x=(SETTINGS.width-viewWidth)*(box.width<768 ? .26 : 0);
        camera.left=-viewWidth/2; camera.right=viewWidth/2;
        camera.top=viewHeight/2; camera.bottom=-viewHeight/2;
        camera.updateProjectionMatrix();
        renderer.setSize(box.width,box.height,false);
        render();
      }
      function hit(event) {
        if (event.target.closest('a,button,input,textarea,select,[contenteditable="true"]')) return false;
        const b=hero.getBoundingClientRect();
        const nx=(event.clientX-b.left)/b.width, ny=(event.clientY-b.top)/b.height;
        const sx=(nx-.5)*viewWidth+SETTINGS.width/2+camera.position.x;
        const sy=(ny-.5)*viewHeight+SETTINGS.height/2;
        const mx=Math.max(0,Math.min(maskCanvas.width-1,Math.floor(sx/SETTINGS.width*maskCanvas.width)));
        const my=Math.max(0,Math.min(maskCanvas.height-1,Math.floor(sy/SETTINGS.height*maskCanvas.height)));
        if (maskPixels && maskPixels[(my*maskCanvas.width+mx)*4+3]>90) return false;
        pointer.set(nx*2-1,1-ny*2);
        scene.updateMatrixWorld(true); camera.updateMatrixWorld(true);
        raycaster.setFromCamera(pointer,camera);
        return raycaster.intersectObject(planet).length>0;
      }
      function release(event) {
        if (!drag || (event && event.pointerId !== undefined && event.pointerId!==drag.id)) return;
        const id=drag.id;
        if (performance.now()-drag.time>100 || paused) speed=0;
        drag=null;
        if (hero.hasPointerCapture(id)) hero.releasePointerCapture(id);
        hero.style.cursor='';
        layer.dataset.dragging='false'; start();
      }
      on(hero,'pointerdown',event => {
        // Touch remains available to the existing vertical slide navigation.
        if (event.pointerType==='touch' || event.button!==0 || !hit(event)) return;
        event.preventDefault();
        canvas.focus({ preventScroll:true });
        drag={ id:event.pointerId, x:event.clientX, y:event.clientY, time:performance.now() };
        speed=0; hero.setPointerCapture(event.pointerId);
        hero.style.cursor='grabbing'; layer.dataset.dragging='true'; start();
      });
      on(hero,'pointermove',event => {
        if (event.pointerType==='touch') return;
        if (drag && drag.id===event.pointerId) {
          const now=performance.now();
          const dx=event.clientX-drag.x, dy=event.clientY-drag.y;
          const scale=hero.clientWidth/viewWidth;
          const angle=dx/scale*SETTINGS.dragSpeed;
          planet.rotation.y+=angle;
          planet.rotation.x=THREE.MathUtils.clamp(planet.rotation.x+dy/scale*SETTINGS.dragSpeed*.45,(eye ? 0 : (earth ? -.15 : SETTINGS.pitch))-.30,(eye ? 0 : (earth ? -.15 : SETTINGS.pitch))+(eye ? .72 : .25));
          speed=THREE.MathUtils.clamp(angle/Math.max((now-drag.time)/1000,.008),-1.6,1.6);
          drag.x=event.clientX; drag.y=event.clientY; drag.time=now;
          render();
        } else hero.style.cursor=hit(event) ? 'grab' : '';
      },{ passive:true });
      on(hero,'pointerup',release); on(hero,'pointercancel',release); on(hero,'lostpointercapture',release);
      on(hero,'pointerleave',() => { if (!drag) hero.style.cursor=''; });
      on(window,'blur',() => release());
      function syncPause() {
        layer.dataset.paused=String(paused);
      }
      function togglePause() { paused=!paused; speed=0; syncPause(); render(); start(); }
      on(canvas,'keydown',event => {
        if (event.key==='ArrowLeft' || event.key==='ArrowRight') {
          event.preventDefault(); event.stopPropagation();
          planet.rotation.y+=event.key==='ArrowLeft' ? -.08 : .08; render();
        } else if (event.key===' ') { event.preventDefault(); event.stopPropagation(); togglePause(); }
      });
      on(reduce,'change',() => { paused=reduce.matches; speed=0; syncPause(); render(); start(); });
      on(document,'visibilitychange',() => { if(document.hidden){release();stop();}else start(); });
      on(canvas,'webglcontextlost',event => { event.preventDefault(); stop(); layer.classList.remove('is-ready'); });
      on(canvas,'webglcontextrestored',() => { layer.classList.add('is-ready'); resize(); start(); });
      const ro=new ResizeObserver(resize); ro.observe(hero); cleanups.push(() => ro.disconnect());
      const io=new IntersectionObserver(entries => {
        visible=entries[0].isIntersecting;
        if (visible) { render(); start(); } else { release(); stop(); }
      },{ threshold:0 });
      io.observe(hero); cleanups.push(() => io.disconnect());
      resize(); syncPause();
      layer.classList.add('is-ready');
    } catch(error) {
      if (state.dead) return;
      console.warn('Planet: Fallback shown. Check image URLs, CORS and Three.js access.',error);
      stopFailure();
    }
    function stopFailure() {
      cancelAnimationFrame(frame); layer.classList.remove('is-ready');
      cleanups.forEach(fn => fn()); resources.forEach(item => item.dispose());
      if(renderer) renderer.dispose();
    }
  }

  mount();
})();
</script>
				
			

3. CSS classes and ID:

bkn-slide bkn-slide-2

world-foreground

 

Requires membership to download. Join here