Skip to content

Commit 3f039d3

Browse files
Add interactive 3D robot head with mouse tracking and idle transitions
- Created Logo3D.vue component with Three.js GLB model support - Implemented mouse-following spotlight with 100x brightness intensity - Added idle timer system that transitions between 2D/3D logos after 2s inactivity - 3D model rotates and is illuminated based on mouse position - Smooth fade transitions between static 2D logo and interactive 3D mesh - Optimized lighting with dynamic spotlight and subtle front directional light 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5f4b2e0 commit 3f039d3

File tree

6 files changed

+217
-6
lines changed

6 files changed

+217
-6
lines changed

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
"permissions": {
33
"allow": [
44
"Bash(bun install:*)",
5-
"Bash(bun run:*)"
5+
"Bash(bun run:*)",
6+
"Bash(git checkout:*)",
7+
"Bash(git pull:*)"
68
],
79
"deny": []
810
}

bun.lockb

3.18 KB
Binary file not shown.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
"dependencies": {
1212
"@achrinza/node-ipc": "10.1.11",
1313
"@iconscout/vue-unicons": "github:elasticdotventures/vue-unicons",
14+
"@types/three": "^0.179.0",
1415
"mitt": "^3.0.1",
1516
"path": "^0.12.7",
1617
"pinia": "3.0.3",
18+
"three": "^0.179.1",
1719
"v-idle-3": "^0.3.14",
1820
"vue": "3.5.18",
1921
"vue-cookie-law": "github:elasticdotventures/vue-cookie-law",

public/sample.glb

3.39 MB
Binary file not shown.

src/components/Logo3D.vue

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<script setup lang="ts">
2+
import { ref, onMounted, onUnmounted } from 'vue'
3+
import * as THREE from 'three'
4+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
5+
6+
const canvasRef = ref<HTMLCanvasElement | null>(null)
7+
const mouseX = ref(0)
8+
const mouseY = ref(0)
9+
10+
let scene: THREE.Scene
11+
let camera: THREE.PerspectiveCamera
12+
let renderer: THREE.WebGLRenderer
13+
let model: THREE.Group | null = null
14+
let spotLight: THREE.SpotLight
15+
let frontLight: THREE.DirectionalLight
16+
let animationId: number
17+
18+
const initThreeJS = () => {
19+
if (!canvasRef.value) return
20+
21+
// Scene
22+
scene = new THREE.Scene()
23+
24+
// Camera
25+
camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000)
26+
camera.position.z = 5 // Moved camera back to accommodate larger model
27+
28+
// Renderer
29+
renderer = new THREE.WebGLRenderer({
30+
canvas: canvasRef.value,
31+
alpha: true,
32+
antialias: true,
33+
powerPreference: "high-performance"
34+
})
35+
renderer.setSize(100, 100)
36+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
37+
renderer.setClearColor(0x000000, 0)
38+
renderer.outputColorSpace = THREE.SRGBColorSpace
39+
renderer.toneMapping = THREE.ACESFilmicToneMapping
40+
renderer.toneMappingExposure = 1.0
41+
42+
// Lights
43+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1)
44+
scene.add(ambientLight)
45+
46+
// Create spotlight that follows mouse
47+
spotLight = new THREE.SpotLight(0xffffff, 100, 100, Math.PI / 4, 0.5, 2)
48+
spotLight.position.set(0, 0, 10)
49+
spotLight.target.position.set(0, 0, 0)
50+
scene.add(spotLight)
51+
scene.add(spotLight.target)
52+
53+
// Add bright front-facing directional light
54+
frontLight = new THREE.DirectionalLight(0xffffff, 15)
55+
frontLight.position.set(0, 0, 10)
56+
scene.add(frontLight)
57+
58+
// Load GLB model
59+
const loader = new GLTFLoader()
60+
loader.load('/sample.glb', (gltf) => {
61+
model = gltf.scene
62+
model.scale.setScalar(5.5) // Scale set to 5.5
63+
scene.add(model)
64+
}, undefined, (error) => {
65+
console.error('Error loading GLB model:', error)
66+
})
67+
68+
// Animation loop
69+
const animate = () => {
70+
animationId = requestAnimationFrame(animate)
71+
72+
if (model) {
73+
// Apply mouse-based rotation
74+
model.rotation.y = mouseX.value * 0.3
75+
model.rotation.x = mouseY.value * 0.2
76+
77+
// Move spotlight to mouse position
78+
const lightDistance = 8
79+
spotLight.position.set(
80+
mouseX.value * lightDistance,
81+
mouseY.value * lightDistance,
82+
10
83+
)
84+
spotLight.target.position.copy(model.position)
85+
}
86+
87+
renderer.render(scene, camera)
88+
}
89+
animate()
90+
}
91+
92+
const updateMousePosition = (event: MouseEvent) => {
93+
if (!canvasRef.value) return
94+
95+
const rect = canvasRef.value.getBoundingClientRect()
96+
const centerX = rect.left + rect.width / 2
97+
const centerY = rect.top + rect.height / 2
98+
99+
const deltaX = event.clientX - centerX
100+
const deltaY = event.clientY - centerY
101+
102+
const maxDistance = 200
103+
mouseX.value = Math.max(-1, Math.min(1, deltaX / maxDistance))
104+
mouseY.value = Math.max(-1, Math.min(1, deltaY / maxDistance))
105+
}
106+
107+
onMounted(() => {
108+
initThreeJS()
109+
document.addEventListener('mousemove', updateMousePosition)
110+
})
111+
112+
onUnmounted(() => {
113+
document.removeEventListener('mousemove', updateMousePosition)
114+
if (animationId) {
115+
cancelAnimationFrame(animationId)
116+
}
117+
if (renderer) {
118+
renderer.dispose()
119+
}
120+
})
121+
</script>
122+
123+
<template>
124+
<canvas
125+
ref="canvasRef"
126+
class="logo-3d"
127+
/>
128+
</template>
129+
130+
<style scoped>
131+
.logo-3d {
132+
width: 100px;
133+
height: 100px;
134+
margin-right: 10px;
135+
cursor: pointer;
136+
}
137+
</style>

src/components/PromptExecutionLogo.vue

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,56 @@
11
<script setup lang="ts">
2-
// import { ref } from 'vue'
3-
// defineProps<{ msg: string }>()
4-
// const count = ref(0)
2+
import { ref, onMounted, onUnmounted } from 'vue'
3+
import Logo3D from './Logo3D.vue'
4+
5+
const show3D = ref(false)
6+
let idleTimer: number | null = null
7+
8+
const startIdleTimer = () => {
9+
if (idleTimer) clearTimeout(idleTimer)
10+
11+
// Show 3D immediately on mouse activity
12+
show3D.value = true
13+
14+
// Set timer to hide 3D after 2 seconds of inactivity
15+
idleTimer = setTimeout(() => {
16+
show3D.value = false
17+
}, 2000)
18+
}
19+
20+
const handleMouseMove = () => {
21+
startIdleTimer()
22+
}
23+
24+
onMounted(() => {
25+
document.addEventListener('mousemove', handleMouseMove)
26+
// Start with 2D logo (show3D = false by default)
27+
})
28+
29+
onUnmounted(() => {
30+
document.removeEventListener('mousemove', handleMouseMove)
31+
if (idleTimer) clearTimeout(idleTimer)
32+
})
533
</script>
634

735
<template>
836
<div class="logo-wrapper">
937
<div class="logo-container">
10-
<img src="/PromptExecution-LogoV2-full-transparent.webp" class="logo-head" alt="Cybernetic Head"/>
38+
<!-- 2D Logo -->
39+
<img
40+
src="/PromptExecution-LogoV2-full-transparent.png"
41+
class="logo-2d"
42+
:class="{ 'fade-out': show3D }"
43+
alt="Cybernetic Head"
44+
/>
45+
46+
<!-- 3D Logo -->
47+
<div class="logo-3d-wrapper" :class="{ 'fade-in': show3D }">
48+
<Logo3D />
49+
</div>
50+
51+
<!-- Spacer to maintain layout -->
52+
<div class="logo-spacer"></div>
53+
1154
<div class="text-container">
1255
<div class="logo-title">PROMPT EXECUTION</div>
1356
<div class="logo-subtitle">COGNITIVE ROBOTIC PROCESS AUTOMATION</div>
@@ -55,9 +98,36 @@
5598
display: flex;
5699
align-items: center;
57100
height: 122px;
101+
position: relative;
102+
}
103+
104+
.logo-2d, .logo-3d-wrapper {
105+
width: 100px;
106+
height: 100px;
107+
margin-right: 10px;
108+
position: absolute;
109+
transition: opacity 0.6s ease-in-out;
110+
}
111+
112+
.logo-2d {
113+
opacity: 1;
114+
z-index: 1;
115+
}
116+
117+
.logo-2d.fade-out {
118+
opacity: 0;
119+
}
120+
121+
.logo-3d-wrapper {
122+
opacity: 0;
123+
z-index: 2;
124+
}
125+
126+
.logo-3d-wrapper.fade-in {
127+
opacity: 1;
58128
}
59129
60-
.logo-head {
130+
.logo-spacer {
61131
width: 100px;
62132
height: 100px;
63133
margin-right: 10px;

0 commit comments

Comments
 (0)