diff --git a/app/components/cube-boy-dancefloor/CubeBoy.vue b/app/components/cube-boy-dancefloor/CubeBoy.vue
new file mode 100644
index 00000000..2a2e5b93
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/CubeBoy.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
diff --git a/app/components/cube-boy-dancefloor/DanceFloor.vue b/app/components/cube-boy-dancefloor/DanceFloor.vue
new file mode 100644
index 00000000..3d2a6eef
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/DanceFloor.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/components/cube-boy-dancefloor/Grid.vue b/app/components/cube-boy-dancefloor/Grid.vue
new file mode 100644
index 00000000..33a06bd4
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/Grid.vue
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
diff --git a/app/components/cube-boy-dancefloor/Lighting.vue b/app/components/cube-boy-dancefloor/Lighting.vue
new file mode 100644
index 00000000..0a2edf13
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/Lighting.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/components/cube-boy-dancefloor/MusicPlayer.vue b/app/components/cube-boy-dancefloor/MusicPlayer.vue
new file mode 100644
index 00000000..a66bd809
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/MusicPlayer.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
diff --git a/app/components/cube-boy-dancefloor/index.global.vue b/app/components/cube-boy-dancefloor/index.global.vue
new file mode 100644
index 00000000..afa7c1c7
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/index.global.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/components/cube-boy-dancefloor/shaderMaterial.ts b/app/components/cube-boy-dancefloor/shaderMaterial.ts
new file mode 100644
index 00000000..ec9eff80
--- /dev/null
+++ b/app/components/cube-boy-dancefloor/shaderMaterial.ts
@@ -0,0 +1,83 @@
+/*
+MIT License
+
+Copyright (c) 2020 react-spring
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import type { Color, CubeTexture, Matrix3, Matrix4, Quaternion, Texture, Vector2, Vector3, Vector4 } from 'three'
+import { MathUtils, ShaderMaterial, UniformsUtils } from 'three'
+
+export function shaderMaterial(
+ uniforms: {
+ [name: string]:
+ | CubeTexture
+ | Texture
+ | Int32Array
+ | Float32Array
+ | Matrix4
+ | Matrix3
+ | Quaternion
+ | Vector4
+ | Vector3
+ | Vector2
+ | Color
+ | number
+ | boolean
+ | Array
+ | null
+ },
+ vertexShader: string,
+ fragmentShader: string,
+ onInit?: (material?: ShaderMaterial) => void,
+) {
+ const material = class extends ShaderMaterial {
+ public key: string = ''
+ constructor(parameters = {}) {
+ const entries = Object.entries(uniforms)
+ // Create unforms and shaders
+ super({
+ uniforms: entries.reduce((acc, [name, value]) => {
+ const uniform = UniformsUtils.clone({ [name]: { value } })
+ return {
+ ...acc,
+ ...uniform,
+ }
+ }, {}),
+ vertexShader,
+ fragmentShader,
+ })
+ // Create getter/setters
+ entries.forEach(([name]) =>
+ Object.defineProperty(this, name, {
+ get: () => this.uniforms[name].value,
+ set: v => (this.uniforms[name].value = v),
+ }),
+ )
+
+ // Assign parameters, this might include uniforms
+ Object.assign(this, parameters)
+ // Call onInit
+ if (onInit) { onInit(this) }
+ }
+ } as unknown as typeof ShaderMaterial & { key: string }
+ material.key = MathUtils.generateUUID()
+ return material
+}
diff --git a/content/experiments/cube-boy-dancefloor.md b/content/experiments/cube-boy-dancefloor.md
new file mode 100644
index 00000000..e48c7701
--- /dev/null
+++ b/content/experiments/cube-boy-dancefloor.md
@@ -0,0 +1,16 @@
+---
+title: Cube Boy Dancefloor
+description: A dancing cube character on an animated dancefloor
+author: alvarosabu
+thumbnail: /experiments/cube-boy-dancefloor.png
+date: 2025-09-11
+tags:
+ - animation
+ - character
+ - dancefloor
+ - cube
+---
+
+# Cube Boy Dancefloor
+
+A dancing cube character on an animated dancefloor experiment showcasing character animation and dynamic environments in TresJS.
\ No newline at end of file
diff --git a/public/experiments/cube-boy-dancefloor.png b/public/experiments/cube-boy-dancefloor.png
new file mode 100644
index 00000000..31af47e4
Binary files /dev/null and b/public/experiments/cube-boy-dancefloor.png differ
diff --git a/public/models/cube-boy/cube-boy-dance.glb b/public/models/cube-boy/cube-boy-dance.glb
new file mode 100644
index 00000000..70a27085
Binary files /dev/null and b/public/models/cube-boy/cube-boy-dance.glb differ
diff --git a/public/music/yarin-primak-just-enough.mp3 b/public/music/yarin-primak-just-enough.mp3
new file mode 100644
index 00000000..eb7e5d0e
Binary files /dev/null and b/public/music/yarin-primak-just-enough.mp3 differ