Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Capybara - Three.js Interactive Showcase</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,300&family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;1,9..144,400&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| fontFamily: { | |
| display: ['"Fraunces"', 'serif'], | |
| body: ['"DM Sans"', 'sans-serif'], | |
| alt: ['"Outfit"', 'sans-serif'], | |
| }, | |
| colors: { | |
| 'capy-1': '#B88050', | |
| 'capy-2': '#9D6B3F', | |
| 'capy-3': '#7A5330', | |
| 'capy-dark': '#4A3525', | |
| 'capy-light': '#C99A6B', | |
| 'capy-rose': '#D4A594', | |
| 'capy-sand': '#F2E6D8', | |
| 'capy-sky': '#8BBCC7', | |
| 'capy-teal': '#5A9BA8', | |
| 'capy-ocean': '#3D7A88', | |
| 'capy-grass': '#7BA872', | |
| 'capy-leaf': '#5E8A56', | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| :root { | |
| --bg-color: #F2E6D8; | |
| --accent: #B88050; | |
| --text: #3D2B1F; | |
| --card-bg: rgba(255, 255, 255, 0.55); | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| html, body { | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| font-family: 'DM Sans', sans-serif; | |
| background: var(--bg-color); | |
| } | |
| #three-canvas { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 0; | |
| } | |
| .ui-layer { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| .ui-layer > * { pointer-events: auto; } | |
| /* Glass card */ | |
| .glass { | |
| background: var(--card-bg); | |
| backdrop-filter: blur(16px) saturate(1.4); | |
| -webkit-backdrop-filter: blur(16px) saturate(1.4); | |
| border: 1px solid rgba(255,255,255,0.4); | |
| box-shadow: | |
| 0 1px 2px rgba(61, 43, 31, 0.04), | |
| 0 4px 12px rgba(61, 43, 31, 0.06), | |
| 0 12px 32px rgba(61, 43, 31, 0.08), | |
| inset 0 1px 0 rgba(255,255,255,0.6); | |
| border-radius: 20px; | |
| } | |
| /* Pills */ | |
| .pill { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 14px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| letter-spacing: 0.3px; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: all 0.35s cubic-bezier(0.22, 1, 0.36, 1); | |
| border: 1px solid rgba(255,255,255,0.5); | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| font-family: 'Outfit', sans-serif; | |
| } | |
| .pill:hover { | |
| transform: translateY(-2px) scale(1.03); | |
| box-shadow: 0 6px 20px rgba(61,43,31,0.12); | |
| } | |
| .pill:active { transform: translateY(0) scale(0.97); } | |
| .pill.active { | |
| box-shadow: 0 0 0 2px var(--accent), 0 4px 16px rgba(184,128,80,0.2); | |
| } | |
| /* Buttons */ | |
| .btn-primary { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 10px 22px; | |
| border-radius: 14px; | |
| font-family: 'Outfit', sans-serif; | |
| font-weight: 500; | |
| font-size: 13px; | |
| letter-spacing: 0.2px; | |
| color: #fff; | |
| background: var(--accent); | |
| border: none; | |
| cursor: pointer; | |
| transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1); | |
| box-shadow: 0 2px 8px rgba(184,128,80,0.25), 0 4px 20px rgba(184,128,80,0.15); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 14px rgba(184,128,80,0.35), 0 8px 28px rgba(184,128,80,0.2); | |
| } | |
| .btn-primary:active { transform: translateY(0); } | |
| .btn-secondary { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 8px 16px; | |
| border-radius: 12px; | |
| font-family: 'Outfit', sans-serif; | |
| font-weight: 500; | |
| font-size: 12px; | |
| letter-spacing: 0.2px; | |
| color: var(--text); | |
| background: rgba(255,255,255,0.55); | |
| border: 1px solid rgba(255,255,255,0.5); | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| } | |
| .btn-secondary:hover { | |
| background: rgba(255,255,255,0.8); | |
| transform: translateY(-1px); | |
| } | |
| /* Loading screen */ | |
| #loading-screen { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 200; | |
| background: #F2E6D8; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| transition: opacity 0.8s ease, visibility 0.8s ease; | |
| } | |
| #loading-screen.hidden { opacity: 0; visibility: hidden; } | |
| .loader-dots { | |
| display: flex; | |
| gap: 8px; | |
| margin-top: 24px; | |
| } | |
| .loader-dots span { | |
| width: 10px; height: 10px; | |
| border-radius: 50%; | |
| background: var(--accent); | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .loader-dots span:nth-child(1) { animation-delay: -0.32s; } | |
| .loader-dots span:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } | |
| 40% { transform: scale(1); opacity: 1; } | |
| } | |
| /* Tooltip */ | |
| .tooltip { | |
| position: relative; | |
| } | |
| .tooltip::after { | |
| content: attr(data-tip); | |
| position: absolute; | |
| bottom: calc(100% + 8px); | |
| left: 50%; | |
| transform: translateX(-50%) translateY(4px); | |
| padding: 5px 12px; | |
| background: rgba(61, 43, 31, 0.9); | |
| color: #fff; | |
| font-size: 11px; | |
| border-radius: 8px; | |
| white-space: nowrap; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: all 0.25s ease; | |
| font-family: 'Outfit', sans-serif; | |
| font-weight: 400; | |
| } | |
| .tooltip:hover::after { | |
| opacity: 1; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| /* Scrollbar */ | |
| .custom-scroll::-webkit-scrollbar { width: 4px; } | |
| .custom-scroll::-webkit-scrollbar-track { background: transparent; } | |
| .custom-scroll::-webkit-scrollbar-thumb { | |
| background: rgba(184,128,80,0.25); | |
| border-radius: 4px; | |
| } | |
| /* Floating label animation */ | |
| @keyframes floatLabel { | |
| 0%, 100% { transform: translateY(0); } | |
| 50% { transform: translateY(-4px); } | |
| } | |
| .float-anim { animation: floatLabel 3s ease-in-out infinite; } | |
| /* Fade in animation */ | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .anim-in { animation: slideUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards; } | |
| .anim-delay-1 { animation-delay: 0.1s; opacity: 0; } | |
| .anim-delay-2 { animation-delay: 0.25s; opacity: 0; } | |
| .anim-delay-3 { animation-delay: 0.4s; opacity: 0; } | |
| .anim-delay-4 { animation-delay: 0.55s; opacity: 0; } | |
| /* Floating info card */ | |
| .info-card { | |
| position: absolute; | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| z-index: 20; | |
| } | |
| .info-card.visible { opacity: 1; } | |
| /* Separator line */ | |
| .sep { | |
| width: 32px; | |
| height: 3px; | |
| border-radius: 2px; | |
| background: var(--accent); | |
| margin: 8px 0; | |
| } | |
| /* Tag dot */ | |
| .tag-dot { | |
| width: 6px; height: 6px; | |
| border-radius: 50%; | |
| } | |
| /* Part info card that follows mouse */ | |
| .part-info { | |
| position: fixed; | |
| pointer-events: none; | |
| z-index: 100; | |
| opacity: 0; | |
| transition: opacity 0.2s ease; | |
| transform: translate(-50%, -100%); | |
| margin-top: -12px; | |
| } | |
| .part-info.visible { opacity: 1; } | |
| /* Custom 3D cursor hint */ | |
| .cursor-hint { | |
| position: fixed; | |
| bottom: 32px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 20; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 8px 18px; | |
| border-radius: 999px; | |
| background: rgba(255,255,255,0.5); | |
| backdrop-filter: blur(12px); | |
| border: 1px solid rgba(255,255,255,0.4); | |
| font-family: 'Outfit', sans-serif; | |
| font-size: 11px; | |
| color: var(--text); | |
| opacity: 0.7; | |
| pointer-events: none; | |
| transition: opacity 0.5s ease; | |
| } | |
| .cursor-hint.fade { opacity: 0; } | |
| /* Decorative blob */ | |
| .blob { | |
| position: fixed; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| opacity: 0.15; | |
| pointer-events: none; | |
| z-index: 1; | |
| } | |
| /* Photo dot */ | |
| .photo-dot { | |
| position: absolute; | |
| width: 12px; height: 12px; | |
| border-radius: 50%; | |
| background: rgba(255,255,255,0.7); | |
| border: 2px solid var(--accent); | |
| cursor: pointer; | |
| z-index: 20; | |
| transition: all 0.3s ease; | |
| } | |
| .photo-dot:hover { | |
| transform: scale(1.4); | |
| background: var(--accent); | |
| } | |
| .photo-dot.active { | |
| background: var(--accent); | |
| box-shadow: 0 0 0 4px rgba(184,128,80,0.2); | |
| } | |
| .photo-line { | |
| position: absolute; | |
| height: 1px; | |
| background: linear-gradient(90deg, var(--accent), transparent); | |
| transform-origin: left center; | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .photo-dot:hover + .photo-line, | |
| .photo-dot.active + .photo-line { opacity: 0.5; } | |
| /* Keyframe for gentle pulse */ | |
| @keyframes gentlePulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.15); } | |
| } | |
| .pulse { animation: gentlePulse 2s ease-in-out infinite; } | |
| </style> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.160.0/build/three.module.js", | |
| "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <!-- Decorative ambient blobs --> | |
| <div class="blob" style="width:400px;height:400px;background:#B88050;top:-100px;left:-100px;"></div> | |
| <div class="blob" style="width:300px;height:300px;background:#5A9BA8;bottom:-50px;right:-50px;"></div> | |
| <div class="blob" style="width:250px;height:250px;background:#7BA872;top:40%;right:10%;"></div> | |
| <!-- Loading Screen --> | |
| <div id="loading-screen"> | |
| <div class="text-center"> | |
| <div class="w-20 h-20 rounded-2xl bg-capy-1 flex items-center justify-center mx-auto mb-6" style="box-shadow: 0 8px 32px rgba(184,128,80,0.25);"> | |
| <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="9" cy="9" r="1.5"/> | |
| <circle cx="15" cy="9" r="1.5"/> | |
| <path d="M7 13.5C7 13.5 9 15.5 12 15.5C15 15.5 17 13.5 17 13.5"/> | |
| </svg> | |
| </div> | |
| <h1 class="font-display text-2xl text-capy-dark mb-1" style="font-weight:500;">Capybara</h1> | |
| <p class="font-body text-sm text-capy-dark/50">Loading 3D scene...</p> | |
| </div> | |
| <div class="loader-dots"> | |
| <span></span><span></span><span></span> | |
| </div> | |
| </div> | |
| <!-- Three.js Canvas --> | |
| <canvas id="three-canvas"></canvas> | |
| <!-- UI Layer --> | |
| <div class="ui-layer"> | |
| <!-- Top Header --> | |
| <div class="w-full px-5 py-4 flex items-center justify-between"> | |
| <!-- Logo --> | |
| <div class="flex items-center gap-3 anim-in anim-delay-1"> | |
| <div class="w-9 h-9 rounded-xl bg-capy-1 flex items-center justify-center" style="box-shadow: 0 2px 10px rgba(184,128,80,0.3);"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="9" cy="9" r="1.5"/> | |
| <circle cx="15" cy="9" r="1.5"/> | |
| <path d="M7 13.5C7 13.5 9 15.5 12 15.5C15 15.5 17 13.5 17 13.5"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1 class="font-display text-base text-capy-dark leading-tight" style="font-weight:600;">Capybara</h1> | |
| <p class="font-body text-[10px] text-capy-dark/40 -mt-0.5">Low Poly 3D Showcase</p> | |
| </div> | |
| </div> | |
| <!-- Top Right Links --> | |
| <div class="flex items-center gap-2 anim-in anim-delay-1"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="pill" style="background:rgba(184,128,80,0.1);color:var(--accent);"> | |
| <span style="font-size:13px;">♥</span> Built with anycoder | |
| </a> | |
| <a href="#" class="pill" style="background:rgba(255,255,255,0.6);color:var(--text);"> | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="12" cy="12" r="10"/> | |
| <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/> | |
| <line x1="12" y1="17" x2="12.01" y2="17"/> | |
| </svg> | |
| Help | |
| </a> | |
| </div> | |
| </div> | |
| <!-- Left Info Panel --> | |
| <div class="absolute left-5 top-1/2 -translate-y-1/2 anim-in anim-delay-2" style="max-width:270px;"> | |
| <div class="glass p-6" style="border-radius:24px;"> | |
| <!-- Tag --> | |
| <div class="flex items-center gap-2 mb-3"> | |
| <span class="tag-dot" style="background:#7BA872;"></span> | |
| <span class="font-alt text-[11px] font-semibold uppercase tracking-widest text-capy-leaf">Interactive</span> | |
| </div> | |
| <h2 class="font-display text-2xl text-capy-dark leading-tight mb-1" style="font-weight:600;"> | |
| The World's<br>Most Chill Animal | |
| </h2> | |
| <div class="sep"></div> | |
| <p class="font-body text-[13px] text-capy-dark/65 leading-relaxed mb-4"> | |
| Capybaras are the largest living rodents, native to South America. Known for their incredibly chill and social nature. | |
| </p> | |
| <!-- Stats --> | |
| <div class="grid grid-cols-3 gap-2 mb-5"> | |
| <div class="text-center p-2 rounded-xl" style="background:rgba(184,128,80,0.08);"> | |
| <div class="font-alt text-lg font-bold text-capy-1 leading-none">1.3m</div> | |
| <div class="font-body text-[9px] text-capy-dark/40 mt-1 uppercase tracking-wider">Length</div> | |
| </div> | |
| <div class="text-center p-2 rounded-xl" style="background:rgba(90,155,168,0.08);"> | |
| <div class="font-alt text-lg font-bold text-capy-teal leading-none">66kg</div> | |
| <div class="font-body text-[9px] text-capy-dark/40 mt-1 uppercase tracking-wider">Weight</div> | |
| </div> | |
| <div class="text-center p-2 rounded-xl" style="background:rgba(123,168,114,0.08);"> | |
| <div class="font-alt text-lg font-bold text-capy-grass leading-none">10yr</div> | |
| <div class="font-body text-[9px] text-capy-dark/40 mt-1 uppercase tracking-wider">Lifespan</div> | |
| </div> | |
| </div> | |
| <!-- Description --> | |
| <p class="font-body text-[12px] text-capy-dark/50 leading-relaxed mb-5"> | |
| These gentle giants spend most of their day in water, grazing on grasses, and hanging out with birds, monkeys, and even crocodiles. | |
| </p> | |
| <!-- Body parts --> | |
| <div class="mb-4"> | |
| <p class="font-alt text-[10px] font-semibold uppercase tracking-widest text-capy-dark/30 mb-2">Anatomy</p> | |
| <div class="flex flex-wrap gap-1.5"> | |
| <span class="pill tooltip" data-tip="Click to inspect" style="background:rgba(184,128,80,0.1);color:var(--accent);" onclick="app.highlightPart('head')">Head</span> | |
| <span class="pill tooltip" data-tip="Click to inspect" style="background:rgba(184,128,80,0.1);color:var(--accent);" onclick="app.highlightPart('ears')">Ears</span> | |
| <span class="pill tooltip" data-tip="Click to inspect" style="background:rgba(184,128,80,0.1);color:var(--accent);" onclick="app.highlightPart('body')">Body</span> | |
| <span class="pill tooltip" data-tip="Click to inspect" style="background:rgba(184,128,80,0.1);color:var(--accent);" onclick="app.highlightPart('legs')">Legs</span> | |
| <span class="pill tooltip" data-tip="Click to inspect" style="background:rgba(184,128,80,0.1);color:var(--accent);" onclick="app.highlightPart('tail')">Tail</span> | |
| </div> | |
| </div> | |
| <!-- Colors --> | |
| <div class="mb-4"> | |
| <p class="font-alt text-[10px] font-semibold uppercase tracking-widest text-capy-dark/30 mb-2">Coat Color</p> | |
| <div class="flex items-center gap-2"> | |
| <button class="w-7 h-7 rounded-full border-2 border-transparent hover:border-capy-1 transition-all tooltip" data-tip="Classic Brown" style="background:#B88050;" onclick="app.setCoatColor('#B88050')"></button> | |
| <button class="w-7 h-7 rounded-full border-2 border-transparent hover:border-capy-1 transition-all tooltip" data-tip="Dark Chocolate" style="background:#7A5330;" onclick="app.setCoatColor('#7A5330')"></button> | |
| <button class="w-7 h-7 rounded-full border-2 border-transparent hover:border-capy-1 transition-all tooltip" data-tip="Golden Tan" style="background:#D4A068;" onclick="app.setCoatColor('#D4A068')"></button> | |
| <button class="w-7 h-7 rounded-full border-2 border-transparent hover:border-capy-1 transition-all tooltip" data-tip="Warm Russet" style="background:#C47A45;" onclick="app.setCoatColor('#C47A45')"></button> | |
| <button class="w-7 h-7 rounded-full border-2 border-transparent hover:border-capy-1 transition-all tooltip" data-tip="Cool Sand" style="background:#D2B898;" onclick="app.setCoatColor('#D2B898')"></button> | |
| </div> | |
| </div> | |
| <!-- Fact --> | |
| <div class="rounded-xl p-3" style="background:rgba(123,168,114,0.08);"> | |
| <div class="flex items-start gap-2"> | |
| <span class="text-base mt-[-2px]">☆</span> | |
| <div> | |
| <p class="font-body text-[12px] text-capy-dark/70 leading-snug"> | |
| Capybaras can stay underwater for up to <span class="font-semibold text-capy-grass">5 minutes</span> to hide from predators! | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bottom Controls --> | |
| <div class="absolute bottom-6 left-1/2 -translate-x-1/2 anim-in anim-delay-3"> | |
| <div class="glass px-4 py-3 flex items-center gap-3" style="border-radius:20px;"> | |
| <button class="btn-secondary tooltip" data-tip="Rotate around" onclick="app.toggleRotation()"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M21 12a9 9 0 1 1-6.219-8.56"/> | |
| <polyline points="21 3 21 9 15 9"/> | |
| </svg> | |
| <span id="rot-label">Rotate</span> | |
| </button> | |
| <div class="w-px h-5" style="background:rgba(61,43,31,0.12);"></div> | |
| <button class="btn-secondary tooltip" data-tip="Jump animation" onclick="app.triggerJump()"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/> | |
| <polyline points="17 18 23 18 23 12"/> | |
| </svg> | |
| Jump | |
| </button> | |
| <button class="btn-secondary tooltip" data-tip="Toggle movement" onclick="app.toggleAnim()"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polygon points="5 3 19 12 5 21 5 3"/> | |
| </svg> | |
| <span id="anim-label">Pause</span> | |
| </button> | |
| <div class="w-px h-5" style="background:rgba(61,43,31,0.12);"></div> | |
| <button class="btn-secondary tooltip" data-tip="Reset view" onclick="app.resetView()"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/> | |
| <polyline points="9 22 9 12 15 12 15 22"/> | |
| </svg> | |
| Reset | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Right Panel --> | |
| <div class="absolute right-5 top-1/2 -translate-y-1/2 anim-in anim-delay-4" style="max-width:220px;"> | |
| <div class="glass p-5" style="border-radius:20px;"> | |
| <!-- Status --> | |
| <div class="mb-4"> | |
| <p class="font-alt text-[10px] font-semibold uppercase tracking-widest text-capy-dark/30 mb-2">Current Mood</p> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-10 h-10 rounded-xl flex items-center justify-center" style="background:rgba(184,128,80,0.1);"> | |
| <span class="text-xl float-anim">☺</span> | |
| </div> | |
| <div> | |
| <p class="font-alt text-sm font-semibold text-capy-dark">Relaxed</p> | |
| <p class="font-body text-[10px] text-capy-dark/40">Enjoying the warmth</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stats bar --> | |
| <div class="mb-4 space-y-2.5"> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-body text-[10px] text-capy-dark/50">Chill Level</span> | |
| <span class="font-body text-[10px] font-semibold text-capy-1">100%</span> | |
| </div> | |
| <div class="h-1.5 rounded-full" style="background:rgba(184,128,80,0.12);"> | |
| <div class="h-full rounded-full transition-all duration-700" style="width:100%;background:var(--accent);"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-body text-[10px] text-capy-dark/50">Socialness</span> | |
| <span class="font-body text-[10px] font-semibold text-capy-teal">98%</span> | |
| </div> | |
| <div class="h-1.5 rounded-full" style="background:rgba(90,155,168,0.12);"> | |
| <div class="h-full rounded-full transition-all duration-700" style="width:98%;background:#5A9BA8;"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-body text-[10px] text-capy-dark/50">Hunger</span> | |
| <span class="font-body text-[10px] font-semibold text-capy-grass">72%</span> | |
| </div> | |
| <div class="h-1.5 rounded-full" style="background:rgba(123,168,114,0.12);"> | |
| <div class="h-full rounded-full transition-all duration-700" style="width:72%;background:#7BA872;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mini gallery --> | |
| <div class="mb-4"> | |
| <p class="font-alt text-[10px] font-semibold uppercase tracking-widest text-capy-dark/30 mb-2">Fun Facts</p> | |
| <div class="space-y-2"> | |
| <div class="flex items-start gap-2 p-2 rounded-lg" style="background:rgba(90,155,168,0.06);"> | |
| <span class="text-xs mt-0.5">🌿</span> | |
| <p class="font-body text-[11px] text-capy-dark/60 leading-snug">Capybaras have webbed feet, making them excellent swimmers.</p> | |
| </div> | |
| <div class="flex items-start gap-2 p-2 rounded-lg" style="background:rgba(123,168,114,0.06);"> | |
| <span class="text-xs mt-0.5">🎨</span> | |
| <p class="font-body text-[11px] text-capy-dark/60 leading-snug">Their scientific name is <em>Hydrochoerus hydrochaeris</em>.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Habitat tag --> | |
| <div class="flex items-center gap-2 p-3 rounded-xl" style="background:rgba(90,155,168,0.08);"> | |
| <span class="text-lg">🌎</span> | |
| <div> | |
| <p class="font-alt text-[11px] font-semibold text-capy-dark">Habitat</p> | |
| <p class="font-body text-[10px] text-capy-dark/50">South American wetlands</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Cursor hint --> | |
| <div class="cursor-hint" id="cursor-hint"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 2a4 4 0 0 0-4 4v2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-2V6a4 4 0 0 0-4-4z"/> | |
| <circle cx="12" cy="15" r="1"/> | |
| <path d="M8 11h8"/> | |
| </svg> | |
| <span>Drag to orbit · Scroll to zoom · Click capybara to interact</span> | |
| </div> | |
| </div> | |
| <!-- Part info tooltip (follows cursor) --> | |
| <div id="part-info" class="part-info"> | |
| <div class="glass px-3 py-2" style="border-radius:12px;"> | |
| <p id="part-info-text" class="font-alt text-xs font-semibold text-capy-dark">Head</p> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
| import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; | |
| import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; | |
| import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; | |
| const canvas = document.getElementById('three-canvas'); | |
| const loadingScreen = document.getElementById('loading-screen'); | |
| const partInfo = document.getElementById('part-info'); | |
| const partInfoText = document.getElementById('part-info-text'); | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color('#F2E6D8'); | |
| scene.fog = new THREE.Fog('#F2E6D8', 12, 28); | |
| const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100); | |
| camera.position.set(5, 3.5, 7); | |
| const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: 'high-performance' }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure = 1.15; | |
| renderer.outputColorSpace = THREE.SRGBColorSpace; | |
| // Post-processing (subtle bloom) | |
| const composer = new EffectComposer(renderer); | |
| composer.addPass(new RenderPass(scene, camera)); | |
| const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.15, 0.4, 0.85); | |
| composer.addPass(bloomPass); | |
| const controls = new OrbitControls(camera, canvas); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.06; | |
| controls.minDistance = 4; | |
| controls.maxDistance = 14; | |
| controls.maxPolarAngle = Math.PI / 2.05; | |
| controls.target.set(0, 0.8, 0); | |
| controls.autoRotate = true; | |
| controls.autoRotateSpeed = 0.6; | |
| // ==================== LIGHTING ==================== | |
| const ambient = new THREE.AmbientLight(0xFFF5E8, 0.45); | |
| scene.add(ambient); | |
| const hemi = new THREE.HemisphereLight(0xE8DDD0, 0xB8C4A8, 0.55); | |
| scene.add(hemi); | |
| const dirLight = new THREE.DirectionalLight(0xFFF0D6, 1.6); | |
| dirLight.position.set(5, 9, 5); | |
| dirLight.castShadow = true; | |
| dirLight.shadow.mapSize.set(2048, 2048); | |
| dirLight.shadow.camera.near = 1; | |
| dirLight.shadow.camera.far = 25; | |
| dirLight.shadow.camera.left = -8; | |
| dirLight.shadow.camera.right = 8; | |
| dirLight.shadow.camera.top = 8; | |
| dirLight.shadow.camera.bottom = -3; | |
| dirLight.shadow.bias = -0.0005; | |
| dirLight.shadow.radius = 3; | |
| scene.add(dirLight); | |
| const fillLight = new THREE.DirectionalLight(0xCCE8F0, 0.5); | |
| fillLight.position.set(-4, 3, -3); | |
| scene.add(fillLight); | |
| const backLight = new THREE.DirectionalLight(0xFFE8D0, 0.4); | |
| backLight.position.set(-3, 5, 7); | |
| scene.add(backLight); | |
| // ==================== MATERIALS ==================== | |
| const furMat = new THREE.MeshStandardMaterial({ | |
| color: 0xB88050, | |
| roughness: 0.78, | |
| metalness: 0.02, | |
| }); | |
| const darkFurMat = new THREE.MeshStandardMaterial({ | |
| color: 0x7A5330, | |
| roughness: 0.82, | |
| metalness: 0.02, | |
| }); | |
| const noseMat = new THREE.MeshStandardMaterial({ | |
| color: 0xC47A65, | |
| roughness: 0.45, | |
| metalness: 0.05, | |
| }); | |
| const eyeMat = new THREE.MeshStandardMaterial({ | |
| color: 0x1A0F08, | |
| roughness: 0.15, | |
| metalness: 0.2, | |
| }); | |
| const eyeShineMat = new THREE.MeshStandardMaterial({ | |
| color: 0xFFFFFF, | |
| roughness: 0.1, | |
| metalness: 0.1, | |
| emissive: 0xFFFFFF, | |
| emissiveIntensity: 0.4, | |
| }); | |
| const toothMat = new THREE.MeshStandardMaterial({ | |
| color: 0xFFF8F0, | |
| roughness: 0.35, | |
| metalness: 0.0, | |
| }); | |
| const grassMat = new THREE.MeshStandardMaterial({ | |
| color: 0x7BA872, | |
| roughness: 0.92, | |
| metalness: 0.0, | |
| }); | |
| const waterMat = new THREE.MeshPhysicalMaterial({ | |
| color: 0x6EB5C2, | |
| roughness: 0.1, | |
| metalness: 0.05, | |
| transmission: 0.45, | |
| thickness: 1.2, | |
| ior: 1.33, | |
| transparent: true, | |
| opacity: 0.7, | |
| }); | |
| const rockMat = new THREE.MeshStandardMaterial({ | |
| color: 0x9A8A78, | |
| roughness: 0.92, | |
| metalness: 0.0, | |
| }); | |
| const lilyMat = new THREE.MeshStandardMaterial({ | |
| color: 0x6B9E5E, | |
| roughness: 0.7, | |
| metalness: 0.0, | |
| side: THREE.DoubleSide, | |
| }); | |
| const lilyFlowerMat = new THREE.MeshStandardMaterial({ | |
| color: 0xF0D0C0, | |
| roughness: 0.5, | |
| metalness: 0.0, | |
| }); | |
| const groundMat = new THREE.MeshStandardMaterial({ | |
| color: 0xD4C8A8, | |
| roughness: 0.95, | |
| metalness: 0.0, | |
| }); | |
| // ==================== CAPYBARA ==================== | |
| const capyGroup = new THREE.Group(); | |
| const bodyParts = {}; | |
| // Body | |
| const bodyGeo = new THREE.SphereGeometry(1.1, 22, 18); | |
| bodyGeo.scale(1.0, 0.82, 1.35); | |
| const body = new THREE.Mesh(bodyGeo, furMat.clone()); | |
| body.position.y = 1.25; | |
| body.castShadow = true; | |
| body.receiveShadow = true; | |
| capyGroup.add(body); | |
| bodyParts.body = body; | |
| // Belly (subtle lighter patch) | |
| const bellyGeo = new THREE.SphereGeometry(0.85, 16, 14); | |
| bellyGeo.scale(0.9, 0.7, 1.1); | |
| const bellyMat = furMat.clone(); | |
| bellyMat.color.setHex(0xC99A6B); | |
| bellyMat.roughness = 0.85; | |
| const belly = new THREE.Mesh(bellyGeo, bellyMat); | |
| belly.position.set(0, 0.95, 0.15); | |
| belly.castShadow = true; | |
| belly.receiveShadow = true; | |
| capyGroup.add(belly); | |
| // Rump | |
| const rumpGeo = new THREE.SphereGeometry(0.82, 18, 14); | |
| rumpGeo.scale(1.02, 0.88, 1.08); | |
| const rump = new THREE.Mesh(rumpGeo, furMat.clone()); | |
| rump.position.set(0, 1.35, -1.1); | |
| rump.castShadow = true; | |
| rump.receiveShadow = true; | |
| capyGroup.add(rump); | |
| // Head group | |
| const headGroup = new THREE.Group(); | |
| headGroup.position.set(0, 1.95, |