anycoder-0682cd7b / index.html
HI7RAI's picture
Upload folder using huggingface_hub
40f6ed0 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS3 3D Model Viewer - AnyCoder</title>
<!-- Import Fonts and Icons -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #00f2ff;
--secondary: #7000ff;
--bg-dark: #0f0f13;
--panel-bg: rgba(255, 255, 255, 0.05);
--border-color: rgba(255, 255, 255, 0.1);
--text-main: #ffffff;
--text-muted: #888888;
/* Smartphone Variables */
--phone-w: 180px;
--phone-h: 360px;
--phone-d: 15px;
--phone-radius: 25px;
/* Glasses Variables */
--glass-frame-w: 70px;
--glass-lens-h: 50px;
--glass-d: 10px;
--glass-bridge: 20px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg-dark);
background-image:
radial-gradient(circle at 10% 20%, rgba(112, 0, 255, 0.15) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(0, 242, 255, 0.15) 0%, transparent 20%);
color: var(--text-main);
font-family: 'Inter', sans-serif;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* --- Header --- */
header {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 20px 30px;
z-index: 100;
display: flex;
justify-content: space-between;
align-items: center;
pointer-events: none; /* Let clicks pass through to canvas */
}
.logo {
font-weight: 800;
font-size: 1.2rem;
letter-spacing: 1px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
pointer-events: auto;
cursor: pointer;
}
.brand-link {
text-decoration: none;
color: var(--text-muted);
font-size: 0.9rem;
pointer-events: auto;
transition: color 0.3s;
}
.brand-link:hover {
color: var(--primary);
}
/* --- Main Scene Container --- */
.scene-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
perspective: 1000px; /* The depth of the 3D scene */
overflow: hidden;
cursor: grab;
}
.scene-container:active {
cursor: grabbing;
}
/* --- 3D World Wrapper --- */
.world {
position: relative;
width: 0;
height: 0;
transform-style: preserve-3d;
transform: rotateX(0deg) rotateY(0deg);
transition: transform 0.1s linear; /* Smooth drag */
}
/* --- Common Face Styles --- */
.face {
position: absolute;
transform-style: preserve-3d;
backface-visibility: hidden; /* Performance optimization */
border: 1px solid rgba(255,255,255,0.05);
}
/* --- MODEL 1: SMARTPHONE --- */
.phone-wrapper {
display: none; /* Hidden by default */
transform-style: preserve-3d;
}
.phone-face {
width: var(--phone-w);
height: var(--phone-h);
background: #111;
border-radius: var(--phone-radius);
box-shadow: inset 0 0 20px rgba(255,255,255,0.1);
}
/* Phone Geometry */
.p-front { transform: translateZ(calc(var(--phone-d) / 2)); }
.p-back { transform: rotateY(180deg) translateZ(calc(var(--phone-d) / 2)); background: #222; }
.p-right { width: var(--phone-d); height: var(--phone-h); transform: rotateY(90deg) translateZ(calc(var(--phone-w) / 2)); background: #050505; }
.p-left { width: var(--phone-d); height: var(--phone-h); transform: rotateY(-90deg) translateZ(calc(var(--phone-w) / 2)); background: #050505; }
.p-top { width: var(--phone-w); height: var(--phone-d); transform: rotateX(90deg) translateZ(calc(var(--phone-h) / 2)); background: #111; }
.p-bottom { width: var(--phone-w); height: var(--phone-d); transform: rotateX(-90deg) translateZ(calc(var(--phone-h) / 2)); background: #111; }
/* Phone Details */
.screen {
width: 100%;
height: 100%;
border-radius: var(--phone-radius);
background: url('https://images.unsplash.com/photo-1616348436168-de43ad0db179?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80') no-repeat center/cover;
position: relative;
overflow: hidden;
}
.screen::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 50%);
pointer-events: none;
}
/* Notch / Dynamic Island */
.notch {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 25px;
background: #000;
border-radius: 15px;
z-index: 2;
display: flex;
justify-content: center;
align-items: center;
}
.notch::before {
content: '';
width: 6px;
height: 6px;
background: #222;
border-radius: 50%;
box-shadow: 0 0 0 2px #333;
}
/* Back Camera Bump */
.camera-bump {
position: absolute;
top: 20px;
left: 20px;
width: 80px;
height: 80px;
background: #1a1a1a;
border-radius: 20px;
transform: translateZ(1px); /* Slightly off back face */
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5px;
padding: 10px;
}
.cam-lens {
background: #000;
border-radius: 50%;
border: 1px solid #333;
box-shadow: inset 0 0 5px rgba(0,0,0,0.8);
}
/* --- MODEL 2: GLASSES --- */
.glasses-wrapper {
display: none; /* Hidden by default */
transform-style: preserve-3d;
}
/* Glasses Group Logic */
.lens-group {
position: absolute;
transform-style: preserve-3d;
}
/* Lenses */
.lens {
width: var(--glass-frame-w);
height: var(--glass-lens-h);
background: rgba(100, 200, 255, 0.1);
border: 4px solid #333;
border-radius: 10px 10px 30px 30px;
box-shadow: inset 0 0 20px rgba(0, 242, 255, 0.2);
backdrop-filter: blur(2px);
position: relative;
}
.lens-reflective {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(135deg, rgba(255,255,255,0.3) 0%, transparent 50%);
border-radius: 6px 6px 26px 26px;
pointer-events: none;
}
.g-left { transform: translateX(calc(-1 * (var(--glass-frame-w) / 2))); }
.g-right { transform: translateX(calc(var(--glass-frame-w) / 2)); }
/* Bridge */
.bridge {
position: absolute;
width: var(--glass-bridge);
height: 10px;
background: #222;
top: 20px;
left: 50%;
transform: translateX(-50%);
}
/* Temples (Arms) */
.temple {
position: absolute;
width: 80px;
height: 12px;
background: #2a2a2a;
top: 15px;
transform-origin: left center;
border-radius: 5px;
}
.t-left { left: calc(-1 * var(--glass-frame-w) / 2); transform: rotateY(-20deg) translateZ(calc(var(--glass-d) / 2)); }
.t-right { right: calc(-1 * var(--glass-frame-w) / 2); transform: rotateY(20deg) translateZ(calc(var(--glass-d) / 2)); }
/* Frame Rim Depth */
.rim-depth {
position: absolute;
width: var(--glass-frame-w);
height: var(--glass-lens-h);
border: 4px solid #222;
border-radius: 10px 10px 30px 30px;
transform: translateZ(calc(var(--glass-d) / 2));
pointer-events: none;
}
.rim-depth.back { transform: translateZ(calc(var(--glass-d) / -2)); }
/* --- UI Controls Panel --- */
.controls {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 400px;
background: var(--panel-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
z-index: 10;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.tab-btn {
flex: 1;
background: rgba(255,255,255,0.05);
border: none;
color: var(--text-muted);
padding: 10px;
border-radius: 10px;
cursor: pointer;
font-family: inherit;
font-weight: 600;
transition: 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.tab-btn.active {
background: var(--primary);
color: #000;
box-shadow: 0 0 15px rgba(0, 242, 255, 0.4);
}
.sliders {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.slider-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.slider-group label {
font-size: 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
}
input[type=range] {
width: 100%;
accent-color: var(--primary);
background: transparent;
}
/* --- Responsive Adjustments --- */
@media (max-width: 768px) {
.controls {
bottom: 10px;
width: 95%;
padding: 15px;
}
:root {
/* Scale down models on mobile */
--phone-w: 140px;
--phone-h: 280px;
}
}
/* Grid floor decoration */
.grid-floor {
position: absolute;
width: 2000px;
height: 2000px;
background-image:
linear-gradient(var(--border-color) 1px, transparent 1px),
linear-gradient(90deg, var(--border-color) 1px, transparent 1px);
background-size: 50px 50px;
transform: rotateX(90deg) translateZ(-200px) translateX(-500px) translateY(-500px);
pointer-events: none;
opacity: 0.2;
}
</style>
</head>
<body>
<header>
<div class="logo">Fiorc3D</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">Built with anycoder</a>
</header>
<!-- 3D Scene -->
<div class="scene-container" id="scene">
<div class="world" id="world">
<!-- Decorative Floor -->
<div class="grid-floor"></div>
<!-- Model: Smartphone -->
<div class="phone-wrapper" id="model-phone">
<div class="face p-front phone-face">
<div class="screen">
<div class="notch"></div>
</div>
</div>
<div class="face p-back phone-face">
<div class="camera-bump">
<div class="cam-lens"></div>
<div class="cam-lens"></div>
<div class="cam-lens"></div>
<div class="cam-lens"></div>
</div>
</div>
<div class="face p-right phone-face"></div>
<div class="face p-left phone-face"></div>
<div class="face p-top phone-face"></div>
<div class="face p-bottom phone-face"></div>
</div>
<!-- Model: Glasses -->
<div class="glasses-wrapper" id="model-glasses">
<div class="lens-group">
<!-- Bridge -->
<div class="bridge"></div>
<!-- Left Lens -->
<div class="lens g-left">
<div class="lens-reflective"></div>
<div class="rim-depth"></div> <!-- Front Rim -->
</div>
<div class="rim-depth back"></div> <!-- Back Rim (simplified positioning) -->
<!-- Right Lens -->
<div class="lens g-right">
<div class="lens-reflective"></div>
<div class="rim-depth"></div>
</div>
<div class="rim-depth back" style="transform: translateX(50%) translateZ(-5px);"></div>
<!-- Temples -->
<div class="temple t-left"></div>
<div class="temple t-right"></div>
</div>
</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="tabs">
<button class="tab-btn active" onclick="switchModel('phone')">
<i class="fas fa-mobile-screen-button"></i> Smartphone
</button>
<button class="tab-btn" onclick="switchModel('glasses')">
<i class="fas fa-glasses"></i> Glasses
</button>
</div>
<div class="sliders">
<div class="slider-group">
<label>Rotate X</label>
<input type="range" min="0" max="360" value="0" id="rotateX">
</div>
<div class="slider-group">
<label>Rotate Y</label>
<input type="range" min="0" max="360" value="0" id="rotateY">
</div>
</div>
</div>
<script>
// --- State Management ---
const state = {
rotX: 15,
rotY: -25,
currentModel: 'phone'
};
// --- DOM Elements ---
const world = document.getElementById('world');
const modelPhone = document.getElementById('model-phone');
const modelGlasses = document.getElementById('model-glasses');
const scene = document.getElementById('scene');
const inputRotateX = document.getElementById('rotateX');
const inputRotateY = document.getElementById('rotateY');
const tabBtns = document.querySelectorAll('.tab-btn');
// --- Initialization ---
function init() {
updateTransform();
showModel(state.currentModel);
// Add Auto-rotation animation
requestAnimationFrame(animate);
}
// --- Animation Loop ---
let autoRotate = true;
let lastTime = 0;
function animate(time) {
// Slowly rotate if not dragging/interacting
if(autoRotate) {
state.rotY += 0.2;
// Update inputs visually
inputRotateY.value = state.rotY % 360;
inputRotateX.value = state.rotX; // Keep X relatively stable
updateTransform();
}
requestAnimationFrame(animate);
}
// --- Core Logic ---
function updateTransform() {
// Apply CSS transform to the 3D world
world.style.transform = `rotateX(${state.rotX}deg) rotateY(${state.rotY}deg)`;
}
function showModel(modelName) {
state.currentModel = modelName;
// Hide all
modelPhone.style.display = 'none';
modelGlasses.style.display = 'none';
// Show active
if(modelName === 'phone') {
modelPhone.style.display = 'block';
// Adjust scale for phone
modelPhone.style.transform = `scale(1.2)`;
} else {
modelGlasses.style.display = 'block';
// Adjust scale for glasses (make it bigger)
modelGlasses.style.transform = `scale(1.8) translateY(-20px)`;
}
// Reset rotation slightly for better initial view
state.rotX = 15;
state.rotY = -25;
inputRotateX.value = 15;
inputRotateY.value = -25;
}
// --- Event Listeners ---
// 1. Sliders
inputRotateX.addEventListener('input', (e) => {
autoRotate = false; // Stop auto rotation on interaction
state.rotX = e.target.value;
updateTransform();
});
inputRotateY.addEventListener('input', (e) => {
autoRotate = false;
state.rotY = e.target.value;
updateTransform();
});
// 2. Mouse/Touch Drag on Scene (Orbit Control Simulation)
let isDragging = false;
let startX, startY;
scene.addEventListener('mousedown', (e) => {
isDragging = true;
autoRotate = false;
startX = e.clientX;
startY = e.clientY;
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
state.rotY += deltaX * 0.5;
state.rotX -= deltaY * 0.5;
// Clamp X rotation to avoid flipping
state.rotX = Math.max(-90, Math.min(90, state.rotX));
// Update Inputs
inputRotateX.value = state.rotX;
inputRotateY.value = state.rotY;
updateTransform();
startX = e.clientX;
startY = e.clientY;
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
// Touch support
scene.addEventListener('touchstart', (e) => {
isDragging = true;
autoRotate = false;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
}, {passive: false});
window.addEventListener('touchmove', (e) => {
if (!isDragging) return;
e.preventDefault(); // Prevent scrolling
const deltaX = e.touches[0].clientX - startX;
const deltaY = e.touches[0].clientY - startY;
state.rotY += deltaX * 0.5;
state.rotX -= deltaY * 0.5;
state.rotX = Math.max(-90, Math.min(90, state.rotX));
updateTransform();
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
}, {passive: false});
window.addEventListener('touchend', () => {
isDragging = false;
});
// 3. Tab Switching
window.switchModel = (model) => {
tabBtns.forEach(btn => btn.classList.remove('active'));
// Find the button that calls this function (hacky but works for simple inline)
if(model === 'phone') {
tabBtns[0].classList.add('active');
} else {
tabBtns[1].classList.add('active');
}
showModel(model);
}
// Start app
init();
</script>
</body>
</html>