| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Our Little 3D Terminal</title> |
| <style> |
| body { margin: 0; overflow: hidden; background-color: #0a0a0a; } |
| canvas { display: block; } |
| #chat-container { |
| position: absolute; |
| bottom: 20px; |
| left: 50%; |
| transform: translateX(-50%); |
| width: 80%; |
| max-width: 600px; |
| z-index: 10; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| } |
| #chat-input { |
| width: 100%; |
| padding: 10px; |
| border: none; |
| border-radius: 5px; |
| background-color: rgba(50, 50, 70, 0.8); |
| color: #00ff00; |
| font-family: 'Courier New', Courier, monospace; |
| font-size: 1em; |
| outline: none; |
| box-shadow: 0 0 10px rgba(0, 255, 0, 0.5); |
| } |
| #chat-input::placeholder { |
| color: rgba(0, 255, 0, 0.5); |
| } |
| </style> |
| </head> |
| <body> |
|
|
| |
| <canvas id="three-canvas"></canvas> |
|
|
| |
| <div id="chat-container"> |
| <input type="text" id="chat-input" placeholder="Type your thoughts here..."> |
| </div> |
|
|
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.158.0/three.min.js"></script> |
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.158.0/examples/js/controls/OrbitControls.js"></script> |
|
|
| <script> |
| |
| const canvas = document.getElementById('three-canvas'); |
| const chatInput = document.getElementById('chat-input'); |
| const chatMessages = []; |
| const maxMessages = 10; |
| |
| |
| const scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x0a0a0a); |
| |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| camera.position.set(0, 1, 5); |
| |
| const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true }); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| |
| |
| const ambientLight = new THREE.AmbientLight(0x404040); |
| scene.add(ambientLight); |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); |
| directionalLight.position.set(1, 1, 1).normalize(); |
| scene.add(directionalLight); |
| |
| |
| const controls = new THREE.OrbitControls(camera, renderer.domElement); |
| controls.enableDamping = true; |
| controls.dampingFactor = 0.25; |
| controls.screenSpacePanning = false; |
| controls.maxPolarAngle = Math.PI / 2; |
| |
| |
| const terminalWidth = 4; |
| const terminalHeight = 2.5; |
| const terminalDepth = 0.1; |
| const terminalGeometry = new THREE.BoxGeometry(terminalWidth, terminalHeight, terminalDepth); |
| |
| |
| const textCanvas = document.createElement('canvas'); |
| textCanvas.width = 512; |
| textCanvas.height = 512; |
| const textContext = textCanvas.getContext('2d'); |
| const terminalTexture = new THREE.CanvasTexture(textCanvas); |
| |
| |
| const terminalMaterial = new THREE.MeshStandardMaterial({ |
| map: terminalTexture, |
| color: 0x222222, |
| emissive: 0x00ff00, |
| emissiveIntensity: 0.8, |
| metalness: 0.1, |
| roughness: 0.8, |
| }); |
| |
| const terminalMesh = new THREE.Mesh(terminalGeometry, terminalMaterial); |
| terminalMesh.position.set(0, 1.5, 0); |
| scene.add(terminalMesh); |
| |
| |
| function updateTerminalTexture() { |
| textContext.fillStyle = '#0a0a0a'; |
| textContext.fillRect(0, 0, textCanvas.width, textCanvas.height); |
| |
| textContext.font = 'Bold 40px Courier New'; |
| textContext.fillStyle = '#00ff00'; |
| textContext.textAlign = 'left'; |
| textContext.textBaseline = 'top'; |
| |
| const padding = 20; |
| const lineHeight = 45; |
| |
| |
| for (let i = 0; i < chatMessages.length; i++) { |
| const message = chatMessages[chatMessages.length - 1 - i]; |
| const y = textCanvas.height - padding - (i * lineHeight); |
| if (y < padding) break; |
| |
| |
| const words = message.split(' '); |
| let line = ''; |
| let currentY = y; |
| |
| for (let j = 0; j < words.length; j++) { |
| const testLine = line + words[j] + ' '; |
| const metrics = textContext.measureText(testLine); |
| const testWidth = metrics.width; |
| |
| if (testWidth > textCanvas.width - padding * 2 && j > 0) { |
| textContext.fillText(line, padding, currentY); |
| line = words[j] + ' '; |
| currentY += lineHeight; |
| if (currentY > textCanvas.height - padding) break; |
| } else { |
| line = testLine; |
| } |
| } |
| if (line.length > 0 && currentY <= textCanvas.height - padding) { |
| textContext.fillText(line, padding, currentY); |
| } |
| if (currentY > textCanvas.height - padding) break; |
| } |
| |
| |
| terminalTexture.needsUpdate = true; |
| } |
| |
| |
| updateTerminalTexture(); |
| |
| |
| chatInput.addEventListener('keypress', function(event) { |
| if (event.key === 'Enter') { |
| const message = chatInput.value.trim(); |
| if (message) { |
| console.log("Sending message:", message); |
| |
| |
| |
| |
| chatMessages.push("You: " + message); |
| while (chatMessages.length > maxMessages) { |
| chatMessages.shift(); |
| } |
| updateTerminalTexture(); |
| |
| chatInput.value = ''; |
| event.preventDefault(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| }); |
| |
| |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| |
| controls.update(); |
| |
| |
| |
| |
| renderer.render(scene, camera); |
| } |
| |
| |
| window.addEventListener('resize', onWindowResize, false); |
| |
| function onWindowResize() { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| } |
| |
| |
| animate(); |
| |
| |
| chatMessages.push("Alexa: Hey there!"); |
| chatMessages.push("Alexa: What's up?"); |
| updateTerminalTexture(); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| </script> |
| </body> |
| </html> |