Restructure. Fix vendor script versions.
This commit is contained in:
parent
fd08d223ae
commit
080f93c3ee
@ -4,7 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Retro TV Player</title>
|
||||
<!-- Load Tailwind CSS for styling --><script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Load Tailwind CSS for styling --><script src="./vendor/tailwind-3.4.17.js" x-src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Load Three.js for 3D rendering --><script src="./vendor/three.min.js" x-src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<script>
|
||||
// Configure Tailwind for the button
|
||||
tailwind.config = {
|
||||
@ -17,7 +18,6 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- Load Three.js for 3D rendering --><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<style>
|
||||
/* Dark room aesthetic */
|
||||
body {
|
||||
|
||||
413
tv-player/mockups/guineapigs.html
Normal file
413
tv-player/mockups/guineapigs.html
Normal file
@ -0,0 +1,413 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>3D Guinea Pig Garden</title>
|
||||
<!-- Load Tailwind CSS for modern styling -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Load Three.js library -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<style>
|
||||
/* Custom CSS to ensure the canvas fills the viewport */
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
#info-box {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 10px 15px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
border-left: 4px solid #38A169; /* Garden color accent */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<div id="info-box">
|
||||
<h2 class="font-bold text-lg mb-1">Guinea Pig Garden Frolic</h2>
|
||||
<p class="text-sm">Click and drag to look around the field.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Global variables for Three.js
|
||||
let scene, camera, renderer, controls;
|
||||
const guineaPigs = [];
|
||||
const NUM_PIGS = 80;
|
||||
const ROOM_SIZE = 15; // Defines the half-width/depth of the movement area (now a field)
|
||||
|
||||
// --- Utility Functions (TTS Helpers kept for completeness) ---
|
||||
|
||||
function base64ToArrayBuffer(base64) {
|
||||
const binaryString = atob(base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
function pcmToWav(int16Array, sampleRate) {
|
||||
const numChannels = 1;
|
||||
const bytesPerSample = 2;
|
||||
const blockAlign = numChannels * bytesPerSample;
|
||||
const dataSize = int16Array.length * bytesPerSample;
|
||||
const buffer = new ArrayBuffer(44 + dataSize);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
writeString(view, 0, 'RIFF');
|
||||
view.setUint32(4, 36 + dataSize, true);
|
||||
writeString(view, 8, 'WAVE');
|
||||
writeString(view, 12, 'fmt ');
|
||||
view.setUint32(16, 16, true);
|
||||
view.setUint16(20, 1, true);
|
||||
view.setUint16(22, numChannels, true);
|
||||
view.setUint32(24, sampleRate, true);
|
||||
view.setUint32(28, sampleRate * blockAlign, true);
|
||||
view.setUint16(32, blockAlign, true);
|
||||
view.setUint16(34, 16, true);
|
||||
writeString(view, 36, 'data');
|
||||
view.setUint32(40, dataSize, true);
|
||||
|
||||
for (let i = 0; i < int16Array.length; i++) {
|
||||
view.setInt16(44 + i * 2, int16Array[i], true);
|
||||
}
|
||||
|
||||
return new Blob([buffer], { type: 'audio/wav' });
|
||||
|
||||
function writeString(view, offset, string) {
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Three.js Setup and Geometry ---
|
||||
|
||||
function init() {
|
||||
// 1. Scene Setup
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x87ceeb); // Lighter Sky Blue
|
||||
|
||||
// 2. Camera Setup
|
||||
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
camera.position.set(0, 5, 20);
|
||||
|
||||
// 3. Renderer Setup
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.shadowMap.enabled = true; // Enable shadows
|
||||
document.getElementById('container').appendChild(renderer.domElement);
|
||||
|
||||
// 4. Lighting (Sunlight simulation)
|
||||
const ambientLight = new THREE.AmbientLight(0x404040, 1.5); // Soft white light
|
||||
scene.add(ambientLight);
|
||||
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5); // Brighter sun
|
||||
directionalLight.position.set(5, 20, 10);
|
||||
directionalLight.castShadow = true;
|
||||
directionalLight.shadow.mapSize.width = 2048;
|
||||
directionalLight.shadow.mapSize.height = 2048;
|
||||
directionalLight.shadow.camera.near = 0.5;
|
||||
directionalLight.shadow.camera.far = 50;
|
||||
directionalLight.shadow.camera.left = -ROOM_SIZE * 2;
|
||||
directionalLight.shadow.camera.right = ROOM_SIZE * 2;
|
||||
directionalLight.shadow.camera.top = ROOM_SIZE * 2;
|
||||
directionalLight.shadow.camera.bottom = -ROOM_SIZE * 2;
|
||||
scene.add(directionalLight);
|
||||
|
||||
// 5. Garden Setup
|
||||
createGarden();
|
||||
|
||||
// 6. Guinea Pigs
|
||||
createGuineaPigs();
|
||||
|
||||
// 7. Camera Controls (using custom mouse drag controls)
|
||||
setupControls();
|
||||
|
||||
// Event Listeners
|
||||
window.addEventListener('resize', onWindowResize, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple, low-poly geometry for a single guinea pig.
|
||||
* The pig is returned as a THREE.Group.
|
||||
*/
|
||||
function createGuineaPigMesh() {
|
||||
const pigGroup = new THREE.Group();
|
||||
|
||||
// Body (Capsule/Cylinder)
|
||||
const bodyGeometry = new THREE.CylinderGeometry(0.5, 0.6, 1.5, 8);
|
||||
const color = new THREE.Color(Math.random(), Math.random(), Math.random()).lerp(new THREE.Color(0.6, 0.6, 0.6), 0.5); // Mixed fur colors
|
||||
const bodyMaterial = new THREE.MeshLambertMaterial({ color: color });
|
||||
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
||||
body.rotation.z = Math.PI / 2; // Lay it flat along the X-axis
|
||||
body.position.y = 0.3; // Lift off floor
|
||||
body.castShadow = true;
|
||||
body.receiveShadow = true;
|
||||
pigGroup.add(body);
|
||||
|
||||
// Head (Sphere)
|
||||
const headGeometry = new THREE.SphereGeometry(0.4, 8, 8);
|
||||
const headMaterial = new THREE.MeshLambertMaterial({ color: color.clone().offsetHSL(0, 0, 0.1) });
|
||||
const head = new THREE.Mesh(headGeometry, headMaterial);
|
||||
head.position.set(0.8, 0.3, 0); // Head positioned along the positive X-axis
|
||||
head.castShadow = true;
|
||||
pigGroup.add(head);
|
||||
|
||||
// Ears (Cones)
|
||||
const earGeometry = new THREE.ConeGeometry(0.1, 0.2, 4);
|
||||
const earMaterial = new THREE.MeshLambertMaterial({ color: 0xcb997e }); // Pinkish
|
||||
const earL = new THREE.Mesh(earGeometry, earMaterial);
|
||||
earL.position.set(0.8, 0.5, 0.3);
|
||||
earL.rotation.x = Math.PI / 2;
|
||||
earL.rotation.y = -Math.PI / 8;
|
||||
earL.rotation.z = -Math.PI / 4;
|
||||
pigGroup.add(earL);
|
||||
|
||||
const earR = new THREE.Mesh(earGeometry, earMaterial);
|
||||
earR.position.set(0.8, 0.5, -0.3);
|
||||
earR.rotation.x = Math.PI / 2;
|
||||
earR.rotation.y = Math.PI / 8;
|
||||
earR.rotation.z = Math.PI / 4;
|
||||
pigGroup.add(earR);
|
||||
|
||||
// FIX: Rotate the entire pig group -90 degrees around the Y-axis.
|
||||
// This aligns the pig's "forward" direction (originally +X) with the scene's +Z axis,
|
||||
// which correctly matches the orientation calculated by the movement logic.
|
||||
pigGroup.rotation.y = -Math.PI / 2;
|
||||
|
||||
return pigGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds NUM_PIGS instances to the scene with initial random positions.
|
||||
*/
|
||||
function createGuineaPigs() {
|
||||
for (let i = 0; i < NUM_PIGS; i++) {
|
||||
const pigMesh = createGuineaPigMesh();
|
||||
|
||||
// Initial random position within the room bounds
|
||||
pigMesh.position.x = (Math.random() - 0.5) * ROOM_SIZE * 1.8;
|
||||
pigMesh.position.z = (Math.random() - 0.5) * ROOM_SIZE * 1.8;
|
||||
pigMesh.rotation.y = Math.random() * Math.PI * 2; // Random initial direction
|
||||
|
||||
// Attach movement properties to the mesh
|
||||
pigMesh.velocity = new THREE.Vector3(
|
||||
(Math.random() - 0.5) * 0.05,
|
||||
0,
|
||||
(Math.random() - 0.5) * 0.05
|
||||
);
|
||||
pigMesh.turnCooldown = Math.random() * 60 + 60; // Pigs turn every 60-120 frames
|
||||
pigMesh.turnTimer = 0;
|
||||
pigMesh.runAnimation = 0;
|
||||
|
||||
scene.add(pigMesh);
|
||||
guineaPigs.push(pigMesh);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds randomized grass and flower meshes to the garden.
|
||||
*/
|
||||
function addFoliage() {
|
||||
const FOLIAGE_COUNT = 300;
|
||||
const MAX_BOUND = ROOM_SIZE * 1.8; // Area where foliage can spawn
|
||||
|
||||
// Simple Grass Mesh (tall, thin cylinder)
|
||||
const grassGeometry = new THREE.CylinderGeometry(0.05, 0.05, 1, 4);
|
||||
const grassMaterial = new THREE.MeshLambertMaterial({ color: 0x68a87b }); // Slightly different green
|
||||
|
||||
// Simple Flower Mesh (stem + petal sphere)
|
||||
const stemGeometry = new THREE.CylinderGeometry(0.03, 0.03, 0.5, 4);
|
||||
const petalGeometry = new THREE.SphereGeometry(0.15, 6, 6);
|
||||
const flowerMaterials = [
|
||||
new THREE.MeshLambertMaterial({ color: 0xffd700 }), // Yellow
|
||||
new THREE.MeshLambertMaterial({ color: 0xff69b4 }), // Pink
|
||||
new THREE.MeshLambertMaterial({ color: 0x7b68ee }) // Lavender
|
||||
];
|
||||
const stemMaterial = new THREE.MeshLambertMaterial({ color: 0x8fbc8f });
|
||||
|
||||
for (let i = 0; i < FOLIAGE_COUNT; i++) {
|
||||
const x = (Math.random() - 0.5) * MAX_BOUND;
|
||||
const z = (Math.random() - 0.5) * MAX_BOUND;
|
||||
|
||||
// Alternate between grass and flowers
|
||||
if (i % 3 === 0) {
|
||||
// Add Flower
|
||||
const stem = new THREE.Mesh(stemGeometry, stemMaterial);
|
||||
stem.position.set(x, 0.25, z);
|
||||
stem.receiveShadow = true;
|
||||
scene.add(stem);
|
||||
|
||||
const petals = new THREE.Mesh(petalGeometry, flowerMaterials[i % 3]);
|
||||
petals.position.set(x, 0.5 + Math.random() * 0.1, z);
|
||||
petals.castShadow = true;
|
||||
scene.add(petals);
|
||||
} else {
|
||||
// Add Grass
|
||||
const grass = new THREE.Mesh(grassGeometry, grassMaterial);
|
||||
const height = Math.random() * 0.5 + 0.5;
|
||||
grass.scale.y = height;
|
||||
grass.position.set(x, height / 2, z);
|
||||
grass.rotation.y = Math.random() * Math.PI * 2;
|
||||
grass.receiveShadow = true;
|
||||
scene.add(grass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the garden ground (replaces the room walls and floor).
|
||||
*/
|
||||
function createGarden() {
|
||||
const grassMaterial = new THREE.MeshLambertMaterial({ color: 0x4f8e5b }); // Rich Grass Green
|
||||
|
||||
// Ground
|
||||
const groundGeometry = new THREE.PlaneGeometry(ROOM_SIZE * 4, ROOM_SIZE * 4); // Large ground plane
|
||||
const ground = new THREE.Mesh(groundGeometry, grassMaterial);
|
||||
ground.rotation.x = -Math.PI / 2;
|
||||
ground.position.y = 0;
|
||||
ground.receiveShadow = true;
|
||||
scene.add(ground);
|
||||
|
||||
// Add flowers and grass
|
||||
addFoliage();
|
||||
}
|
||||
|
||||
// --- Interaction / Controls ---
|
||||
|
||||
function setupControls() {
|
||||
let isDragging = false;
|
||||
let previousMousePosition = { x: 0, y: 0 };
|
||||
const rotationSpeed = 0.005;
|
||||
|
||||
// Create a target for the camera to look at, placed roughly in the center of the scene.
|
||||
const pivot = new THREE.Group();
|
||||
pivot.position.set(0, 0, 0);
|
||||
scene.add(pivot);
|
||||
pivot.add(camera);
|
||||
|
||||
const mouseDownHandler = (e) => {
|
||||
isDragging = true;
|
||||
previousMousePosition.x = e.clientX;
|
||||
previousMousePosition.y = e.clientY;
|
||||
};
|
||||
|
||||
const mouseUpHandler = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const mouseMoveHandler = (e) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const deltaX = e.clientX - previousMousePosition.x;
|
||||
const deltaY = e.clientY - previousMousePosition.y;
|
||||
|
||||
// Rotate the pivot around the Y axis for horizontal movement
|
||||
pivot.rotation.y += deltaX * rotationSpeed;
|
||||
|
||||
// Calculate vertical rotation (tilt the camera up/down)
|
||||
let newXRotation = pivot.rotation.x + deltaY * rotationSpeed;
|
||||
|
||||
// Clamp the vertical rotation to prevent flipping
|
||||
const PI_HALF = Math.PI / 2;
|
||||
newXRotation = Math.max(-PI_HALF + 0.1, Math.min(PI_HALF - 0.1, newXRotation));
|
||||
pivot.rotation.x = newXRotation;
|
||||
|
||||
previousMousePosition.x = e.clientX;
|
||||
previousMousePosition.y = e.clientY;
|
||||
};
|
||||
|
||||
renderer.domElement.addEventListener('mousedown', mouseDownHandler, false);
|
||||
renderer.domElement.addEventListener('mouseup', mouseUpHandler, false);
|
||||
renderer.domElement.addEventListener('mousemove', mouseMoveHandler, false);
|
||||
renderer.domElement.addEventListener('mouseout', mouseUpHandler, false); // Stop dragging if mouse leaves
|
||||
}
|
||||
|
||||
// --- Animation Loop ---
|
||||
|
||||
function updateGuineaPigs() {
|
||||
const boundary = ROOM_SIZE * 0.9;
|
||||
const speed = 0.05;
|
||||
|
||||
guineaPigs.forEach(pig => {
|
||||
// 1. Movement and Rotation
|
||||
pig.position.x += pig.velocity.x * speed;
|
||||
pig.position.z += pig.velocity.z * speed;
|
||||
|
||||
// 2. Bound Checking (Check if near edges of the field)
|
||||
let needsTurn = false;
|
||||
if (pig.position.x > boundary || pig.position.x < -boundary) {
|
||||
pig.velocity.x *= -1;
|
||||
needsTurn = true;
|
||||
pig.position.x = Math.max(-boundary, Math.min(boundary, pig.position.x));
|
||||
}
|
||||
if (pig.position.z > boundary || pig.position.z < -boundary) {
|
||||
pig.velocity.z *= -1;
|
||||
needsTurn = true;
|
||||
pig.position.z = Math.max(-boundary, Math.min(boundary, pig.position.z));
|
||||
}
|
||||
|
||||
// 3. Random Turning
|
||||
pig.turnTimer++;
|
||||
if (pig.turnTimer > pig.turnCooldown || needsTurn) {
|
||||
pig.turnTimer = 0;
|
||||
// Calculate new random velocity and rotation
|
||||
pig.velocity.x = (Math.random() - 0.5) * 2;
|
||||
pig.velocity.z = (Math.random() - 0.5) * 2;
|
||||
pig.velocity.normalize(); // Keep consistent speed
|
||||
|
||||
// Set rotation based on new velocity direction (no offset needed as the mesh is pre-rotated)
|
||||
pig.rotation.y = Math.atan2(pig.velocity.x, pig.velocity.z)-Math.PI/2;
|
||||
|
||||
pig.turnCooldown = Math.random() * 60 + 60;
|
||||
}
|
||||
|
||||
// 4. Simple 'Run' Animation (wobbling)
|
||||
pig.runAnimation += 0.1;
|
||||
pig.rotation.x = Math.sin(pig.runAnimation) * 0.05; // Head wobble
|
||||
pig.position.y = Math.abs(Math.sin(pig.runAnimation * 0.5)) * 0.05 + 0.3; // Slight bounce
|
||||
});
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
updateGuineaPigs(); // Move and update the pigs
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
// Start the application when the window loads
|
||||
window.onload = function () {
|
||||
// Check if Three.js is loaded
|
||||
if (typeof THREE === 'undefined') {
|
||||
console.error("Three.js not loaded. Please ensure the CDN link is correct.");
|
||||
return;
|
||||
}
|
||||
init();
|
||||
animate();
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
83
tv-player/vendor/tailwind-3.4.17.js
vendored
Normal file
83
tv-player/vendor/tailwind-3.4.17.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
tv-player/vendor/three.min.js
vendored
Normal file
6
tv-player/vendor/three.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user