← Back to library

Fresnel distorted sphere

Vertex-displaced icosphere with a glowing fresnel rim.

A five-times-subdivided icosphere whose every vertex is pushed along its own normal by three summed sines at different frequencies, so the surface swells and folds without ever repeating. The fragment stage lights it by viewing angle alone: facing surfaces stay near-black while grazing ones flare amber and then lime, which is the fresnel falloff real glass and rimlit metal exhibit. No scene lights at all — the glow is pure geometry-versus-camera. This is the signature premium AI-product hero object.

hero-fresnel-sphere · intensity awwwards

Base-catalogue pattern — no generated axes.

Generation prompts are part of a CreativePower subscription, and are never public.

See plans

Stylesheet

css · 3305 chars
.fds-stage {
  position: relative;
  min-height: 100vh;
  overflow: hidden;
  /* stageMode "dark": every Batch 02 effect is additive light, so the stage derives a
     dark ground FROM the spec (on-surface pulled 8% toward surface) rather than taking
     --color-surface directly. Both sides fall back to the same literal, so a token-less
     page renders exactly as before. */
  --fds-ink: color-mix(in oklab, var(--color-on-surface, #0a0a0a) 92%, var(--color-surface, #0a0a0a));
  /* Prefix-free alias, so a builder that doesn't know this stage's prefix (axes#fallbackFor,
     the shared lights() rig) can still read the derived ground. Element-scoped, so two
     Batch 02 stages on one page each see their own. */
  --tok-ink: var(--fds-ink);
  --fds-warm: color-mix(in oklab, var(--color-primary, #c8793a) 80%, var(--color-surface, #c8793a));
  --fds-bright: color-mix(in oklab, var(--color-secondary, var(--color-primary, #c8f564)) 80%, var(--color-surface, #c8f564));
  --fds-core: color-mix(in oklab, var(--color-on-surface, #0d0805) 92%, var(--color-surface, #0d0805));
  /* Also the graceful-degradation backdrop: the canvas paints over this when WebGL
     is available, so a blocked CDN or a failed context degrades to a static field
     rather than a black rectangle. */
  background:
    radial-gradient(42% 42% at 50% 48%, color-mix(in oklab, var(--fds-warm) 34%, transparent), transparent),
    radial-gradient(30% 30% at 50% 48%, color-mix(in oklab, var(--fds-bright) 16%, transparent), transparent),
    var(--fds-ink);
  display: flex;
  align-items: center;
  justify-content: center;
}
@supports (color: oklch(from red min(l, 1 - l) c h)) {
  .fds-stage {
    --fds-ds-ground: oklch(from color-mix(in oklab, var(--color-on-surface) 92%, var(--color-surface)) min(l, 1 - l) c h);
    --fds-ds-ink: oklch(from var(--color-surface) max(l, 1 - l) c h);
    --fds-ink: var(--fds-ds-ground, color-mix(in oklab, var(--color-on-surface, #0a0a0a) 92%, var(--color-surface, #0a0a0a)));
    --fds-warm: color-mix(in oklab, var(--color-primary, #c8793a) 80%, var(--fds-ds-ink, #c8793a));
    --fds-bright: color-mix(in oklab, var(--color-secondary, var(--color-primary, #c8f564)) 80%, var(--fds-ds-ink, #c8f564));
    --fds-core: var(--fds-ds-ground, color-mix(in oklab, var(--color-on-surface, #0d0805) 92%, var(--color-surface, #0d0805)));
  }
}
.fds-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
}
.fds-content {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 0 5%;
  /* stageMode "dark": content sits on the dark stage, so it takes the SURFACE colour. */
  color: var(--color-surface, #f5f5f0);
  pointer-events: none;
}
@supports (color: oklch(from red min(l, 1 - l) c h)) {
  .fds-content {
    --fds-ds-ink: oklch(from var(--color-surface) max(l, 1 - l) c h);
    color: var(--fds-ds-ink, #f5f5f0);
  }
}
.fds-title {
  font-family: 'Geist', sans-serif;
  font-size: clamp(2.5rem, 6vw, 5rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1.05;
  margin: 0 0 1rem;
}
.fds-sub {
  font-family: 'Geist Mono', monospace;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.6;
}
@media (prefers-reduced-motion: reduce) {
  .fds-canvas { display: none; }
}

Script

js · 7449 chars
(function(){
  const stage = document.querySelector('.fds-stage');
  const canvas = document.querySelector('.fds-canvas');
  if (!stage || !canvas) return;
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
  if (typeof THREE === 'undefined') return;

  // ── Token resolution ───────────────────────────────────────────────────
  // Canvas and WebGL take numbers, not var(). Resolve the tokens once, through the
  // browser: a hidden probe forces the cascade to compute the declaration, so
  // color-mix() and any token indirection arrive here already serialized as
  // rgb()/rgba(). With no tokens declared every fallback is the pattern's original
  // literal, so the painted result is unchanged.
  const TOKEN_ROOT = (stage) || document.body;
  function computed(prop, decl, fallback){
    const probe = document.createElement('span');
    probe.style.cssText = 'position:absolute;left:-9999px;width:0;height:0;';
    probe.style.setProperty(prop, decl);
    (TOKEN_ROOT || document.body).appendChild(probe);
    const v = getComputedStyle(probe)[prop];
    probe.remove();
    return v || fallback;
  }
  function rgbOf(decl, fallback){
    const v = computed('color', decl, null);
    if (!v) return fallback;
    // DO NOT parse the numbers out of this string. A color-mix(in oklab, ...) serializes
    // as 'oklab(L a b)', so a naive match() would read lightness as a red channel and
    // every derived colour would collapse to near-black. Paint it and read the real
    // 8-bit sRGB back instead — that works for rgb(), oklab(), lab(), color(), any of it.
    try {
      const c = document.createElement('canvas');
      c.width = 1; c.height = 1;
      const x = c.getContext('2d', { willReadFrequently: true });
      x.fillStyle = '#000';
      x.fillStyle = v;               // an unparseable value leaves fillStyle at #000
      x.fillRect(0, 0, 1, 1);
      const d = x.getImageData(0, 0, 1, 1).data;
      return [ d[0], d[1], d[2] ];
    } catch (e) {}
    // No 2D context (or a hostile embed). Fall back to the legacy rgb() shape, which is
    // still what a plain var() with a hex fallback resolves to.
    const m = String(v).match(/^rgba?\(([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
    return m ? [ +m[1], +m[2], +m[3] ] : fallback;
  }
  function rgb01(decl, fallback){
    const c = rgbOf(decl, null);
    return c ? [ c[0]/255, c[1]/255, c[2]/255 ] : fallback;
  }
  function hexOf(decl, fallback){
    const c = rgbOf(decl, null);
    return c ? ((c[0]|0) << 16) + ((c[1]|0) << 8) + (c[2]|0) : fallback;
  }
  // The renderer's ground and the scene's brand colours come from the stage's own custom
  // properties, so the WebGL field and the CSS degradation backdrop behind it are one
  // palette. tokColor() is what the verbatim factories call in place of a vec3 literal.
  const CLEAR = hexOf('var(--fds-ink, #0a0a0a)', 0x0a0a0a);
  function tokColor(decl, r, g, b){
    const c = rgb01(decl, [r, g, b]);
    return new THREE.Color(c[0], c[1], c[2]);
  }

  // OBJECTS[0] — "Fresnel Distorted Sphere". Scene factory VERBATIM from the
  // source sheet; returns {scene, camera, update(t), resize(w, h)} where t is ms.
  function build(w, h){
   const sc=new THREE.Scene();const cam=new THREE.PerspectiveCamera(45,w/h,0.1,100);cam.position.z=4.2;
   const u={u_time:{value:0},
     CORE:{value:tokColor('var(--fds-core, #0d0805)',0.05,0.03,0.02)},
     WARM:{value:tokColor('var(--fds-warm, #c8793a)',0.784,0.475,0.227)},
     BRIGHT:{value:tokColor('var(--fds-bright, #c8f564)',0.784,0.961,0.392)}};
   const mat=new THREE.ShaderMaterial({uniforms:u,vertexShader:`uniform float u_time;varying vec3 vN;varying vec3 vV;
     float wob(vec3 p){return sin(p.x*2.0+u_time)*0.5+sin(p.y*2.5-u_time*1.1)*0.4+sin(p.z*3.0+u_time*0.7)*0.3;}
     void main(){vN=normalize(normalMatrix*normal);vec3 d=position+normal*wob(position)*0.28;
     vec4 mv=modelViewMatrix*vec4(d,1.0);vV=normalize(-mv.xyz);gl_Position=projectionMatrix*mv;}`,
     fragmentShader:`uniform vec3 CORE;uniform vec3 WARM;uniform vec3 BRIGHT;varying vec3 vN;varying vec3 vV;
     void main(){float f=pow(1.0-max(dot(normalize(vN),normalize(vV)),0.0),2.5);
     vec3 base=mix(CORE,WARM,f);
     base+=BRIGHT*pow(f,2.0)*0.85;gl_FragColor=vec4(base,1.0);}`});
   const m=new THREE.Mesh(new THREE.IcosahedronGeometry(1.4,5),mat);sc.add(m);
   return {scene:sc,camera:cam,update(t){u.u_time.value=t*0.001;m.rotation.y=t*0.0003;m.rotation.x=t*0.0002;},
     resize(w,h){cam.aspect=w/h;cam.updateProjectionMatrix();}};
  }

  // ── Renderer ───────────────────────────────────────────────────────────
  let renderer;
  try {
    renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: false });
  } catch (e) {
    // No WebGL — leave the canvas unpainted so the stage's static gradient shows.
    canvas.style.display = 'none';
    return;
  }
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setClearColor(CLEAR, 1);

  function size(){
    return {
      w: stage.clientWidth || window.innerWidth,
      h: stage.clientHeight || window.innerHeight
    };
  }

  const dim = size();
  renderer.setSize(dim.w, dim.h, false);

  let gl;
  try {
    gl = build(dim.w, dim.h);
  } catch (e) {
    canvas.style.display = 'none';
    return;
  }

  function resize(){
    const d = size();
    renderer.setSize(d.w, d.h, false);
    try { if (gl && gl.resize) gl.resize(d.w, d.h); } catch (e) {}
  }
  window.addEventListener('resize', resize);

  // ── Context loss ───────────────────────────────────────────────────────
  // Browsers cap live WebGL contexts (commonly ~16). A long catalog page can
  // exceed that and this context may be force-lost. preventDefault() is what
  // makes restoration possible at all; hiding the canvas reveals the static
  // gradient painted behind it, so a lost stage degrades to the fallback field
  // instead of a black rectangle.
  let rafId = 0;
  let lost = false;

  canvas.addEventListener('webglcontextlost', function (e) {
    e.preventDefault();
    lost = true;
    if (rafId) { cancelAnimationFrame(rafId); rafId = 0; }
    canvas.style.opacity = '0';
  }, false);

  canvas.addEventListener('webglcontextrestored', function () {
    try {
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.setClearColor(CLEAR, 1);
      const d = size();
      renderer.setSize(d.w, d.h, false);
      // Geometry, lights and instanced buffers do not survive the loss —
      // rebuild the scene rather than re-uploading a stale graph.
      gl = build(d.w, d.h);
      lost = false;
      canvas.style.opacity = '1';
      if (!rafId) rafId = requestAnimationFrame(frame);
    } catch (e) {
      // Rebuild failed — stay on the static gradient.
      canvas.style.opacity = '0';
    }
  }, false);

  // ── Loop ───────────────────────────────────────────────────────────────
  // Honors the showcase pause bridge: when the stage is offscreen the GPU work
  // is skipped. Rotation is driven from elapsed time, so a resume continues from
  // the current angle rather than snapping back.
  const start = performance.now();
  function frame(now){
    rafId = 0;
    if (!lost && window.__cpPaused !== true) {
      const t = now - start;
      try { gl.update(t); renderer.render(gl.scene, gl.camera); } catch (e) {}
    }
    if (!lost) rafId = requestAnimationFrame(frame);
  }
  rafId = requestAnimationFrame(frame);
})();

Markup

html · 224 chars
<section class="fds-stage">
  <canvas class="fds-canvas"></canvas>
  <div class="fds-content">
    <h1 class="fds-title">A surface that breathes</h1>
    <p class="fds-sub">— Fresnel sphere · three.js</p>
  </div>
</section>

Required CDN scripts