Motion paths are a powerful tool in modern web design, allowing for dynamic and engaging user experiences. By guiding elements along predefined routes, designers can create visually appealing animations that capture attention and convey information effectively.
In this article, we will explore ten compelling motion path examples that demonstrate the versatility and creativity possible with this technique. Whether you're looking to enhance your website's interactivity or simply seeking inspiration, these examples will provide valuable insights and ideas.
CODE1
Here's the code:
CODETEXT1
CODE2
Here's the code:
CODETEXT2
CODE3
Here's the code:
CODETEXT3
CODE4
Here's the code:
CODETEXT4
CODE5
Here's the code:
CODETEXT5
Subframe's drag-and-drop interface and intuitive, responsive canvas make designing motion paths a breeze. Loved by designers and developers alike, it ensures pixel-perfect UI every time.
Ready to elevate your designs? Start for free today!
CODE6
Here's the code:
CODETEXT6
CODE7
Here's the code:
CODETEXT7
CODE8
Here's the code:
CODETEXT8
CODE9
Here's the code:
CODETEXT9
CODE10
Here's the code:
CODETEXT10
Unlock the full potential of your designs with Subframe. Our platform offers unparalleled efficiency and the ability to create pixel-perfect UIs, including stunning motion paths, in minutes.
Why wait? Start creating immediately and see the difference for yourself. Start for free today!
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Cart Animation</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 700px; background: #f8fafc; overflow: hidden; } .container { width: 100%; max-width: 700px; height: 700px; padding: 2rem; position: relative; } .header { text-align: center; margin-bottom: 2rem; } h1 { font-size: 1.8rem; color: #1e293b; margin-bottom: 0.5rem; font-weight: 700; } p.tagline { color: #64748b; font-size: 1rem; max-width: 500px; margin: 0 auto; } .products { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem; max-width: 620px; margin: 0 auto; } .product { background: white; border-radius: 12px; padding: 1.2rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; cursor: pointer; position: relative; overflow: hidden; border: 1px solid #f1f5f9; } .product:hover { transform: translateY(-5px); box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05); } .product-img { width: 100%; height: 120px; background: #f8fafc; border-radius: 8px; margin-bottom: 1rem; display: flex; justify-content: center; align-items: center; color: #94a3b8; font-size: 2rem; } .product-name { font-weight: 600; color: #334155; font-size: 0.95rem; margin-bottom: 0.5rem; } .product-price { color: #0f766e; font-weight: 700; font-size: 1.1rem; } .add-to-cart { background: linear-gradient(135deg, #0f766e, #0d9488); color: white; border: none; padding: 0.6rem 1rem; border-radius: 6px; font-weight: 600; font-size: 0.8rem; cursor: pointer; margin-top: 0.8rem; width: 100%; transition: all 0.2s ease; } .add-to-cart:hover { background: linear-gradient(135deg, #0d9488, #0f766e); transform: translateY(-2px); } .cart-container { position: fixed; bottom: 2rem; right: 2rem; background: white; width: 60px; height: 60px; border-radius: 30px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); display: flex; justify-content: center; align-items: center; z-index: 100; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .cart-container:hover { transform: scale(1.1); } .cart-icon { font-size: 1.8rem; color: #0f766e; position: relative; } .cart-badge { position: absolute; top: -8px; right: -8px; background: #ef4444; color: white; width: 20px; height: 20px; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 0.7rem; font-weight: 700; opacity: 0; transform: scale(0.5); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .cart-badge.show { opacity: 1; transform: scale(1); } .flying-item { position: absolute; width: 30px; height: 30px; background: linear-gradient(135deg, #0f766e, #0d9488); border-radius: 50%; display: flex; justify-content: center; align-items: center; color: white; font-size: 0.8rem; font-weight: 700; pointer-events: none; opacity: 0; z-index: 1000; } @media (max-width: 600px) { .products { grid-template-columns: repeat(2, 1fr); } h1 { font-size: 1.5rem; } p.tagline { font-size: 0.9rem; } .product { padding: 1rem; } .product-img { height: 100px; } } @media (max-width: 400px) { .products { grid-template-columns: 1fr; } } .success-msg { position: fixed; top: -50px; left: 50%; transform: translateX(-50%); background: #10b981; color: white; padding: 0.8rem 1.5rem; border-radius: 8px; font-weight: 600; box-shadow: 0 8px 15px rgba(16, 185, 129, 0.2); z-index: 2000; transition: top 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .success-msg.show { top: 20px; } </style> </head> <body> <div class="container"> <div class="header"> <h1>Trending Tech Picks</h1> <p class="tagline">Add these top-rated gadgets to your cart with our satisfying drop-in animation</p> </div> <div class="products"> <div class="product" data-name="Wireless Earbuds" data-emoji="🎧"> <div class="product-img">🎧</div> <div class="product-name">Noise-Cancelling Earbuds</div> <div class="product-price">$89.99</div> <button class="add-to-cart">Add to Cart</button> </div> <div class="product" data-name="Smart Watch" data-emoji="⌚"> <div class="product-img">⌚</div> <div class="product-name">Fitness Tracking Watch</div> <div class="product-price">$125.00</div> <button class="add-to-cart">Add to Cart</button> </div> <div class="product" data-name="Portable SSD" data-emoji="💾"> <div class="product-img">💾</div> <div class="product-name">512GB USB-C SSD</div> <div class="product-price">$79.99</div> <button class="add-to-cart">Add to Cart</button> </div> <div class="product" data-name="Power Bank" data-emoji="🔋"> <div class="product-img">🔋</div> <div class="product-name">Fast-Charge Power Bank</div> <div class="product-price">$39.50</div> <button class="add-to-cart">Add to Cart</button> </div> <div class="product" data-name="Wireless Charger" data-emoji="⚡"> <div class="product-img">⚡</div> <div class="product-name">15W Wireless Charger</div> <div class="product-price">$28.99</div> <button class="add-to-cart">Add to Cart</button> </div> <div class="product" data-name="Bluetooth Speaker" data-emoji="🔊"> <div class="product-img">🔊</div> <div class="product-name">Waterproof Speaker</div> <div class="product-price">$59.95</div> <button class="add-to-cart">Add to Cart</button> </div> </div> </div> <div class="cart-container"> <div class="cart-icon">🛒 <div class="cart-badge">0</div> </div> </div> <div class="success-msg">Item added to your cart!</div> <script> document.addEventListener('DOMContentLoaded', function() { const cartBadge = document.querySelector('.cart-badge'); const cartContainer = document.querySelector('.cart-container'); const successMsg = document.querySelector('.success-msg'); const addButtons = document.querySelectorAll('.add-to-cart'); let cartCount = 0; addButtons.forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); const product = this.closest('.product'); const productRect = product.getBoundingClientRect(); const cartRect = cartContainer.getBoundingClientRect(); const emoji = product.dataset.emoji; // Create flying item const flyingItem = document.createElement('div'); flyingItem.className = 'flying-item'; flyingItem.textContent = emoji; flyingItem.style.left = `${productRect.left + productRect.width / 2}px`; flyingItem.style.top = `${productRect.top + productRect.height / 2}px`; document.body.appendChild(flyingItem); // Make the item visible with a slight delay setTimeout(() => { flyingItem.style.opacity = '1'; }, 50); // Define bezier curve path const startX = productRect.left + productRect.width / 2; const startY = productRect.top + productRect.height / 2; const endX = cartRect.left + cartRect.width / 2; const endY = cartRect.top + cartRect.height / 2; const controlX = startX > endX ? startX - 100 : startX + 100; const controlY = startY - 150; // Animate along the bezier curve const startTime = performance.now(); const duration = 800; function animate(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); if (progress < 1) { // Bezier curve calculation const tSquared = progress * progress; const tCubed = tSquared * progress; const bx = 3 * (1 - progress) * (1 - progress) * progress * controlX + 3 * (1 - progress) * tSquared * endX + tCubed * endX; const by = 3 * (1 - progress) * (1 - progress) * progress * controlY + 3 * (1 - progress) * tSquared * endY + tCubed * endY; flyingItem.style.left = `${bx}px`; flyingItem.style.top = `${by}px`; // Scale down gradually const scale = 1 - (progress * 0.5); flyingItem.style.transform = `scale(${scale})`; requestAnimationFrame(animate); } else { // Animation complete - add bounce effect to cart cartContainer.style.transform = 'scale(1.2)'; setTimeout(() => { cartContainer.style.transform = 'scale(1)'; }, 300); // Update cart count cartCount++; cartBadge.textContent = cartCount; cartBadge.classList.add('show'); // Show success message successMsg.classList.add('show'); setTimeout(() => { successMsg.classList.remove('show'); }, 2000); // Remove flying item document.body.removeChild(flyingItem); // Disable the add button temporarily button.disabled = true; button.textContent = 'Added ✓'; setTimeout(() => { button.disabled = false; button.textContent = 'Add to Cart'; }, 1500); } } requestAnimationFrame(animate); }); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Onboarding Flow Guide</title> <style> :root { --primary-color: #ffd4e3; --secondary-color: #c8e7ff; --accent-color: #b3c4ff; --text-color: #333; --path-color: #c8c8ff; --step-1-color: #ffadc7; --step-2-color: #adceff; --step-3-color: #c9adff; --button-color: #9dadff; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f9f9ff; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; } .container { width: 100%; max-width: 700px; height: 700px; position: relative; background-color: white; border-radius: 16px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07); overflow: hidden; padding: 20px; display: flex; flex-direction: column; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .logo { font-size: 24px; font-weight: bold; color: var(--text-color); letter-spacing: -0.5px; } .logo span { color: var(--accent-color); } .skip-button { background: none; border: none; color: var(--accent-color); font-size: 14px; cursor: pointer; font-weight: 500; padding: 5px 10px; border-radius: 20px; transition: all 0.3s ease; } .skip-button:hover { background-color: var(--secondary-color); transform: translateY(-2px); } .progress-bar { height: 4px; background-color: #f0f0f0; border-radius: 2px; margin-bottom: 30px; overflow: hidden; } .progress-indicator { height: 100%; width: 0%; background-color: var(--accent-color); border-radius: 2px; transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1); } .screen-container { flex: 1; position: relative; display: flex; justify-content: center; align-items: center; overflow: hidden; } .screen { position: absolute; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; opacity: 0; transform: translateX(50px); transition: opacity 0.5s ease, transform 0.5s ease; padding: 0 30px; } .screen.active { opacity: 1; transform: translateX(0); } .phone-mockup { position: relative; width: 220px; height: 440px; background-color: white; border-radius: 25px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); border: 10px solid #f5f5f5; margin-bottom: 30px; overflow: hidden; } .phone-screen { width: 100%; height: 100%; position: relative; background-color: #fbfbfb; display: flex; flex-direction: column; } .phone-content { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 20px; position: relative; } .motion-path { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } .path { fill: none; stroke: var(--path-color); stroke-width: 2; stroke-dasharray: 5, 5; opacity: 0.7; } .instruction-point { position: absolute; width: 40px; height: 40px; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-weight: bold; color: white; z-index: 2; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15); opacity: 0; transform: scale(0.5); transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } .instruction-box { position: absolute; background-color: white; border-radius: 12px; padding: 15px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); max-width: 180px; z-index: 2; opacity: 0; transform: translateY(10px); transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } .instruction-title { font-weight: 600; font-size: 14px; margin-bottom: 5px; color: var(--text-color); } .instruction-desc { font-size: 12px; color: #666; line-height: 1.4; } .cta-container { margin-top: 30px; display: flex; flex-direction: column; align-items: center; } .title { font-size: 22px; font-weight: 700; margin-bottom: 10px; color: var(--text-color); text-align: center; } .description { font-size: 16px; text-align: center; color: #666; margin-bottom: 25px; line-height: 1.5; } .next-button { background-color: var(--button-color); border: none; color: white; padding: 12px 30px; border-radius: 25px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(173, 173, 255, 0.3); } .next-button:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(173, 173, 255, 0.4); } .animation-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } .floating-bubble { position: absolute; border-radius: 50%; background-color: rgba(255, 255, 255, 0.6); pointer-events: none; animation: float 6s infinite ease-in-out; } @keyframes float { 0%, 100% { transform: translateY(0) translateX(0); } 50% { transform: translateY(-15px) translateX(10px); } } .step-dots { display: flex; margin-top: 30px; } .step-dot { width: 8px; height: 8px; border-radius: 50%; background-color: #e0e0e0; margin: 0 5px; transition: all 0.3s ease; } .step-dot.active { width: 20px; border-radius: 4px; background-color: var(--accent-color); } /* Screen-specific styles */ #screen1 .instruction-point { background-color: var(--step-1-color); } #screen2 .instruction-point { background-color: var(--step-2-color); } #screen3 .instruction-point { background-color: var(--step-3-color); } /* Responsive adjustments */ @media (max-width: 500px) { .container { padding: 15px; } .phone-mockup { width: 180px; height: 360px; } .title { font-size: 20px; } .description { font-size: 14px; } } </style> </head> <body> <div class="container"> <div class="header"> <div class="logo">Clarity<span>Flow</span></div> <button class="skip-button">Skip Tour</button> </div> <div class="progress-bar"> <div class="progress-indicator"></div> </div> <div class="screen-container"> <div id="screen1" class="screen active"> <div class="phone-mockup"> <div class="phone-screen"> <div class="phone-content"> <svg class="motion-path" viewBox="0 0 200 400"> <path id="path1" class="path" d="M50,80 C70,140 130,180 150,280" /> </svg> <div id="point1" class="instruction-point" style="top: 80px; left: 50px;">1</div> <div id="box1" class="instruction-box" style="top: 60px; left: 80px;"> <div class="instruction-title">Tap to create</div> <div class="instruction-desc">Begin by tapping the plus icon to start your first project.</div> </div> <div id="point2" class="instruction-point" style="top: 280px; left: 150px;">2</div> <div id="box2" class="instruction-box" style="top: 260px; left: 90px;"> <div class="instruction-title">Name your project</div> <div class="instruction-desc">Give your project a descriptive name to stay organized.</div> </div> </div> </div> </div> <div class="cta-container"> <h2 class="title">Create Your First Project</h2> <p class="description">Follow the guided path to learn how to create and customize your first project in just seconds.</p> <button class="next-button">Next</button> </div> <div class="step-dots"> <div class="step-dot active"></div> <div class="step-dot"></div> <div class="step-dot"></div> </div> </div> <div id="screen2" class="screen"> <div class="phone-mockup"> <div class="phone-screen"> <div class="phone-content"> <svg class="motion-path" viewBox="0 0 200 400"> <path id="path2" class="path" d="M40,120 C90,150 110,200 160,220" /> </svg> <div id="point3" class="instruction-point" style="top: 120px; left: 40px;">1</div> <div id="box3" class="instruction-box" style="top: 100px; left: 70px;"> <div class="instruction-title">Choose template</div> <div class="instruction-desc">Select from our collection of beautiful templates.</div> </div> <div id="point4" class="instruction-point" style="top: 220px; left: 160px;">2</div> <div id="box4" class="instruction-box" style="top: 200px; left: 100px;"> <div class="instruction-title">Customize colors</div> <div class="instruction-desc">Tap to choose colors that match your brand identity.</div> </div> </div> </div> </div> <div class="cta-container"> <h2 class="title">Personalize Your Experience</h2> <p class="description">Select templates and color schemes that reflect your unique style and goals.</p> <button class="next-button">Next</button> </div> <div class="step-dots"> <div class="step-dot"></div> <div class="step-dot active"></div> <div class="step-dot"></div> </div> </div> <div id="screen3" class="screen"> <div class="phone-mockup"> <div class="phone-screen"> <div class="phone-content"> <svg class="motion-path" viewBox="0 0 200 400"> <path id="path3" class="path" d="M160,100 C110,150 90,200 40,250" /> </svg> <div id="point5" class="instruction-point" style="top: 100px; left: 160px;">1</div> <div id="box5" class="instruction-box" style="top: 80px; left: 100px;"> <div class="instruction-title">Add collaborators</div> <div class="instruction-desc">Invite team members to join your project.</div> </div> <div id="point6" class="instruction-point" style="top: 250px; left: 40px;">2</div> <div id="box6" class="instruction-box" style="top: 230px; left: 70px;"> <div class="instruction-title">Set deadlines</div> <div class="instruction-desc">Schedule important milestones and stay on track.</div> </div> </div> </div> </div> <div class="cta-container"> <h2 class="title">Collaborate Seamlessly</h2> <p class="description">Work with your team in real-time and keep everyone aligned with project deadlines.</p> <button class="next-button">Get Started</button> </div> <div class="step-dots"> <div class="step-dot"></div> <div class="step-dot"></div> <div class="step-dot active"></div> </div> </div> </div> <div class="animation-container"> <div class="floating-bubble" style="width: 30px; height: 30px; top: 10%; left: 10%; opacity: 0.5; background-color: var(--step-1-color); animation-delay: 0s;"></div> <div class="floating-bubble" style="width: 20px; height: 20px; top: 20%; left: 80%; opacity: 0.3; background-color: var(--step-2-color); animation-delay: 0.5s;"></div> <div class="floating-bubble" style="width: 40px; height: 40px; top: 70%; left: 20%; opacity: 0.2; background-color: var(--step-3-color); animation-delay: 1s;"></div> <div class="floating-bubble" style="width: 15px; height: 15px; top: 80%; left: 85%; opacity: 0.4; background-color: var(--accent-color); animation-delay: 1.5s;"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const screens = document.querySelectorAll('.screen'); const nextButtons = document.querySelectorAll('.next-button'); const skipButton = document.querySelector('.skip-button'); const progressIndicator = document.querySelector('.progress-indicator'); const stepDots = document.querySelectorAll('.step-dot'); let currentScreen = 0; // Initialize points and boxes with animations function animateInstructionPoints(screenIndex) { const pointsToAnimate = document.querySelectorAll(`#screen${screenIndex + 1} .instruction-point`); const boxesToAnimate = document.querySelectorAll(`#screen${screenIndex + 1} .instruction-box`); pointsToAnimate.forEach((point, index) => { setTimeout(() => { point.style.opacity = '1'; point.style.transform = 'scale(1)'; }, 300 + (index * 300)); }); boxesToAnimate.forEach((box, index) => { setTimeout(() => { box.style.opacity = '1'; box.style.transform = 'translateY(0)'; }, 500 + (index * 300)); }); } // Animate the motion path function animateMotionPath(screenIndex) { const path = document.querySelector(`#screen${screenIndex + 1} .path`); if (!path) return; const length = path.getTotalLength(); path.style.strokeDasharray = length; path.style.strokeDashoffset = length; setTimeout(() => { path.style.transition = 'stroke-dashoffset 1.5s ease-in-out'; path.style.strokeDashoffset = '0'; }, 200); } function showScreen(index) { screens.forEach((screen, i) => { if (i === index) { screen.classList.add('active'); // Reset instruction points and boxes const points = screen.querySelectorAll('.instruction-point'); const boxes = screen.querySelectorAll('.instruction-box'); points.forEach(point => { point.style.opacity = '0'; point.style.transform = 'scale(0.5)'; }); boxes.forEach(box => { box.style.opacity = '0'; box.style.transform = 'translateY(10px)'; }); // Start animations setTimeout(() => { animateMotionPath(index); animateInstructionPoints(index); }, 300); } else { screen.classList.remove('active'); } }); // Update progress indicator progressIndicator.style.width = `${(index + 1) / screens.length * 100}%`; // Update step dots stepDots.forEach((dot, i) => { if (i === index) { dot.classList.add('active'); } else { dot.classList.remove('active'); } }); currentScreen = index; } // Initialize first screen animations animateMotionPath(0); animateInstructionPoints(0); // Handle next button clicks nextButtons.forEach((button, index) => { button.addEventListener('click', () => { const nextIndex = (currentScreen + 1) % screens.length; if (nextIndex < screens.length) { showScreen(nextIndex); } }); }); // Handle skip button skipButton.addEventListener('click', () => { showScreen(screens.length - 1); }); // Handle dot navigation stepDots.forEach((dot, index) => { dot.addEventListener('click', () => { showScreen(index); }); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Wanderlust - Expedition Tracker</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Playfair Display', serif; } body { width: 100%; height: 100vh; overflow: hidden; background-color: #f8f4e9; display: flex; flex-direction: column; align-items: center; justify-content: center; } .container { width: 700px; height: 700px; position: relative; overflow: hidden; padding: 20px; display: flex; flex-direction: column; } .map-container { flex: 1; position: relative; background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="700" height="700" viewBox="0 0 700 700"><filter id="paper" x="0%" y="0%" width="100%" height="100%"><feTurbulence type="fractalNoise" baseFrequency="0.04" numOctaves="5" result="noise"/><feDiffuseLighting in="noise" lighting-color="%23f4e9d3" surfaceScale="2"><feDistantLight azimuth="45" elevation="60"/></feDiffuseLighting></filter><rect x="0" y="0" width="700" height="700" filter="url(%23paper)" opacity="0.5"/></svg>'); background-size: cover; border-radius: 8px; box-shadow: 0 4px 15px rgba(141, 109, 57, 0.2); border: 4px solid #a88c64; overflow: hidden; } .title-container { text-align: center; margin-bottom: 15px; } h1 { color: #5a4024; font-size: 2.2rem; margin-bottom: 5px; letter-spacing: 1px; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); } .subtitle { color: #8d6d39; font-size: 1rem; font-style: italic; } .map-overlay { position: absolute; width: 100%; height: 100%; background-image: linear-gradient(rgba(244, 233, 211, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(244, 233, 211, 0.1) 1px, transparent 1px); background-size: 20px 20px; z-index: 1; } .path { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 2; } svg { width: 100%; height: 100%; } .landmark { position: absolute; width: 20px; height: 20px; transform: translate(-50%, -50%); z-index: 3; cursor: pointer; transition: all 0.3s ease; } .landmark-icon { width: 100%; height: 100%; background-color: #c17f46; border-radius: 50%; border: 2px solid #5a4024; box-shadow: 0 0 0 4px rgba(193, 127, 70, 0.3); transition: all 0.3s ease; position: relative; transform-origin: center; } .landmark-icon:after { content: ''; position: absolute; top: 50%; left: 50%; width: 6px; height: 6px; background-color: #f8f4e9; border-radius: 50%; transform: translate(-50%, -50%); } .landmark:hover .landmark-icon { transform: scale(1.3); box-shadow: 0 0 0 6px rgba(193, 127, 70, 0.5), 0 0 20px rgba(193, 127, 70, 0.7); } .landmark-info { position: absolute; width: 180px; background-color: #f4e9d3; border: 2px solid #a88c64; border-radius: 6px; padding: 10px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); opacity: 0; visibility: hidden; transition: all 0.3s ease; transform: translateY(10px); z-index: 10; pointer-events: none; } .landmark:hover .landmark-info { opacity: 1; visibility: visible; transform: translateY(0); } .landmark-name { font-weight: bold; color: #5a4024; margin-bottom: 5px; font-size: 1rem; } .landmark-desc { color: #8d6d39; font-size: 0.8rem; line-height: 1.4; } .controls { display: flex; margin-top: 15px; justify-content: space-between; width: 100%; } .btn { background-color: #a88c64; color: #f4e9d3; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-weight: bold; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } .btn:hover { background-color: #5a4024; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } .btn:active { transform: translateY(0); box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); } .btn.btn-ghost { background-color: transparent; border: 1px solid #a88c64; color: #a88c64; } .btn.btn-ghost:hover { background-color: rgba(168, 140, 100, 0.1); color: #5a4024; border-color: #5a4024; } .compass { position: absolute; bottom: 20px; right: 20px; width: 60px; height: 60px; background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" fill="none" stroke="%235a4024" stroke-width="2"/><circle cx="50" cy="50" r="40" fill="none" stroke="%23a88c64" stroke-width="1" stroke-dasharray="2,2"/><path d="M50,10 L53,50 L50,90 L47,50 Z" fill="%23c17f46"/><path d="M10,50 L50,47 L90,50 L50,53 Z" fill="%235a4024"/><circle cx="50" cy="50" r="5" fill="%23f4e9d3" stroke="%235a4024" stroke-width="1"/></svg>'); background-size: contain; z-index: 5; opacity: 0.7; transition: all 0.3s ease; transform: rotate(0deg); } .compass:hover { opacity: 1; transform: scale(1.1) rotate(15deg); cursor: pointer; } .legend { position: absolute; bottom: 20px; left: 20px; background-color: rgba(244, 233, 211, 0.8); border: 1px solid #a88c64; border-radius: 4px; padding: 8px; font-size: 0.8rem; color: #5a4024; z-index: 5; transition: all 0.3s ease; } .legend-title { font-weight: bold; margin-bottom: 5px; text-decoration: underline; } .legend-item { display: flex; align-items: center; margin-bottom: 3px; } .legend-icon { width: 10px; height: 10px; margin-right: 5px; background-color: #c17f46; border-radius: 50%; border: 1px solid #5a4024; } .legend-line { width: 15px; height: 2px; margin-right: 5px; background-color: #5a4024; } .expedition-details { position: absolute; top: 20px; right: 20px; background-color: rgba(244, 233, 211, 0.8); border: 1px solid #a88c64; border-radius: 4px; padding: 10px; font-size: 0.8rem; color: #5a4024; z-index: 5; max-width: 150px; } .expedition-title { font-weight: bold; margin-bottom: 5px; } .progress { width: 100%; height: 4px; background-color: rgba(90, 64, 36, 0.2); border-radius: 2px; margin-top: 8px; overflow: hidden; } .progress-bar { height: 100%; background-color: #a88c64; transition: width 0.6s ease; } .pulse { position: absolute; border-radius: 50%; background-color: rgba(193, 127, 70, 0.6); animation: pulse 1.5s ease-out infinite; z-index: 2; } @keyframes pulse { 0% { transform: scale(0.5); opacity: 0.6; } 100% { transform: scale(2); opacity: 0; } } .path-segment { stroke-dasharray: 1000; stroke-dashoffset: 1000; transition: stroke-dashoffset 1.5s ease; } .travel-tip { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(90, 64, 36, 0.9); color: #f4e9d3; padding: 15px 20px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); z-index: 20; max-width: 300px; text-align: center; animation: fadeInOut 3s forwards; } @keyframes fadeInOut { 0% { opacity: 0; transform: translate(-50%, -60%); } 15% { opacity: 1; transform: translate(-50%, -50%); } 85% { opacity: 1; transform: translate(-50%, -50%); } 100% { opacity: 0; transform: translate(-50%, -40%); } } @media (max-width: 700px) { .container { width: 100%; height: 100vh; padding: 10px; } h1 { font-size: 1.8rem; } .controls { flex-wrap: wrap; gap: 10px; } .expedition-details, .legend { position: static; margin-bottom: 10px; max-width: 100%; } } </style> </head> <body> <div class="container"> <div class="title-container"> <h1>Wanderlust Expeditions</h1> <div class="subtitle">Tracing the Ancient Spice Route</div> </div> <div class="map-container"> <div class="map-overlay"></div> <div class="path"> <svg> <path class="path-segment" id="path-1" d="M100,300 C180,280 250,350 320,300" fill="none" stroke="#5a4024" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1000" stroke-dashoffset="1000"/> <path class="path-segment" id="path-2" d="M320,300 C380,260 400,220 450,250" fill="none" stroke="#5a4024" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1000" stroke-dashoffset="1000"/> <path class="path-segment" id="path-3" d="M450,250 C500,270 520,350 580,370" fill="none" stroke="#5a4024" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1000" stroke-dashoffset="1000"/> <path class="path-segment" id="path-4" d="M580,370 C630,390 650,330 600,280" fill="none" stroke="#5a4024" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1000" stroke-dashoffset="1000"/> </svg> </div> <div class="landmark" id="landmark-1" style="top: 300px; left: 100px;"> <div class="landmark-icon"></div> <div class="landmark-info" style="left: 20px; top: -20px;"> <div class="landmark-name">Venice, Italy</div> <div class="landmark-desc">The starting point of the Silk Road for European merchants. Famed for its canals and Marco Polo's expeditions.</div> </div> </div> <div class="landmark" id="landmark-2" style="top: 300px; left: 320px;"> <div class="landmark-icon"></div> <div class="landmark-info" style="left: 20px; top: -20px;"> <div class="landmark-name">Constantinople</div> <div class="landmark-desc">The gateway between East and West. A crucial trading hub where cultures and goods flowed freely.</div> </div> </div> <div class="landmark" id="landmark-3" style="top: 250px; left: 450px;"> <div class="landmark-icon"></div> <div class="landmark-info" style="left: -160px; top: -20px;"> <div class="landmark-name">Samarkand</div> <div class="landmark-desc">The jewel of the Silk Road with stunning architecture and bustling bazaars selling exotic spices.</div> </div> </div> <div class="landmark" id="landmark-4" style="top: 370px; left: 580px;"> <div class="landmark-icon"></div> <div class="landmark-info" style="left: -160px; top: -20px;"> <div class="landmark-name">Kashgar</div> <div class="landmark-desc">Ancient oasis city where caravans rested. Famous for its Sunday Market and cultural fusion.</div> </div> </div> <div class="landmark" id="landmark-5" style="top: 280px; left: 600px;"> <div class="landmark-icon"></div> <div class="landmark-info" style="left: -160px; top: -20px;"> <div class="landmark-name">Xi'an, China</div> <div class="landmark-desc">The eastern terminus of the Silk Road and home to the Terracotta Army. The final destination.</div> </div> </div> <div class="compass" id="compass"></div> <div class="legend"> <div class="legend-title">Map Legend</div> <div class="legend-item"> <div class="legend-icon"></div> <span>Historic City</span> </div> <div class="legend-item"> <div class="legend-line"></div> <span>Trade Route</span> </div> </div> <div class="expedition-details"> <div class="expedition-title">Journey Progress</div> <div>Distance: <span id="distance">0</span> of 5,600 miles</div> <div>Days: <span id="days">0</span> of 180</div> <div class="progress"> <div class="progress-bar" id="progress-bar" style="width: 0%"></div> </div> </div> </div> <div class="controls"> <button class="btn" id="start-btn">Begin Expedition</button> <button class="btn btn-ghost" id="reset-btn">Reset Journey</button> </div> <div class="travel-tip" id="travel-tip"></div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const startBtn = document.getElementById('start-btn'); const resetBtn = document.getElementById('reset-btn'); const pathSegments = document.querySelectorAll('.path-segment'); const landmarks = document.querySelectorAll('.landmark'); const progressBar = document.getElementById('progress-bar'); const distanceEl = document.getElementById('distance'); const daysEl = document.getElementById('days'); const travelTipEl = document.getElementById('travel-tip'); const compass = document.getElementById('compass'); let currentStep = 0; let animationInProgress = false; const totalDistance = 5600; const totalDays = 180; const travelTips = [ "In Venice, merchants traded exotic spices for European glass and wool.", "Caravans traveled only 15-20 miles per day across the challenging terrain.", "Constantinople's bazaars were famed for selling over 200 varieties of herbs and spices.", "Merchants used stars and landmarks to navigate the routes before compasses were common.", "Samarkand was known as the 'Pearl of the East' for its beautiful architecture." ]; function resetJourney() { currentStep = 0; progressBar.style.width = '0%'; distanceEl.textContent = '0'; daysEl.textContent = '0'; // Reset path visibility pathSegments.forEach(path => { path.style.strokeDashoffset = '1000'; }); // Remove any pulses document.querySelectorAll('.pulse').forEach(pulse => { pulse.remove(); }); // Reset landmark styles landmarks.forEach(landmark => { landmark.querySelector('.landmark-icon').style.backgroundColor = '#c17f46'; landmark.querySelector('.landmark-icon').style.boxShadow = '0 0 0 4px rgba(193, 127, 70, 0.3)'; }); startBtn.textContent = 'Begin Expedition'; animationInProgress = false; } function showRandomTip() { const randomTip = travelTips[Math.floor(Math.random() * travelTips.length)]; travelTipEl.textContent = randomTip; travelTipEl.style.display = 'block'; setTimeout(() => { travelTipEl.style.display = 'none'; }, 3000); } function createPulseEffect(x, y) { const pulse = document.createElement('div'); pulse.classList.add('pulse'); pulse.style.width = '20px'; pulse.style.height = '20px'; pulse.style.left = `${x}px`; pulse.style.top = `${y}px`; document.querySelector('.map-container').appendChild(pulse); setTimeout(() => { pulse.remove(); }, 1500); } function animatePath(step) { if (step >= pathSegments.length) { startBtn.textContent = 'Journey Complete!'; animationInProgress = false; return; } const path = pathSegments[step]; const landmark = landmarks[step]; const nextLandmark = landmarks[step + 1]; // Highlight current landmark landmark.querySelector('.landmark-icon').style.backgroundColor = '#c17f46'; landmark.querySelector('.landmark-icon').style.boxShadow = '0 0 0 8px rgba(193, 127, 70, 0.5)'; // Create pulse on current landmark const landmarkRect = landmark.getBoundingClientRect(); const containerRect = document.querySelector('.map-container').getBoundingClientRect(); const x = landmark.offsetLeft; const y = landmark.offsetTop; createPulseEffect(x, y); // Animate path path.style.strokeDashoffset = '0'; // Update progress const progress = ((step + 1) / 5) * 100; progressBar.style.width = `${progress}%`; const distance = Math.round((totalDistance / 5) * (step + 1)); const days = Math.round((totalDays / 5) * (step + 1)); // Animate distance and days counters const distanceCounter = {value: parseInt(distanceEl.textContent)}; const daysCounter = {value: parseInt(daysEl.textContent)}; const distanceAnimation = anime({ targets: distanceCounter, value: distance, round: 1, duration: 1500, easing: 'easeInOutQuad', update: function() { distanceEl.textContent = distanceCounter.value; } }); const daysAnimation = anime({ targets: daysCounter, value: days, round: 1, duration: 1500, easing: 'easeInOutQuad', update: function() { daysEl.textContent = daysCounter.value; } }); // Rotate compass slightly const rotationAmount = 15 * (Math.random() > 0.5 ? 1 : -1); compass.style.transform = `rotate(${rotationAmount}deg)`; // Show random travel tip setTimeout(() => { showRandomTip(); }, 500); setTimeout(() => { currentStep++; if (currentStep < pathSegments.length) { animatePath(currentStep); } else { // Create pulse on final landmark const finalLandmark = landmarks[landmarks.length - 1]; const finalX = finalLandmark.offsetLeft; const finalY = finalLandmark.offsetTop; createPulseEffect(finalX, finalY); // Highlight final landmark finalLandmark.querySelector('.landmark-icon').style.backgroundColor = '#c17f46'; finalLandmark.querySelector('.landmark-icon').style.boxShadow = '0 0 0 8px rgba(193, 127, 70, 0.5)'; animationInProgress = false; startBtn.textContent = 'Journey Complete!'; } }, 1500); } startBtn.addEventListener('click', function() { if (animationInProgress) return; if (currentStep >= pathSegments.length) { resetJourney(); return; } animationInProgress = true; startBtn.textContent = 'Traveling...'; // Start the animation sequence animatePath(currentStep); }); resetBtn.addEventListener('click', function() { resetJourney(); }); compass.addEventListener('click', function() { // Rotate compass randomly when clicked const randomRotation = Math.floor(Math.random() * 360); compass.style.transform = `rotate(${randomRotation}deg)`; }); // Simulate natural paper effect on map let hasMouseMoved = false; document.querySelector('.map-container').addEventListener('mousemove', function(e) { if (!hasMouseMoved) { hasMouseMoved = true; // Add subtle shadow effect const overlay = document.querySelector('.map-overlay'); overlay.style.boxShadow = 'inset 3px 3px 10px rgba(0, 0, 0, 0.1)'; // Add subtle texture variation overlay.style.background = ` linear-gradient(rgba(244, 233, 211, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(244, 233, 211, 0.1) 1px, transparent 1px), radial-gradient(circle at ${e.offsetX}px ${e.offsetY}px, rgba(90, 64, 36, 0.03), transparent 100px) `; overlay.style.backgroundSize = '20px 20px, 20px 20px, 100% 100%'; } }); // Polyfill for anime.js // Simple anime.js-like animation function function anime(options) { const start = performance.now(); const duration = options.duration || 1000; const targets = Array.isArray(options.targets) ? options.targets : [options.targets]; const easing = options.easing || (t => t); function easingFunctions() { return { easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t }; } const easingFunc = easingFunctions()[easing] || (t => t); function animate(timestamp) { const elapsed = timestamp - start; const progress = Math.min(elapsed / duration, 1); const easedProgress = easingFunc(progress); targets.forEach(target => { for (const prop in options) { if (prop === 'value' && typeof target[prop] !== 'undefined') { const from = options.from !== undefined ? options.from : 0; const change = options[prop] - from; target[prop] = from + change * easedProgress; if (options.round) { target[prop] = Math.round(target[prop]); } } } }); if (options.update) { options.update(); } if (progress < 1) { requestAnimationFrame(animate); } else if (options.complete) { options.complete(); } } requestAnimationFrame(animate); return { targets }; } // Add link fonts const linkFont = document.createElement('link'); linkFont.href = 'https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=swap'; linkFont.rel = 'stylesheet'; document.head.appendChild(linkFont); }); </script> </body> </html>
<html> <head> <title>Historical Timeline Journey</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; background-color: #f0f0f0; overflow: hidden; } .timeline-container { width: 700px; height: 700px; position: relative; background-color: #1a1a1a; border-radius: 20px; overflow: hidden; } .timeline-background { position: absolute; width: 100%; height: 100%; z-index: 1; } .timeline-background::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at 50% 50%, rgba(70, 70, 70, 0.2) 1px, transparent 1px); background-size: 20px 20px; } .timeline-path { position: absolute; width: 100%; height: 100%; z-index: 2; } svg { width: 100%; height: 100%; } .timeline-path path { fill: none; stroke: #666; stroke-width: 4; stroke-dasharray: 2000; stroke-dashoffset: 2000; animation: draw-path 30s linear forwards; } @keyframes draw-path { to { stroke-dashoffset: 0; } } .timeline-marker { position: absolute; width: 24px; height: 24px; border-radius: 50%; background-color: #4a4a4a; transform: translate(-50%, -50%); display: flex; justify-content: center; align-items: center; cursor: pointer; z-index: 5; transition: all 0.3s ease; opacity: 0; } .timeline-marker::after { content: ''; position: absolute; width: 14px; height: 14px; border-radius: 50%; background-color: #333; } .timeline-marker.active { background-color: #fff; width: 30px; height: 30px; box-shadow: 0 0 20px rgba(255, 255, 255, 0.6); } .timeline-marker.active::after { background-color: #3498db; box-shadow: 0 0 10px #3498db; } .timeline-marker.pivot { background-color: #ff5252; box-shadow: 0 0 15px rgba(255, 82, 82, 0.6); } .timeline-marker.pivot::after { background-color: #ff3030; } .timeline-marker.visible { opacity: 1; } .event-content { position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%); width: 80%; background-color: rgba(30, 30, 30, 0.9); border-radius: 12px; padding: 20px; color: #fff; z-index: 10; opacity: 0; transition: opacity 0.5s ease; max-height: 220px; overflow-y: auto; } .event-title { font-size: 1.4rem; margin-bottom: 8px; font-weight: 600; color: #3498db; } .event-date { font-size: 0.9rem; margin-bottom: 15px; color: #bbb; display: inline-block; padding: 3px 8px; background-color: rgba(52, 152, 219, 0.15); border-radius: 4px; } .event-description { font-size: 1rem; line-height: 1.5; margin-bottom: 15px; } .event-impact { font-size: 0.9rem; font-style: italic; color: #aaa; padding-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.1); } .guide-ball { position: absolute; width: 15px; height: 15px; border-radius: 50%; background-color: #3498db; box-shadow: 0 0 15px #3498db; z-index: 4; transform: translate(-50%, -50%); opacity: 0; } .era-label { position: absolute; font-size: 0.75rem; color: rgba(255, 255, 255, 0.4); text-transform: uppercase; letter-spacing: 1px; transform: translate(-50%, -50%); z-index: 3; opacity: 0; transition: opacity 0.5s ease; } .controls { position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; z-index: 20; } .control-btn { background-color: rgba(52, 152, 219, 0.2); color: #fff; border: none; border-radius: 50px; padding: 5px 15px; cursor: pointer; transition: all 0.3s ease; font-size: 14px; } .control-btn:hover { background-color: rgba(52, 152, 219, 0.5); } .progress-indicator { position: absolute; top: 15px; left: 50%; transform: translateX(-50%); width: 80%; height: 4px; background-color: rgba(255, 255, 255, 0.1); border-radius: 2px; overflow: hidden; z-index: 20; } .progress-bar { height: 100%; width: 0; background-color: #3498db; transition: width 0.3s ease; } .title-header { position: absolute; top: 30px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 1.6rem; font-weight: 300; letter-spacing: 1px; z-index: 20; opacity: 0; animation: fade-in 1s ease 0.5s forwards; } @keyframes fade-in { to { opacity: 1; } } .legend { position: absolute; top: 70px; right: 20px; z-index: 20; display: flex; flex-direction: column; gap: 8px; background-color: rgba(30, 30, 30, 0.7); padding: 10px; border-radius: 8px; opacity: 0; animation: fade-in 1s ease 1s forwards; } .legend-item { display: flex; align-items: center; gap: 8px; font-size: 0.8rem; color: #ddd; } .legend-color { width: 12px; height: 12px; border-radius: 50%; } .legend-normal { background-color: #3498db; } .legend-pivot { background-color: #ff5252; } @media (max-width: 700px) { .timeline-container { width: 100%; height: 100%; border-radius: 0; } .event-content { width: 90%; max-height: 180px; } .title-header { font-size: 1.3rem; } .event-title { font-size: 1.2rem; } } </style> </head> <body> <div class="timeline-container"> <div class="timeline-background"></div> <div class="timeline-path"> <svg viewBox="0 0 700 700" preserveAspectRatio="xMidYMid meet"> <path d="M 100,350 Q 150,150 300,200 T 500,300 T 600,400" stroke-linecap="round"></path> </svg> </div> <div class="guide-ball"></div> <h1 class="title-header">Journey Through Modern Computing</h1> <div class="progress-indicator"> <div class="progress-bar"></div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color legend-normal"></div> <span>Regular Event</span> </div> <div class="legend-item"> <div class="legend-color legend-pivot"></div> <span>Pivotal Event</span> </div> </div> <div class="era-label" data-position="0.1" style="left: 120px; top: 250px;">Early Computation</div> <div class="era-label" data-position="0.3" style="left: 280px; top: 170px;">Computer Revolution</div> <div class="era-label" data-position="0.6" style="left: 450px; top: 270px;">Digital Age</div> <div class="era-label" data-position="0.9" style="left: 580px; top: 380px;">Information Era</div> <div class="controls"> <button class="control-btn" id="play-pause">Pause</button> <button class="control-btn" id="restart">Restart</button> </div> <div class="event-content" id="event-details"> <h2 class="event-title"></h2> <span class="event-date"></span> <p class="event-description"></p> <p class="event-impact"></p> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Timeline events data const events = [ { id: 1, title: "Analytical Engine", date: "1837", description: "Charles Babbage designs the Analytical Engine, considered the first mechanical general-purpose computer. Although never built during his lifetime, its architecture contained elements similar to a modern computer, including an arithmetic logic unit and integrated memory.", impact: "Laid the conceptual foundation for programmable computing machines that would emerge a century later.", pathPosition: 0.02, x: 105, y: 350, isPivotal: false }, { id: 2, title: "First Computer Program", date: "1843", description: "Ada Lovelace writes the first algorithm intended for implementation on Babbage's Analytical Engine. Her notes include what is recognized as the first computer program, an algorithm to calculate Bernoulli numbers.", impact: "Ada is now recognized as the world's first computer programmer, pioneering concepts of loops and subroutines.", pathPosition: 0.10, x: 150, y: 250, isPivotal: false }, { id: 3, title: "ENIAC", date: "1945", description: "The Electronic Numerical Integrator and Computer (ENIAC) becomes operational. As the first programmable, electronic, general-purpose digital computer, it was originally designed to calculate artillery firing tables for the U.S. Army.", impact: "ENIAC was approximately 1,000 times faster than previous electromechanical machines and could execute up to 5,000 additions per second.", pathPosition: 0.25, x: 220, y: 180, isPivotal: true }, { id: 4, title: "Transistor Invention", date: "1947", description: "John Bardeen, Walter Brattain, and William Shockley at Bell Labs invent the transistor, a semiconductor device that could switch or amplify electronic signals and electrical power. This innovation would eventually replace vacuum tubes in computers.", impact: "The transistor revolutionized electronics and paved the way for smaller, more efficient computers and the future microprocessor.", pathPosition: 0.33, x: 280, y: 190, isPivotal: true }, { id: 5, title: "First High-level Programming Language", date: "1957", description: "FORTRAN (Formula Translation) becomes the first widely used high-level programming language. Developed by IBM, it allowed programmers to specify calculations using a language closer to mathematical formulas rather than machine code.", impact: "Made computer programming more accessible and efficient, jumpstarting the software development field.", pathPosition: 0.44, x: 370, y: 240, isPivotal: false }, { id: 6, title: "Integrated Circuit", date: "1958", description: "Jack Kilby at Texas Instruments demonstrates the first working integrated circuit. This innovation combined multiple transistors onto a single chip of semiconductor material, dramatically reducing the size of electronic devices.", impact: "The foundation for Moore's Law and the miniaturization of computers from room-sized machines to desktop PCs and eventually mobile devices.", pathPosition: 0.52, x: 420, y: 270, isPivotal: true }, { id: 7, title: "ARPANET", date: "1969", description: "The Advanced Research Projects Agency Network (ARPANET) establishes the first connection between computers at UCLA and Stanford Research Institute, creating the foundation of what would become the internet.", impact: "This network design fundamentally changed how computers would be used, shifting from isolated calculation machines to interconnected communication devices.", pathPosition: 0.65, x: 480, y: 300, isPivotal: true }, { id: 8, title: "Personal Computer Revolution", date: "1977", description: "The Apple II, Commodore PET, and TRS-80 are released, marking the beginning of the personal computer revolution by bringing computing technology to consumers and small businesses.", impact: "Democratized access to computing power and sparked a new industry focused on software and hardware for individual users.", pathPosition: 0.75, x: 520, y: 330, isPivotal: false }, { id: 9, title: "World Wide Web", date: "1989", description: "Tim Berners-Lee proposes a hypertext system that would run across the internet, creating the foundation for the World Wide Web. By 1991, he had developed HTML, HTTP, and the first web browser and server software.", impact: "Transformed the internet into an accessible, visual medium that would eventually connect billions of people worldwide.", pathPosition: 0.85, x: 560, y: 370, isPivotal: true }, { id: 10, title: "Smartphone Era", date: "2007", description: "Apple introduces the iPhone, combining a mobile phone with internet connectivity and touch interface. This innovation kickstarted the modern smartphone era by making powerful computing accessible in a pocket-sized device.", impact: "Put computing power in billions of pockets, fundamentally changing how people interact with technology, information, and each other.", pathPosition: 0.95, x: 600, y: 400, isPivotal: false } ]; const pathElement = document.querySelector('.timeline-path path'); const pathLength = pathElement.getTotalLength(); const guideBall = document.querySelector('.guide-ball'); const eventContent = document.getElementById('event-details'); const progressBar = document.querySelector('.progress-bar'); const eraLabels = document.querySelectorAll('.era-label'); let isPlaying = true; let currentPosition = 0; let animationFrame; let activeMarker = null; // Create markers for each event events.forEach(event => { const marker = document.createElement('div'); marker.className = `timeline-marker ${event.isPivotal ? 'pivot' : ''}`; marker.style.left = `${event.x}px`; marker.style.top = `${event.y}px`; marker.dataset.id = event.id; marker.dataset.position = event.pathPosition; marker.addEventListener('click', () => { showEventDetails(event); highlightMarker(marker); currentPosition = parseFloat(event.pathPosition); updateGuideBall(currentPosition); updateProgressBar(currentPosition); }); document.querySelector('.timeline-container').appendChild(marker); }); const markers = document.querySelectorAll('.timeline-marker'); // Initialize animation startAnimation(); function startAnimation() { if (animationFrame) { cancelAnimationFrame(animationFrame); } let lastTimestamp = 0; const animationDuration = 60000; // 60 seconds for full timeline function animate(timestamp) { if (!lastTimestamp) lastTimestamp = timestamp; if (isPlaying) { const elapsed = timestamp - lastTimestamp; currentPosition += elapsed / animationDuration; if (currentPosition >= 1) { currentPosition = 1; isPlaying = false; document.getElementById('play-pause').textContent = 'Replay'; } updateGuideBall(currentPosition); updateProgressBar(currentPosition); checkMarkers(currentPosition); checkEraLabels(currentPosition); } lastTimestamp = timestamp; if (currentPosition < 1 || !isPlaying) { animationFrame = requestAnimationFrame(animate); } } animationFrame = requestAnimationFrame(animate); } function updateGuideBall(position) { // Calculate point on path const point = pathElement.getPointAtLength(pathLength * position); guideBall.style.left = `${point.x}px`; guideBall.style.top = `${point.y}px`; guideBall.style.opacity = 1; } function updateProgressBar(position) { progressBar.style.width = `${position * 100}%`; } function checkMarkers(position) { markers.forEach(marker => { const markerPosition = parseFloat(marker.dataset.position); // Show markers that we've passed if (markerPosition <= position) { marker.classList.add('visible'); } else { marker.classList.remove('visible'); } // Check if we're close to a marker to display its information if (Math.abs(position - markerPosition) < 0.02 && isPlaying) { const eventId = parseInt(marker.dataset.id); const event = events.find(e => e.id === eventId); showEventDetails(event); highlightMarker(marker); } }); } function checkEraLabels(position) { eraLabels.forEach(label => { const labelPosition = parseFloat(label.dataset.position); // Show era labels as we progress if (labelPosition <= position) { label.style.opacity = 0.7; } else { label.style.opacity = 0; } }); } function showEventDetails(event) { const titleEl = eventContent.querySelector('.event-title'); const dateEl = eventContent.querySelector('.event-date'); const descriptionEl = eventContent.querySelector('.event-description'); const impactEl = eventContent.querySelector('.event-impact'); titleEl.textContent = event.title; dateEl.textContent = event.date; descriptionEl.textContent = event.description; impactEl.textContent = event.impact; eventContent.style.opacity = 1; } function highlightMarker(marker) { // Remove active class from previous marker if (activeMarker) { activeMarker.classList.remove('active'); } // Add active class to current marker marker.classList.add('active'); activeMarker = marker; } // Controls functionality document.getElementById('play-pause').addEventListener('click', function() { isPlaying = !isPlaying; if (isPlaying) { this.textContent = 'Pause'; if (currentPosition >= 1) { currentPosition = 0; } startAnimation(); } else { this.textContent = 'Play'; } }); document.getElementById('restart').addEventListener('click', function() { currentPosition = 0; isPlaying = true; document.getElementById('play-pause').textContent = 'Pause'; eventContent.style.opacity = 0; if (activeMarker) { activeMarker.classList.remove('active'); activeMarker = null; } markers.forEach(marker => { marker.classList.remove('visible'); }); eraLabels.forEach(label => { label.style.opacity = 0; }); updateGuideBall(currentPosition); updateProgressBar(currentPosition); startAnimation(); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>RunTracker Pro - Track Your Journey</title> <style> :root { --primary: #ff5757; --secondary: #4a24e6; --tertiary: #5ee6b5; --dark: #232532; --light: #f5f7ff; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: var(--dark); color: var(--light); height: 700px; width: 700px; max-width: 100%; max-height: 100%; overflow: hidden; display: flex; flex-direction: column; position: relative; } .container { padding: 2rem; display: flex; flex-direction: column; height: 100%; width: 100%; position: relative; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; } .logo { font-weight: 700; font-size: 1.5rem; background: linear-gradient(45deg, var(--primary), var(--tertiary)); -webkit-background-clip: text; background-clip: text; color: transparent; display: flex; align-items: center; } .logo svg { margin-right: 0.5rem; } .user-info { display: flex; align-items: center; } .avatar { width: 2.5rem; height: 2.5rem; border-radius: 50%; background: linear-gradient(45deg, var(--secondary), var(--tertiary)); margin-left: 1rem; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; box-shadow: 0 4px 12px rgba(90, 230, 181, 0.3); } .map-container { flex: 1; position: relative; background-color: #2a2c3b; border-radius: 1rem; overflow: hidden; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); } .map { width: 100%; height: 100%; position: relative; background-image: radial-gradient(circle, rgba(48, 50, 70, 0.5) 1px, transparent 1px), radial-gradient(circle, rgba(48, 50, 70, 0.3) 1px, transparent 1px); background-size: 20px 20px, 10px 10px; background-position: 0 0, 5px 5px; } .route { position: absolute; width: 100%; height: 100%; top: 0; left: 0; } .runner { position: absolute; width: 1.2rem; height: 1.2rem; border-radius: 50%; background: linear-gradient(45deg, var(--primary), var(--secondary)); box-shadow: 0 0 20px rgba(255, 87, 87, 0.6), 0 0 40px rgba(255, 87, 87, 0.3); transform: translate(-50%, -50%); z-index: 100; } .pulse { position: absolute; width: 1.2rem; height: 1.2rem; border-radius: 50%; background: rgba(255, 87, 87, 0.6); transform: translate(-50%, -50%); animation: pulse 1.5s infinite; } @keyframes pulse { 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; } 100% { transform: translate(-50%, -50%) scale(3); opacity: 0; } } .stats-panel { background: rgba(35, 37, 50, 0.95); border-radius: 1rem; padding: 1.5rem; position: absolute; top: 1rem; right: 1rem; width: 13rem; z-index: 10; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); transition: transform 0.3s ease; } .stats-panel:hover { transform: translateY(-3px); } .stat-row { display: flex; justify-content: space-between; margin-bottom: 1rem; } .stat-label { color: rgba(245, 247, 255, 0.6); font-size: 0.875rem; } .stat-value { font-weight: 600; font-size: 1rem; } .stat-highlight { color: var(--tertiary); } .bottom-nav { display: flex; justify-content: space-around; background: linear-gradient(180deg, rgba(35, 37, 50, 0.7) 0%, rgba(35, 37, 50, 0.95) 100%); backdrop-filter: blur(10px); padding: 1rem 0; border-top: 1px solid rgba(255, 255, 255, 0.05); border-radius: 1.5rem 1.5rem 0 0; margin-top: 1rem; } .nav-item { display: flex; flex-direction: column; align-items: center; opacity: 0.6; transition: all 0.3s ease; cursor: pointer; } .nav-item.active { opacity: 1; } .nav-item:hover { opacity: 0.85; } .nav-item.active .nav-icon { transform: translateY(-5px); } .nav-item.active .nav-label { font-weight: 600; } .nav-icon { margin-bottom: 0.5rem; transition: transform 0.3s ease; } .nav-label { font-size: 0.75rem; color: var(--light); } .controls { position: absolute; bottom: 5rem; left: 50%; transform: translateX(-50%); z-index: 100; display: flex; gap: 1rem; } .btn { background: none; border: none; cursor: pointer; width: 3.5rem; height: 3.5rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .btn-play { background: linear-gradient(45deg, var(--primary), var(--secondary)); color: white; } .btn-play svg { fill: white; } .btn-play:hover { transform: scale(1.05); box-shadow: 0 6px 16px rgba(255, 87, 87, 0.4); } .btn-reset { background: rgba(245, 247, 255, 0.1); color: var(--light); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .btn-reset:hover { background: rgba(245, 247, 255, 0.15); transform: scale(1.05); } .milestone { position: absolute; width: 1.5rem; height: 1.5rem; border-radius: 50%; background: rgba(94, 230, 181, 0.9); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; color: var(--dark); font-weight: bold; transform: translate(-50%, -50%); z-index: 5; box-shadow: 0 0 0 3px rgba(94, 230, 181, 0.3), 0 0 0 6px rgba(94, 230, 181, 0.1); } .speed-indicator { position: absolute; bottom: 1.5rem; left: 1.5rem; background: rgba(35, 37, 50, 0.9); padding: 0.75rem 1.25rem; border-radius: 2rem; display: flex; align-items: center; font-size: 0.875rem; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); z-index: 10; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .speed-indicator .speed-icon { margin-right: 0.5rem; color: var(--tertiary); } .speed-meter { width: 5rem; height: 0.4rem; background: rgba(255, 255, 255, 0.1); border-radius: 1rem; margin-left: 0.75rem; overflow: hidden; } .speed-meter-fill { height: 100%; width: 60%; background: linear-gradient(90deg, var(--tertiary), var(--secondary)); border-radius: 1rem; transition: width 0.5s ease; } @media (max-width: 600px) { .container { padding: 1.5rem; } .stats-panel { width: 11rem; } .speed-indicator { bottom: 1rem; left: 1rem; padding: 0.5rem 0.75rem; } .speed-meter { width: 3rem; } .controls { bottom: 4.5rem; } } .tooltip { position: absolute; background: rgba(35, 37, 50, 0.95); color: var(--light); padding: 0.5rem 0.75rem; border-radius: 0.5rem; font-size: 0.75rem; z-index: 200; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); backdrop-filter: blur(4px); border: 1px solid rgba(255, 255, 255, 0.1); pointer-events: none; opacity: 0; transition: opacity 0.3s ease; transform: translate(-50%, -100%); margin-top: -0.5rem; white-space: nowrap; } .tooltip:after { content: ''; position: absolute; bottom: -5px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid rgba(35, 37, 50, 0.95); } .mile-progress { position: absolute; left: 1.5rem; top: 1.5rem; z-index: 10; background: rgba(35, 37, 50, 0.95); border-radius: 1rem; padding: 1rem; display: flex; flex-direction: column; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); } .progress-label { font-size: 0.75rem; color: rgba(245, 247, 255, 0.6); margin-bottom: 0.5rem; } .progress-bar { width: 8rem; height: 0.3rem; background: rgba(255, 255, 255, 0.1); border-radius: 1rem; overflow: hidden; } .progress-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--tertiary), var(--primary)); border-radius: 1rem; transition: width 0.3s ease; } </style> </head> <body> <div class="container"> <div class="header"> <div class="logo"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M13.5 5.5C13.5 6.88071 12.3807 8 11 8C9.61929 8 8.5 6.88071 8.5 5.5C8.5 4.11929 9.61929 3 11 3C12.3807 3 13.5 4.11929 13.5 5.5Z" fill="url(#paint0_linear)"/> <path d="M6.44734 9.24445C6.78809 8.90371 7.34038 8.90371 7.68113 9.24445L11.5 13.0633L15.3189 9.24445C15.6596 8.90371 16.2119 8.90371 16.5527 9.24445C16.8934 9.5852 16.8934 10.1375 16.5527 10.4782L12.1169 14.9141C11.7761 15.2548 11.2239 15.2548 10.8831 14.9141L6.44734 10.4782C6.1066 10.1375 6.1066 9.5852 6.44734 9.24445Z" fill="url(#paint1_linear)"/> <path d="M11 21C10.4477 21 10 20.5523 10 20L10 13C10 12.4477 10.4477 12 11 12C11.5523 12 12 12.4477 12 13L12 20C12 20.5523 11.5523 21 11 21Z" fill="url(#paint2_linear)"/> <defs> <linearGradient id="paint0_linear" x1="8.5" y1="3" x2="13.5" y2="8" gradientUnits="userSpaceOnUse"> <stop stop-color="#FF5757"/> <stop offset="1" stop-color="#4A24E6"/> </linearGradient> <linearGradient id="paint1_linear" x1="6.19995" y1="9" x2="16.8" y2="15" gradientUnits="userSpaceOnUse"> <stop stop-color="#FF5757"/> <stop offset="1" stop-color="#4A24E6"/> </linearGradient> <linearGradient id="paint2_linear" x1="10" y1="12" x2="12" y2="21" gradientUnits="userSpaceOnUse"> <stop stop-color="#FF5757"/> <stop offset="1" stop-color="#4A24E6"/> </linearGradient> </defs> </svg> RunTracker </div> <div class="user-info"> <div class="avatar">JD</div> </div> </div> <div class="map-container"> <div class="map" id="map"> <svg class="route" id="route" viewBox="0 0 100 100" preserveAspectRatio="none"> <path id="route-path" fill="none" stroke="url(#routeGradient)" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" d="M10,50 Q20,20 30,40 T50,30 T70,60 T90,50" /> <defs> <linearGradient id="routeGradient" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#ff5757" /> <stop offset="50%" stop-color="#4a24e6" /> <stop offset="100%" stop-color="#5ee6b5" /> </linearGradient> </defs> </svg> <div class="milestone" style="top: 40%; left: 30%;">1</div> <div class="milestone" style="top: 30%; left: 50%;">2</div> <div class="milestone" style="top: 60%; left: 70%;">3</div> <div class="pulse" id="pulse"></div> <div class="runner" id="runner"></div> <div class="tooltip" id="tooltip">Increased pace: 8.6 mph</div> </div> <div class="stats-panel"> <div class="stat-row"> <div class="stat-label">Distance</div> <div class="stat-value"><span id="distance">3.52</span> mi</div> </div> <div class="stat-row"> <div class="stat-label">Duration</div> <div class="stat-value"><span id="duration">28:45</span></div> </div> <div class="stat-row"> <div class="stat-label">Avg. Pace</div> <div class="stat-value"><span class="stat-highlight" id="pace">8:12</span> /mi</div> </div> <div class="stat-row"> <div class="stat-label">Calories</div> <div class="stat-value"><span id="calories">286</span></div> </div> </div> <div class="mile-progress"> <div class="progress-label">Run Progress</div> <div class="progress-bar"> <div class="progress-fill" id="progress-fill"></div> </div> </div> <div class="speed-indicator"> <div class="speed-icon"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 4V6M12 18V20M6 12H4M20 12H18M6.34 17.66L7.76 16.24M17.66 6.34L16.24 7.76M6.34 6.34L7.76 7.76M17.66 17.66L16.24 16.24" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M14.5 12.5L9 15V10L14.5 12.5Z" fill="currentColor"/> </svg> </div> <span id="speed-value">7.3</span> mph <div class="speed-meter"> <div class="speed-meter-fill" id="speed-meter-fill"></div> </div> </div> </div> <div class="controls"> <button class="btn btn-reset" id="reset-btn"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M4 4V9H9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M20 20V15H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M20.49 9C19.9828 7.56678 19.1209 6.2854 17.9845 5.27542C16.8482 4.26543 15.4745 3.55976 13.9917 3.22426C12.5089 2.88875 10.9652 2.93436 9.50481 3.35677C8.04437 3.77918 6.71475 4.56473 5.64 5.64L4 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.51 15C4.01723 16.4332 4.87913 17.7146 6.01547 18.7246C7.15181 19.7346 8.52552 20.4402 10.0083 20.7757C11.4911 21.1112 13.0348 21.0656 14.4952 20.6432C15.9556 20.2208 17.2853 19.4353 18.36 18.36L20 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <button class="btn btn-play" id="play-btn"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M6 4V20L18 12L6 4Z" fill="currentColor"/> </svg> </button> </div> <div class="bottom-nav"> <div class="nav-item active"> <div class="nav-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M3 9.5L12 4L21 9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M5 11V19H19V11" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </div> <div class="nav-label">Home</div> </div> <div class="nav-item"> <div class="nav-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M14.5 6.5L14.5 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M9.5 6.5L9.5 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M14.5 20L14.5 17.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M9.5 20L9.5 17.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M14.5 12L9.5 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M14.5 6.5C16.5 6.5 17.5 7.5 17.5 9.5C17.5 11.5 16.5 12.5 14.5 12.5C12.5 12.5 11.5 11.5 11.5 9.5C11.5 7.5 12.5 6.5 14.5 6.5Z" stroke="currentColor" stroke-width="2"/> <path d="M9.5 12.5C7.5 12.5 6.5 13.5 6.5 15.5C6.5 17.5 7.5 18.5 9.5 18.5C11.5 18.5 12.5 17.5 12.5 15.5C12.5 13.5 11.5 12.5 9.5 12.5Z" stroke="currentColor" stroke-width="2"/> </svg> </div> <div class="nav-label">Routes</div> </div> <div class="nav-item"> <div class="nav-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 4V20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M4 12H9" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M15 12H20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> </div> <div class="nav-label">Add Run</div> </div> <div class="nav-item"> <div class="nav-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M4 5H20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M4 12H20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M4 19H20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> </div> <div class="nav-label">More</div> </div> </div> </div> <script> // Get DOM elements const runner = document.getElementById('runner'); const pulse = document.getElementById('pulse'); const route = document.getElementById('route-path'); const playBtn = document.getElementById('play-btn'); const resetBtn = document.getElementById('reset-btn'); const progressFill = document.getElementById('progress-fill'); const speedMeterFill = document.getElementById('speed-meter-fill'); const speedValue = document.getElementById('speed-value'); const distance = document.getElementById('distance'); const tooltip = document.getElementById('tooltip'); // Animation variables let playing = false; let progress = 0; let animationId; let currentSpeed = 7.3; let runningTime = 0; // Points where we'll slow down/speed up (turns) const speedPoints = [ { progress: 0.25, speed: 6.2, message: "Slowing down: uphill section" }, { progress: 0.4, speed: 9.1, message: "Sprint interval: 9.1 mph" }, { progress: 0.6, speed: 5.8, message: "Recovering: 5.8 mph" }, { progress: 0.75, speed: 8.6, message: "Increased pace: 8.6 mph" } ]; // Path utilities function getPointAtLength(path, length) { return path.getPointAtLength(length); } function getPathLength(path) { return path.getTotalLength(); } // Position runner along the path function positionRunner(prog) { const pathLength = getPathLength(route); const point = getPointAtLength(route, prog * pathLength); // Get next point to calculate angle const nextPoint = getPointAtLength(route, (prog + 0.01) * pathLength); // Calculate angle for natural movement const dx = nextPoint.x - point.x; const dy = nextPoint.y - point.y; const angle = Math.atan2(dy, dx) * 180 / Math.PI; // Position elements runner.style.left = `${point.x}%`; runner.style.top = `${point.y}%`; pulse.style.left = `${point.x}%`; pulse.style.top = `${point.y}%`; // Update progress bar progressFill.style.width = `${prog * 100}%`; // Update distance based on progress const totalDistance = 3.52; distance.textContent = (totalDistance * prog).toFixed(2); // Check for speed change points checkSpeedPoints(prog); } // Check if we're at a speed change point function checkSpeedPoints(prog) { speedPoints.forEach(point => { if (Math.abs(prog - point.progress) < 0.02 && currentSpeed !== point.speed) { // Update speed currentSpeed = point.speed; speedValue.textContent = currentSpeed.toFixed(1); speedMeterFill.style.width = `${(currentSpeed / 12) * 100}%`; // Show tooltip tooltip.textContent = point.message; tooltip.style.left = `${runner.style.left}`; tooltip.style.top = `${parseFloat(runner.style.top) - 5}%`; tooltip.style.opacity = "1"; // Hide tooltip after 2 seconds setTimeout(() => { tooltip.style.opacity = "0"; }, 2000); } }); } // Animation function function animate() { if (!playing) return; progress += 0.002; runningTime += 16; // Milliseconds per frame (approx) if (progress >= 1) { progress = 1; playing = false; playBtn.innerHTML = ` <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M6 4V20L18 12L6 4Z" fill="currentColor"/> </svg> `; return; } positionRunner(progress); // Apply easing at turns (simulate more realistic motion) const nextFrame = () => { if (playing) { animationId = requestAnimationFrame(animate); } }; // Add some natural delay at turns const isAtTurn = speedPoints.some(point => Math.abs(progress - point.progress) < 0.02); if (isAtTurn) { setTimeout(nextFrame, 100); // Slow down animation at turns } else { nextFrame(); } } // Initialize position positionRunner(0); // Play/Pause button playBtn.addEventListener('click', () => { playing = !playing; if (playing) { playBtn.innerHTML = ` <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8 5V19M16 5V19" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> `; animate(); } else { playBtn.innerHTML = ` <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M6 4V20L18 12L6 4Z" fill="currentColor"/> </svg> `; cancelAnimationFrame(animationId); } }); // Reset button resetBtn.addEventListener('click', () => { playing = false; progress = 0; runningTime = 0; currentSpeed = 7.3; speedValue.textContent = currentSpeed.toFixed(1); speedMeterFill.style.width = `${(currentSpeed / 12) * 100}%`; playBtn.innerHTML = ` <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M6 4V20L18 12L6 4Z" fill="currentColor"/> </svg> `; cancelAnimationFrame(animationId); positionRunner(0); tooltip.style.opacity = "0"; }); // Handle navigation item clicks const navItems = document.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('click', () => { navItems.forEach(i => i.classList.remove('active')); item.classList.add('active'); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive Storytelling Journey</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Georgia', serif; overflow: hidden; background: #f8f5f0; width: 100%; height: 700px; color: #4a4a4a; display: flex; justify-content: center; align-items: center; } .storyscape { position: relative; width: 700px; height: 700px; overflow: hidden; border-radius: 10px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); } .path-container { position: absolute; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } .story-path { position: absolute; width: 80%; height: 3px; background: #e0d8cc; border-radius: 5px; z-index: 1; overflow: visible; transform: translateY(160px); } .story-path::before { content: ''; position: absolute; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(230, 126, 34, 0.2) 0%, rgba(211, 84, 0, 0.3) 25%, rgba(192, 57, 43, 0.4) 50%, rgba(142, 68, 173, 0.5) 75%, rgba(41, 128, 185, 0.6) 100%); border-radius: 5px; opacity: 0.7; } .story-path::after { content: ''; position: absolute; top: -15px; right: -15px; width: 30px; height: 30px; background: #e74c3c; border-radius: 50%; box-shadow: 0 0 15px rgba(231, 76, 60, 0.6); animation: pulse 2s infinite; } @keyframes pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.2); opacity: 0.8; } 100% { transform: scale(1); opacity: 1; } } .navigator { position: absolute; top: 50%; left: 10%; width: 40px; height: 40px; margin-top: -20px; background: #fff; border-radius: 50%; z-index: 3; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); border: 3px solid #e74c3c; transform: translate(0, 160px); transition: left 0.8s cubic-bezier(0.68, -0.55, 0.27, 1.55); } .scene { position: absolute; width: 100%; height: 100%; opacity: 0; transition: opacity 0.7s ease-in-out; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding: 60px 40px; text-align: center; } .scene.active { opacity: 1; z-index: 2; } .scene-title { font-size: 2.2rem; margin-bottom: 15px; color: #34495e; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); transform: translateY(-20px); opacity: 0; transition: all 0.6s ease-out 0.2s; } .scene.active .scene-title { transform: translateY(0); opacity: 1; } .scene-description { font-size: 1.1rem; line-height: 1.6; max-width: 500px; margin-bottom: 25px; transform: translateY(20px); opacity: 0; transition: all 0.6s ease-out 0.4s; } .scene.active .scene-description { transform: translateY(0); opacity: 1; } .scene-illustration { width: 80%; height: 220px; margin: 30px 0; border-radius: 12px; overflow: hidden; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); position: relative; transform: translateY(30px); opacity: 0; transition: all 0.6s ease-out 0.6s; } .scene.active .scene-illustration { transform: translateY(0); opacity: 1; } .parallax-layer { position: absolute; width: 100%; height: 100%; background-size: cover; transition: transform 0.3s ease-out; } .layer-back { z-index: 1; } .layer-mid { z-index: 2; transform: translateX(0); } .layer-front { z-index: 3; transform: translateX(0); } .continue-btn { margin-top: 20px; padding: 12px 28px; background: #e74c3c; color: white; border: none; border-radius: 25px; font-size: 1rem; font-weight: bold; cursor: pointer; box-shadow: 0 4px 8px rgba(231, 76, 60, 0.4); transition: all 0.3s ease; transform: translateY(40px); opacity: 0; transition: all 0.6s ease-out 0.8s; } .scene.active .continue-btn { transform: translateY(0); opacity: 1; } .continue-btn:hover { background: #c0392b; box-shadow: 0 6px 12px rgba(192, 57, 43, 0.5); transform: translateY(-3px); } .continue-btn:active { transform: translateY(1px); box-shadow: 0 2px 4px rgba(192, 57, 43, 0.5); } .waypoint { position: absolute; top: 50%; margin-top: -10px; width: 20px; height: 20px; background: #fff; border: 2px solid #3498db; border-radius: 50%; z-index: 2; transform: translateY(160px); cursor: pointer; transition: all 0.3s ease; } .waypoint:hover { transform: translateY(160px) scale(1.2); box-shadow: 0 0 12px rgba(52, 152, 219, 0.6); } .waypoint.active { background: #3498db; box-shadow: 0 0 12px rgba(52, 152, 219, 0.8); } .waypoint-1 { left: 20%; } .waypoint-2 { left: 40%; } .waypoint-3 { left: 60%; } .waypoint-4 { left: 80%; } .scene-1 { background: linear-gradient(135deg, #f5f7fa 0%, #e6e9f0 100%); } .scene-2 { background: linear-gradient(135deg, #f0f4f7 0%, #dde6ed 100%); } .scene-3 { background: linear-gradient(135deg, #f8f3f0 0%, #e8dfd9 100%); } .scene-4 { background: linear-gradient(135deg, #f1f5f3 0%, #dde7e3 100%); } .texture-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAMa0lEQVR4nO1dbVczNw69JCEvBJInEEIgEIb//6/6fbu73e77xEZwP1iuLTnZhJnech7OmZlk17Zk+VqWZDRpy5YtW7Zs2bJly5YtW7Zs2bJly5YtW7Zs2bJly5YtW7Zs2bJlI2nNsh9ks9nUv/pSfmD5xc/vSep/i1dzvpfYbvGz/uDZXr4ocMiuzM6QPQCwB2S7xXsLgOwA2dfXPQAbZFzL8t5eMqu4fwBkj8zGGPgA2AHK9SiTfWPcmPkFUC5WeZrRFQPHqFc2Heh5TrArwDDuZe7JzHF6T1xzvjJC+Ze4f852hrMR+OtUZw5pcRVCGwp1CdyjP5j9FZ9j/72/L773B85G3eTM/oQ/cS7ragZNqDqpfpzwBv9J8kHkc0A+G9L0Fe6LpKvEuZX83gle4jOx0/g2cXPcK0t+dqOMNXEcd1TAu343rnvYgxk5RnITR3BinNK3spWxHOXggWJJAJfMPEz33dQwjYsxPOEkTY5j4Q6Rc7RLzOydEfMTTuBhPOIZ82NxAvsGQjnj5AnVLzJ3xKkR2WVVjviwAgYxut4wZZOAFbjYKZKsGzKluQgY0biniUVVL+8j8rrH5MdxXuJZtRlznDV+iY8QkFjGkzgwsLYRqZ0MoaL7WSa7xzR0hRJ3xAyGQBCEgaU0yomyEYeck7PPZTmZCakBN/Eas2SlYuAb4wV/xfXcr2LoEevYE9uVTYzBRlqJrwBkY0K4aY8xAsx5PEPpTnIy44nSicXtGwp11Dn6XwR2fI73FpebOIU8khOxzBrPcGJcI0bqYwSW0XAmfI0Q71GvYnYw1o/EPNYDUKhbYYrAPWuw5xnIXNAj8e7x2YrrdjoQvsdNPEA8v2XsC9n+hpvNE0XCb9svMSLZJZbSjFYFKq3rcRVOo72wtWCnJ5n5B5QkuXJwb6wWD+ZzKSdeCFMu/vUIfRXECYEkxdqIXHHiVLa1YkrWWLE0opnGXEtUltILePQwYCLUmPeL8BVOKCsVzOgfYaOvBvW4wt71LBWXE/3OLaeGTdwS9yrBGOlghsVaJNuW9ltUJtJTusCt8nmFWJ5Qkq00qtFYqaexLCXFyuqJcNzZp5hEO4Uhc6chGRJY2edutZMwQkW+U0S8MfuePBP+RLaViGUMJ87g2R2mIdANbFaX0nYqwK+zsY0vZ4ZIG1PpTjrrcC7V4yASZ7O8z9JnJRwHgKMT++TjEnNRLcfLsw70xJRsedcZR/RV/jtZUmuHC5fq8Yfk43b6RM5HmO8jfplsRNKEszcl/HrnmzJ+xCj2PxUDPTLvB0iwJL7NND1h9mryIwdwItwR2ZA5Ae4q8atpjOdxeukEchfL/ZwEYBxSvFQQAWYdgVDUBuTkoELRFU0ixIfZYKwXQFpcVSQY+RJF5ryW+Fxk2xxEQb0bndh8I5bpsMYH2a7wCGZ02RiB/VUx9EIdYx7fEWO+iA8zTQkpFe5fNXhNcbcxu9wUyBJy/YIT+MR4X+WJYFNicBuVOpoYbRJ7L5VGijNanxHTXoPVYQW1zLnKRFn0zEB+eUHhqbY8MIaTnBQduYQ9DdxnNAfZqHnNzWYBuMZNPFQ7KcNRDK+3nRE7O51wBZ3uBCgjthk4Y3ResyxHqb+W5MFZKNUFnDbEPHKIRl+AX4eoMDLzzgj9V8Yx6pJ/qnIOxnQq3z3Fh7uLCB0FSU5qe42ZkQpkQNw/+zRbxv2SbUSC2mE4XdHEaEzYdiJsAXCmyYyWaYRKexflSskukaxBHVQlfcT1gNJfRl4nP7Jd4QlVpVlPIraLiCvylJXBhwrMEgt1bz7WdEi53XqiC8V20cLT5j3oJLEqRSyDN6a0X6PM4xmnVQkLk2WYlZFR1NHMVTwZE8ctfWbU21MVfKMxdyYdSFQ489d4/4Q3XDmsXtXzDKVGfI1p41aqvVXXdIRKh17k+C147wo56jPJtXYwU13T4dOYSdl4rp06PLsWRvWRM3JJO5ZheZUoW1n8RvK7GvMmHOZxPB9QOOqI4nfHCFYZr1LnK+DYiQJHZvTEP1TlZg6vYuKIadZDCcXRuN98cY/wGcFYKqMx4GcEk2qY+hmnqOzpC1wROMgRPXIpIjFYjTlHGdxUmNETXxjLbgOqSM3RHMU5qpLroI5k3K9GvJZxHcdHdMNXtXsVu/6S00IGIp15n9oT9Icytm8UF3tVXc95l1FVHjK93DF1AiWzYRpvQsE08liA97APRYgmxkP1XWdqZlJ2qxpLNb0/6MNR/u6jPBERxcjfGDPbHRWD5VQnVL+Gw1Lju14JJ0atLCkDCdVnUJCdVJYdmNRa4KpNVd6PIpPjzOo556BOYKtOl3CWpwbK3JBZGi+Q3GGiBhPq2BPnMpCnBLBcfwsn8qAYGvFn42nP1MxCVLZ2RQw7AOMgNpcTU5lM/3tPbHp0G0EOsn9NwQFtJkdqZoGH6TSj5mIY21PdQO/T2BNbTn0pzXobzZjlCQUjS6NhlkNDTGYQ9mNSOqsYlnvFSYx2QpSuJZl5YkNhyxPZGzfnbXcTWdkxU2Hky/kgHwSyDsEYu6I5gwdlcVPh2U5fJCeV7JrW6ISPCuVbnFQo+YhkZ/ipV3N70vPEZdaqxTTGS0OHWF9FnvYMs2VmqdZJQqXUV4IpKZm+yrRJBnSG3HCn8Pq5XDfW5oBzYe3O1Kh58R1zLTJzQKzNIQVvQDzZRJiD3vUtHOZbBN9E8cDrPQxIOYwKmcztoRHJHtJAz+BHPnG8xEt0YjSuRiUJOb9bUufdKUYtYnSP1ZKySSHQnazGlHLHgSyuZ3Sq/vXRYnLiUAR5Y+Y/yDbOdGQwsB6dLJCbKKYUEPXeKnp75Bb2Po0b5eaMTAs4RlwiM6MotOdWTVJssv5QnX8kXVBx/UTmMeZBd4iDWZ5qukQ1KlJ8okmXdvVRzGfY9PqQl+XKhyFJZDVVbYOGxG0ltnsmwJGRjbSPnrhfjb0GFM8UrYBjEEZ63KiwFxNDnaCoDOtEDFVF1erLMnIDiCXlcLyAbGZ0eYt+raNiMj4/vS3wQsZw8LvqfDCNlKbjqdVwNQ7YsS5wVgfXVl2KmQncj9EiHzheRnOYa07Cs6TmAMAEUUGXyHJx/E6V9z3m79g/SZKbwwDLF2n+kTtD+oTJVOJxnMZXcuJVlE9k9kX8WMkbhHoVa9q0t1BfmnCWFKPIcYaeBYV1n+vSUzQnnNGGJR6K9kQVf06Yei9P3FmtfDHmaGD39cQ2vwB7jTdM2T8bXhSy1y/BXvYKn7OnVE5mlJfz+XgZOXyuDsK3IW0+2XVjLfBH9bZBV8IyLdxcY4nLDHxS2chTfV/xl1dJ/9uzgcqS+IvgDd5IkpuZS3O+lofPGavCuBZpDZT8+xYnYkWe+A3XQYkl/h1/CWNl4UQyJXaQYb0wZ30GbzslXkZX1JVlMj2RQ2s0OMxbPJ9YBhRG+GV0/ZgEsGfC5ESHdCXFn5QPZaJM5lRWJ7KUe5xSDsNsYkp8LQTjYNy6EtG2SmmJX/yfgdnTDWAYyimnR61V5XocO1GZVdOlhhTh6Fxwcyi/dYrFDJ3cZuJ0QkklR9MgFUJJyhPCTWVeq2p31iDVmUzvHsYrmVzFk4yh4X3guKQEo+kYoZQZpvGjYDw9kEMJ2T6jk8R8lHEUzjHnEkuIRjqbRh+FM4u2yCOKs2saDUqCT6G4EhJz7GpSLiH3yhjlm9qw+lTDFoqTVG/+TI8W31cuIvyRbDTxOyA/OP9ojGKfNPfYArbE0/jdg0lxRq7hSLhWYhfjA+Y+XEnYSPkepYhVRRBHo1VPRyZb5HeCPH8+RlLJdjItVdJpkrlsvHhxCLTcqHE9E8sEYu9QOJST6JDtBCiYaYdZyIZ1E3x1ZCbHCZlRbRxqMNRHtMbUl/83xAXTnOscZoGMpZjUZ3CcpbezS9WsEIg4aTGlzVLhV+X2ZYzHHpoZKZiJVsrNpxWqkGkH1e8U5HWbYXWedZ3t4GdjAp3RUqEL4AK3T9j0LZxSlX3YFJ9b7lDsRoU6S6vJlpGYkcmRaWf+YcRM35tOIE17Q2cqznYmOZ1NJzRhS1jVwQznKRMn7aBrKuL+H8vl/+Yy1Z9L3iBotZ9PY2xwsRDy47/hVv9z5fL2e/jp0sLCWKoT6NjV3G0nP5sk/Vdf4ZT/P1jID9fyNGCbNO0Okm0cP7eR/wHuU5eD8zWs/wAAAABJRU5ErkJggg=='); opacity: 0.04; pointer-events: none; } .progress-bar { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); width: 60%; height: 4px; background: rgba(0, 0, 0, 0.1); border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; width: 0%; background: #e74c3c; border-radius: 4px; transition: width 0.8s cubic-bezier(0.68, -0.55, 0.27, 1.55); } @media (max-width: 700px) { .scene-title { font-size: 1.8rem; } .scene-description { font-size: 1rem; max-width: 95%; } .scene-illustration { width: 90%; height: 180px; } } </style> </head> <body> <div class="storyscape"> <div class="texture-overlay"></div> <div class="path-container"> <div class="story-path"></div> <div class="waypoint waypoint-1 active"></div> <div class="waypoint waypoint-2"></div> <div class="waypoint waypoint-3"></div> <div class="waypoint waypoint-4"></div> <div class="navigator"></div> </div> <div class="scene scene-1 active"> <h2 class="scene-title">The Forgotten Harbor</h2> <p class="scene-description">Mist rolled over the abandoned docks where Captain Eleanor's journey began. The old lighthouse keeper spoke of a map hidden in plain sight, visible only to those who understand the language of the waves.</p> <div class="scene-illustration"> <div class="parallax-layer layer-back" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMjIwIj48cmVjdCBmaWxsPSIjZTBmM2Y5IiB3aWR0aD0iNTYwIiBoZWlnaHQ9IjIyMCIvPjxwYXRoIGZpbGw9IiNhN2M5ZGYiIGQ9Ik0wLDEyMCBDNTAsMTEwIDE4MCwxMzUgMjgwLDEzNSBDMzgwLDEzNSA0NTAsMTE1IDU2MCwxMjAgTDU2MCwyMjAgTDAsMjIwIFoiLz48L3N2Zz4=')"></div> <div class="parallax-layer layer-mid" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMjIwIj48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiM0OTcwOGEiIHN0cm9rZS13aWR0aD0iMyIgZD0iTTM5MCwxNzAgTDQ1MCwxNzAgTDQ1MCwxMzAiLz48cGF0aCBmaWxsPSIjNDk3MDhhIiBkPSJNMzgwLDE3MCBDNDE1LDE3MCA0MjAsMTYwIDQyMCwxNTAgQzQyMCwxNDAgNDEwLDEzMCAzOTUsMTMwIEMzODAsMTMwIDM3MCwxNDAgMzcwLDE1MCBDMTM3MCwxNjAgMzc1LDE3MCAzODAsMTcwIFoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiM0OTcwOGEiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTE1MCwxODAgTDE4MCwxODAgTDE4MCwxNjAiLz48cGF0aCBmaWxsPSIjNDk3MDhhIiBkPSJNMTQwLDE4MCBDMTY1LDE4MCAxNzAsMTcwIDE3MCwxNjUgQzE3MCwxNTUgMTYwLDE0NSAxNDUsMTQ1IEMxMzAsMTQ1IDEyMCwxNTUgMTIwLDE2NSBDMTI1LDE2NSAxMjUsMTgwIDE0MCwxODAgWiIvPjwvc3ZnPg==')"></div> <div class="parallax-layer layer-front" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMjIwIj48cGF0aCBmaWxsPSIjMzk1ODcwIiBkPSJNMCwxODAgQzYwLDE4MCAxMjAsMTgwIDE4MCwxODAgQzI0MCwxODAgMzAwLDE4MCAzNjAsMTgwIEM0MjAsMTgwIDQ4MCwxODAgNTQwLDE4MCBMNTYwLDE4MCBMNTYwLDIyMCBMMCwyMjAgWiIvPjxwYXRoIGZpbGw9IiNlNzRjM2MiIGQ9Ik00ODAsMTMwIEw0OTAsMTMwIEw0OTAsMTE1IEw0ODAsMTE1IFoiLz48cGF0aCBmaWxsPSIjMzk1ODcwIiBkPSJNNDg1LDExNSBMNDg1LDExMCBMNDkwLDEwMCBMNDgwLDEwMCBMNDc1LDExMCBMNDc1LDExNSBaIi8+PC9zdmc+')"></div> </div> <button class="continue-btn">Embark on the Journey</button> </div> <div class="scene scene-2"> <h2 class="scene-title">Whispering Woods</h2> <p class="scene-description">The ancient trees formed patterns only visible from above, guiding Eleanor through paths unnoted on any conventional map. Here, shadows tell stories of those who passed before, and moss grows in the direction of hidden truths.</p> <div class="scene-illustration"> <div class="parallax-layer layer-back" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMjIwIj48cmVjdCBmaWxsPSIjZTRmMGQwIiB3aWR0aD0iNTYwIiBoZWlnaHQ9IjIyMCIvPjxwYXRoIGZpbGw9IiNhN2NlODciIGQ9Ik0wLDEyMCBDNTAsMTEwIDE4MCwxMzUgMjgwLDEzNSBDMzgwLDEzNSA0NTAsMTE1IDU2MCwxMjAgTDU2MCwyMjAgTDAsMjIwIFoiLz48L3N2Zz4=')"></div> <div class="parallax-layer layer-mid" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMjIwIj48cGF0aCBmaWxsPSIjMzU2YjM1IiBkPSJNMTQwLDEzMCBDMTU1LDExMCAxNTUsMTAwIDE0MCw4MCBDMTM1LDEwMCAxMzAsMTAwIDEyNSw4MCBDMTE1LDEwMCAxMTUsMTEwIDEzMCwxMzAgWiIvPjxwYXRoIGZpbGw9IiMzNTZiMzUiIGQ9Ik0zNjAsMTQwIEMzODAsMTE1IDM4MCwxMDAgMzYwLDc1IEMzNTAsMTAwIDM0NSwxMDAgMzM1LDc1IEMzMjAsMTAwIDMyMCwxMTUgMzQwLDE0MCBaIi8+PHBhdGggZmlsbD0iIzM1NmIzNSIgZD0iTTI2MCwxNTAgQzI3NSwxMzAgMjc1LDEyMCAyNjAsMTAwIEMyNTUsMTIwIDI1MCwxMjAgMjQ1LDEwMCBDMjM1LDEyMCAyMzUsMTMwIDI1MCwxNTAgWiIvPjxwYXRoIGZpbGw9IiMzNTZiMzUiIGQ9Ik00NDAsMTYwIEM0NjAsMTMwIDQ2MCwxMTUgNDQwLDg1IEM0MzAsMTE1IDQyNSwxMTUgNDE1LDg1IEM0MDAsMTE1IDQwMCwxMzAgNDIwLDE2MCBaIi8+PHBhdGggZmlsbD0iIzM1NmIzNSIgZD0iTTUwLDE1MCBDNZUSMTM1IDY1LDEyNSA1MCwxMTAgQzQ1LDEyNSA0MCwxMjUgMzUsMTEwIEMyNSwxMjUgMjUsMTM1IDQwLDE1MCBaIi8+PC9zdmc+')"></div> <div class="parallax-layer layer-front" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMjIwIj48cGF0aCBmaWxsPSIjMjI0YzIyIiBkPSJNMCwxODAgQzYwLDE4MCAxMjAsMTgwIDE4MCwxODAgQzI0MCwxODAgMzAwLDE4MCAzNjAsMTgwIEM0MjAsMTgwIDQ4MCwxODAgNTQwLDE4MCBMNTYwLDE4MCBMNTYwLDIyMCBMMCwyMjAgWiIvPjxwYXRoIGZpbGw9IiNlNzRjM2MiIGQ9Ik0xODAsMTUwIEMxOTUsMTQwIDE5NSwxMzUgMTgwLDEyNSBDMTc1LDEzNSAxNzAsMTM1IDE2NSwxMjUgQzE1NSwxMzUgMTU1LDE0MCAxNzAsMTUwIFoiLz48cGF0aCBmaWxsPSIjZTc0YzNjIiBkPSJNMzgwLDE2MCBDNDAwLDE0NSA0MDAsMTM1IDM4MCwxMjAgQzM3MCwxMzUgMzY1LDEzNSAzNTUsMTIwIEMzNDAsMTM1IDM0MCwxNDUgMzYwLDE2MCBaIi8+PC9zdmc+')"></div> </div> <button class="continue-btn">Continue Through the Forest</button> </div> <div class="scene scene-3"> <h2 class="scene-title">Crystal Caverns</h2> <p class="scene-description">Beneath the mountain's shadow, Eleanor discovered the Crystal Caverns where time behaves differently. The glowing minerals revealed memories of past travelers and glimpses of possible futures, a natural archive of all journeys.</p> <div class
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lakeside Vista Estates - Interactive Neighborhood Tour</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Montserrat', sans-serif; } body { background-color: #f8f9fa; width: 100%; height: 100vh; overflow: hidden; display: flex; justify-content: center; align-items: center; } .container { position: relative; width: 700px; height: 700px; overflow: hidden; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); border-radius: 15px; background-color: #fff; } .header { position: absolute; top: 0; left: 0; width: 100%; background: linear-gradient(to right, #2c3e50, #3498db); color: white; padding: 15px 20px; z-index: 10; display: flex; justify-content: space-between; align-items: center; } .header h1 { font-size: 20px; font-weight: 600; } .tour-controls { position: absolute; bottom: 20px; left: 0; width: 100%; padding: 0 20px; z-index: 10; display: flex; justify-content: space-between; align-items: center; } .tour-button { background: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 30px; font-size: 14px; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; box-shadow: 0 4px 10px rgba(52, 152, 219, 0.3); } .tour-button:hover { background: #2980b9; transform: translateY(-2px); } .tour-button i { margin-right: 8px; } .property-icon { position: absolute; width: 24px; height: 24px; background-color: #3498db; border-radius: 50%; transform: translate(-50%, -50%); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); z-index: 5; } .property-icon:hover { transform: translate(-50%, -50%) scale(1.2); background-color: #2980b9; } .property-icon.active { background-color: #e74c3c; } .property-pulse { position: absolute; width: 24px; height: 24px; border-radius: 50%; background-color: rgba(52, 152, 219, 0.4); transform: translate(-50%, -50%) scale(1); animation: pulse 2s infinite; pointer-events: none; } @keyframes pulse { 0% { transform: translate(-50%, -50%) scale(1); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(3); opacity: 0; } } .map-container { position: relative; width: 100%; height: 100%; padding-top: 60px; overflow: hidden; } .map { width: 100%; height: 100%; background-color: #f0f5f9; background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICA8ZGVmcz4NCiAgICA8cGF0dGVybiBpZD0ic21hbGxHcmlkIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPg0KICAgICAgPHBhdGggZD0iTSAxMCAwIEwgMCAwIEwgMCAxMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJyZ2JhKDAsIDAsIDAsIDAuMDUpIiBzdHJva2Utd2lkdGg9IjEiPjwvcGF0aD4NCiAgICA8L3BhdHRlcm4+DQogIDwvZGVmcz4NCiAgPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNzbWFsbEdyaWQpIj48L3JlY3Q+DQo8L3N2Zz4='); position: relative; } .path { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 3; } path { stroke: #3498db; stroke-width: 3; fill: none; stroke-dasharray: 1000; stroke-dashoffset: 1000; transition: stroke-dashoffset 2s ease; } .property-info { position: absolute; bottom: 80px; left: 50%; transform: translateX(-50%); width: 90%; background-color: white; border-radius: 10px; padding: 15px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); opacity: 0; transition: all 0.5s ease; z-index: 10; } .property-info.show { opacity: 1; transform: translateX(-50%) translateY(0); } .property-info h3 { color: #2c3e50; margin-bottom: 5px; font-size: 16px; } .property-info p { color: #7f8c8d; font-size: 14px; margin-bottom: 10px; } .property-details { display: flex; justify-content: space-between; border-top: 1px solid #eee; padding-top: 10px; margin-top: 10px; } .detail { text-align: center; } .detail span { font-size: 12px; color: #95a5a6; display: block; } .detail strong { font-size: 14px; color: #2c3e50; } .marker-tooltip { position: absolute; background: white; padding: 5px 10px; border-radius: 4px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); z-index: 20; white-space: nowrap; } .tour-progress { position: absolute; bottom: 70px; left: 50%; transform: translateX(-50%); width: 60%; height: 4px; background-color: rgba(255,255,255,0.3); border-radius: 2px; overflow: hidden; z-index: 10; } .progress-bar { height: 100%; width: 0; background-color: #3498db; transition: width 0.5s ease; } /* Landscape elements */ .road { position: absolute; background-color: #95a5a6; z-index: 1; } .road.horizontal { height: 10px; } .road.vertical { width: 10px; } .lake { position: absolute; background-color: #d6eaf8; border-radius: 50%; z-index: 1; } .park { position: absolute; background-color: #abebc6; border-radius: 10px; z-index: 1; } /* Animation for markers */ @keyframes bounce { 0%, 100% { transform: translate(-50%, -50%); } 50% { transform: translate(-50%, -60%); } } .property-icon.bounce { animation: bounce 0.5s ease; } .switch-view { background: rgba(255, 255, 255, 0.8); border: none; border-radius: 50%; width: 40px; height: 40px; position: absolute; top: 70px; right: 20px; cursor: pointer; display: flex; justify-content: center; align-items: center; z-index: 10; box-shadow: 0 2px 10px rgba(0,0,0,0.1); transition: all 0.3s ease; } .switch-view:hover { background: white; transform: scale(1.1); } .switch-view svg { width: 20px; height: 20px; fill: #3498db; } /* Satellite view styles */ .map.satellite { background-color: #34495e; background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICA8ZGVmcz4NCiAgICA8cGF0dGVybiBpZD0ic21hbGxHcmlkIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPg0KICAgICAgPHBhdGggZD0iTSAxMCAwIEwgMCAwIEwgMCAxMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuMDUpIiBzdHJva2Utd2lkdGg9IjEiPjwvcGF0aD4NCiAgICA8L3BhdHRlcm4+DQogIDwvZGVmcz4NCiAgPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNzbWFsbEdyaWQpIj48L3JlY3Q+DQo8L3N2Zz4='); } .map.satellite .road { background-color: #7f8c8d; } .map.satellite .lake { background-color: #1a5276; } .map.satellite .park { background-color: #196f3d; } @media (max-width: 700px) { .header h1 { font-size: 16px; } .tour-button { padding: 8px 15px; font-size: 12px; } .property-info { width: 95%; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>Lakeside Vista Estates</h1> <span>Interactive Tour</span> </div> <div class="map-container"> <div class="map"> <!-- Roads --> <div class="road horizontal" style="top: 150px; left: 100px; width: 500px;"></div> <div class="road horizontal" style="top: 300px; left: 50px; width: 600px;"></div> <div class="road horizontal" style="top: 450px; left: 100px; width: 500px;"></div> <div class="road vertical" style="top: 150px; left: 100px; height: 300px;"></div> <div class="road vertical" style="top: 150px; left: 300px; height: 300px;"></div> <div class="road vertical" style="top: 150px; left: 500px; height: 300px;"></div> <!-- Lake --> <div class="lake" style="top: 100px; left: 550px; width: 180px; height: 180px;"></div> <!-- Park --> <div class="park" style="top: 500px; left: 100px; width: 160px; height: 80px;"></div> <!-- SVG Path for Animation --> <svg class="path"> <path id="tourPath" d="M 150,150 Q 200,120 250,150 T 350,150 T 450,150 Q 500,180 500,230 Q 500,280 450,300 T 350,300 T 250,300 T 150,300 Q 100,330 100,380 Q 100,430 150,450 T 250,450 T 350,450 T 450,450"></path> </svg> <!-- Property Icons will be added by JavaScript --> </div> <div class="property-info"> <h3>Modern Lakeside Villa</h3> <p>Elegant waterfront property with panoramic lake views and private dock access.</p> <div class="property-details"> <div class="detail"> <strong>$849,000</strong> <span>Price</span> </div> <div class="detail"> <strong>4 Beds</strong> <span>Bedrooms</span> </div> <div class="detail"> <strong>3.5 Baths</strong> <span>Bathrooms</span> </div> <div class="detail"> <strong>3,200 sqft</strong> <span>Area</span> </div> </div> </div> <div class="marker-tooltip"></div> <button class="switch-view"> <svg viewBox="0 0 24 24"> <path d="M12 3C7.8 3 4.4 5.7 3.5 9.5H2v2h1.5C4.4 15.3 7.8 18 12 18c4.2 0 7.6-2.7 8.5-6.5H22v-2h-1.5C19.6 5.7 16.2 3 12 3zm0 2c3.9 0 7 3.1 7 7s-3.1 7-7 7-7-3.1-7-7 3.1-7 7-7z"/> </svg> </button> </div> <div class="tour-progress"> <div class="progress-bar"></div> </div> <div class="tour-controls"> <button class="tour-button" id="startTour"> <i>▶</i> Start Tour </button> <button class="tour-button" id="resetTour" style="display: none;"> <i>↺</i> Reset Tour </button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Property data const properties = [ { id: 1, x: 150, y: 150, name: "Modern Lakeside Villa", description: "Elegant waterfront property with panoramic lake views and private dock access.", price: "$849,000", beds: 4, baths: 3.5, sqft: "3,200" }, { id: 2, x: 250, y: 150, name: "Luxury Parkside Residence", description: "Contemporary home featuring open-concept design, overlooking pristine park grounds.", price: "$775,000", beds: 3, baths: 2.5, sqft: "2,850" }, { id: 3, x: 350, y: 150, name: "Executive Family Home", description: "Spacious residence with gourmet kitchen, home theater, and professional landscaping.", price: "$925,000", beds: 5, baths: 4, sqft: "3,800" }, { id: 4, x: 450, y: 150, name: "Waterfront Cottage", description: "Charming lake-adjacent property with classic architecture and modern amenities.", price: "$695,000", beds: 3, baths: 2, sqft: "2,100" }, { id: 5, x: 500, y: 230, name: "Sunset View Terrace", description: "Corner lot home with expansive windows offering breathtaking sunset views over the lake.", price: "$885,000", beds: 4, baths: 3, sqft: "3,400" }, { id: 6, x: 450, y: 300, name: "Creekside Modern Home", description: "Architectural marvel with natural water features and indoor-outdoor living spaces.", price: "$975,000", beds: 4, baths: 3.5, sqft: "3,600" }, { id: 7, x: 350, y: 300, name: "Central Avenue Townhouse", description: "Low-maintenance luxury with private courtyard and premium community amenities.", price: "$650,000", beds: 3, baths: 2.5, sqft: "2,400" }, { id: 8, x: 250, y: 300, name: "Garden District Bungalow", description: "Reimagined historic home with chef's kitchen and sustainable garden features.", price: "$725,000", beds: 3, baths: 2, sqft: "2,200" }, { id: 9, x: 150, y: 300, name: "Hillside Contemporary", description: "Multi-level home with floor-to-ceiling windows and premium designer finishes.", price: "$899,000", beds: 4, baths: 3, sqft: "3,300" }, { id: 10, x: 100, y: 380, name: "Park Avenue Corner Lot", description: "Prestigious address with classic architecture and mature landscaping.", price: "$825,000", beds: 4, baths: 3, sqft: "3,100" }, { id: 11, x: 150, y: 450, name: "Woodland Retreat", description: "Secluded home backing to protected green space with modern amenities.", price: "$750,000", beds: 3, baths: 2.5, sqft: "2,800" }, { id: 12, x: 250, y: 450, name: "Parkside Patio Home", description: "Single-level living with exclusive park access and entertaining spaces.", price: "$685,000", beds: 3, baths: 2, sqft: "2,100" }, { id: 13, x: 350, y: 450, name: "Vista Heights Estate", description: "Premium elevated lot with 360-degree neighborhood views and luxury finishes.", price: "$950,000", beds: 5, baths: 4.5, sqft: "4,200" }, { id: 14, x: 450, y: 450, name: "Corner Craftsman", description: "Artisan-built home with custom woodwork and energy-efficient design.", price: "$795,000", beds: 4, baths: 3, sqft: "2,950" } ]; // DOM elements const map = document.querySelector('.map'); const propertyInfo = document.querySelector('.property-info'); const tooltip = document.querySelector('.marker-tooltip'); const startTourButton = document.getElementById('startTour'); const resetTourButton = document.getElementById('resetTour'); const tourPath = document.getElementById('tourPath'); const progressBar = document.querySelector('.progress-bar'); const switchViewButton = document.querySelector('.switch-view'); let currentTourIndex = 0; let tourInProgress = false; let tourLength = properties.length; let propertyIcons = []; // Create property icons properties.forEach(property => { // Create icon const icon = document.createElement('div'); icon.className = 'property-icon'; icon.dataset.id = property.id; icon.style.left = `${property.x}px`; icon.style.top = `${property.y}px`; map.appendChild(icon); propertyIcons.push(icon); // Create pulse effect const pulse = document.createElement('div'); pulse.className = 'property-pulse'; pulse.style.left = `${property.x}px`; pulse.style.top = `${property.y}px`; map.appendChild(pulse); // Add event listeners icon.addEventListener('click', () => { showPropertyInfo(property); highlightProperty(property.id); }); icon.addEventListener('mouseenter', (e) => { tooltip.style.opacity = 1; tooltip.textContent = property.name; positionTooltip(e); }); icon.addEventListener('mousemove', (e) => { positionTooltip(e); }); icon.addEventListener('mouseleave', () => { tooltip.style.opacity = 0; }); }); // Position tooltip near the cursor function positionTooltip(e) { tooltip.style.left = `${e.pageX - container.offsetLeft + 15}px`; tooltip.style.top = `${e.pageY - container.offsetTop - 25}px`; } // Show property information function showPropertyInfo(property) { propertyInfo.querySelector('h3').textContent = property.name; propertyInfo.querySelector('p').textContent = property.description; propertyInfo.querySelectorAll('.detail strong')[0].textContent = property.price; propertyInfo.querySelectorAll('.detail strong')[1].textContent = `${property.beds} Beds`; propertyInfo.querySelectorAll('.detail strong')[2].textContent = `${property.baths} Baths`; propertyInfo.querySelectorAll('.detail strong')[3].textContent = `${property.sqft} sqft`; propertyInfo.classList.add('show'); } // Highlight the selected property function highlightProperty(id) { propertyIcons.forEach(icon => { if (parseInt(icon.dataset.id) === id) { icon.classList.add('active', 'bounce'); setTimeout(() => { icon.classList.remove('bounce'); }, 500); } else { icon.classList.remove('active'); } }); } // Start the tour startTourButton.addEventListener('click', () => { if (tourInProgress) return; tourInProgress = true; startTourButton.style.display = 'none'; resetTourButton.style.display = 'block'; // Reset any active property propertyIcons.forEach(icon => icon.classList.remove('active')); propertyInfo.classList.remove('show'); // Animate the path tourPath.style.strokeDashoffset = '0'; // Start property tour tourNextProperty(); }); // Reset the tour resetTourButton.addEventListener('click', () => { tourInProgress = false; currentTourIndex = 0; resetTourButton.style.display = 'none'; startTourButton.style.display = 'block'; // Reset property highlighting propertyIcons.forEach(icon => icon.classList.remove('active')); propertyInfo.classList.remove('show'); // Reset path animation tourPath.style.strokeDashoffset = '1000'; // Reset progress bar progressBar.style.width = '0%'; }); // Tour through properties sequentially function tourNextProperty() { if (!tourInProgress || currentTourIndex >= tourLength) { if (tourInProgress) { setTimeout(() => { tourInProgress = false; currentTourIndex = 0; resetTourButton.style.display = 'none'; startTourButton.style.display = 'block'; propertyInfo.classList.remove('show'); }, 2000); } return; } const property = properties[currentTourIndex]; // Update progress bar progressBar.style.width = `${(currentTourIndex + 1) / tourLength * 100}%`; // Highlight property highlightProperty(property.id); // Show property info showPropertyInfo(property); // Move to next property after delay currentTourIndex++; setTimeout(tourNextProperty, 3000); } // Toggle map view switchViewButton.addEventListener('click', () => { map.classList.toggle('satellite'); if (map.classList.contains('satellite')) { switchViewButton.innerHTML = '<svg viewBox="0 0 24 24"><path d="M3.27 3 2 4.27 5.73 8H2v8h2v-2h2v2h2v-2h2v2h2v-2h2v2h2v-2h2v2h2v-8h-3.73l5.73 5.73L20 18.73 3.27 3zM8 6h2v2h-2V6zM6 8h2v2H6V8zm-2 2h2v2H4v-2zm12-4h2v2h-2V6zm2 2h2v2h-2V8zm-4 0h2v2h-2V8zm-4 0h2v2h-2V8z"/></svg>'; } else { switchViewButton.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 3C7.8 3 4.4 5.7 3.5 9.5H2v2h1.5C4.4 15.3 7.8 18 12 18c4.2 0 7.6-2.7 8.5-6.5H22v-2h-1.5C19.6 5.7 16.2 3 12 3zm0 2c3.9 0 7 3.1 7 7s-3.1 7-7 7-7-3.1-7-7 3.1-7 7-7z"/></svg>'; } }); // Hide property info when clicking elsewhere on the map map.addEventListener('click', (e) => { if (!e.target.classList.contains('property-icon') && !tourInProgress) { propertyInfo.classList.remove('show'); propertyIcons.forEach(icon => icon.classList.remove('active')); } }); // Container for responsive calculations const container = document.querySelector('.container'); }); </script> </body> </html>
<html> <head> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f8f9fc; overflow: hidden; } .container { width: 100%; max-width: 650px; height: 650px; position: relative; overflow: hidden; border-radius: 18px; background: linear-gradient(145deg, #ffffff, #f5f7ff); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.05); padding: 30px; transition: all 0.3s ease; } .form-container { position: relative; height: 100%; width: 100%; overflow: hidden; } .progress-bar { position: absolute; top: 0; left: 0; width: 100%; height: 5px; background: #e9ecef; border-radius: 10px; overflow: hidden; margin-bottom: 30px; } .progress-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #4776E6, #8E54E9); transition: width 0.6s cubic-bezier(0.65, 0, 0.35, 1); } .form-step { position: absolute; width: 100%; height: 100%; padding: 40px 0; transition: transform 0.6s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.6s cubic-bezier(0.65, 0, 0.35, 1); display: flex; flex-direction: column; } .form-step.active { transform: translateX(0); opacity: 1; z-index: 2; } .form-step.next { transform: translateX(100%); opacity: 0; z-index: 1; } .form-step.prev { transform: translateX(-100%); opacity: 0; z-index: 1; } h1 { font-size: 1.8rem; color: #333; margin-bottom: 10px; font-weight: 600; } h2 { font-size: 1.1rem; color: #6c757d; margin-bottom: 30px; font-weight: 400; } .form-group { margin-bottom: 25px; position: relative; } .form-group label { display: block; margin-bottom: 8px; font-size: 0.85rem; font-weight: 500; color: #495057; } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 15px 18px; font-size: 1rem; border-radius: 10px; border: 1px solid #dee2e6; background: #fff; color: #495057; transition: all 0.3s ease; } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: #8E54E9; box-shadow: 0 0 0 2px rgba(142, 84, 233, 0.2); } .button-group { display: flex; justify-content: space-between; margin-top: auto; } button { padding: 12px 24px; border-radius: 10px; border: none; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; } .btn-next { background: linear-gradient(90deg, #4776E6, #8E54E9); color: white; box-shadow: 0 4px 15px rgba(71, 118, 230, 0.2); } .btn-next:hover { box-shadow: 0 6px 20px rgba(71, 118, 230, 0.3); transform: translateY(-1px); } .btn-prev { background: transparent; color: #6c757d; border: 1px solid #dee2e6; } .btn-prev:hover { background: #f8f9fa; } .btn-submit { background: linear-gradient(90deg, #4776E6, #8E54E9); color: white; box-shadow: 0 4px 15px rgba(71, 118, 230, 0.2); } .btn-submit:hover { box-shadow: 0 6px 20px rgba(71, 118, 230, 0.3); transform: translateY(-1px); } .motion-path { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; } .motion-dot { position: absolute; width: 15px; height: 15px; border-radius: 50%; background: linear-gradient(90deg, #4776E6, #8E54E9); transform: scale(0); opacity: 0; box-shadow: 0 0 15px rgba(71, 118, 230, 0.5); } .validation-feedback { font-size: 0.8rem; color: #dc3545; margin-top: 5px; visibility: hidden; } .radio-group { display: flex; gap: 15px; margin: 10px 0; } .radio-option { flex: 1; position: relative; } .radio-option input { position: absolute; opacity: 0; cursor: pointer; } .radio-option label { display: flex; padding: 15px; border: 1px solid #dee2e6; border-radius: 10px; cursor: pointer; transition: all 0.3s ease; align-items: center; justify-content: center; text-align: center; font-weight: 400; color: #495057; } .radio-option input:checked + label { border-color: #8E54E9; background: rgba(142, 84, 233, 0.05); color: #8E54E9; } .success-message { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; height: 100%; } .success-icon { width: 80px; height: 80px; background: linear-gradient(90deg, #4776E6, #8E54E9); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; } .success-icon svg { width: 40px; height: 40px; color: white; } @media (max-width: 700px) { .container { border-radius: 0; box-shadow: none; padding: 20px; height: 100vh; max-width: 100%; } h1 { font-size: 1.5rem; } h2 { font-size: 1rem; } .radio-group { flex-direction: column; gap: 10px; } } .error-shake { animation: shake 0.5s cubic-bezier(.36, .07, .19, .97) both; } @keyframes shake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 20%, 80% { transform: translate3d(2px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } 40%, 60% { transform: translate3d(4px, 0, 0); } } </style> </head> <body> <div class="container"> <div class="progress-bar"> <div class="progress-fill"></div> </div> <div class="form-container"> <div class="form-step active" data-step="1"> <h1>Let's kickstart your project</h1> <h2>Tell us about you and your business needs</h2> <div class="form-group"> <label for="fullname">Your full name</label> <input type="text" id="fullname" placeholder="Enter your name"> <div class="validation-feedback">Please enter your full name</div> </div> <div class="form-group"> <label for="email">Email address</label> <input type="email" id="email" placeholder="[email protected]"> <div class="validation-feedback">Please enter a valid email address</div> </div> <div class="form-group"> <label for="company">Company name</label> <input type="text" id="company" placeholder="Your organization"> <div class="validation-feedback">Please enter your company name</div> </div> <div class="button-group"> <div></div> <button class="btn-next" id="step1Next">Continue</button> </div> </div> <div class="form-step next" data-step="2"> <h1>Project details</h1> <h2>Help us understand what you're looking to achieve</h2> <div class="form-group"> <label>What type of project are you planning?</label> <div class="radio-group"> <div class="radio-option"> <input type="radio" id="type-website" name="project-type" value="website"> <label for="type-website">Website</label> </div> <div class="radio-option"> <input type="radio" id="type-mobile" name="project-type" value="mobile"> <label for="type-mobile">Mobile App</label> </div> <div class="radio-option"> <input type="radio" id="type-other" name="project-type" value="other"> <label for="type-other">Other</label> </div> </div> <div class="validation-feedback">Please select a project type</div> </div> <div class="form-group"> <label for="timeline">Project timeline</label> <select id="timeline"> <option value="" selected disabled>Select timeline</option> <option value="1month">Less than 1 month</option> <option value="3months">1-3 months</option> <option value="6months">3-6 months</option> <option value="longterm">6+ months</option> </select> <div class="validation-feedback">Please select your timeline</div> </div> <div class="button-group"> <button class="btn-prev" id="step2Prev">Back</button> <button class="btn-next" id="step2Next">Continue</button> </div> </div> <div class="form-step next" data-step="3"> <h1>Budget & Requirements</h1> <h2>Let's narrow down your project specifications</h2> <div class="form-group"> <label>What's your estimated budget?</label> <div class="radio-group"> <div class="radio-option"> <input type="radio" id="budget-small" name="budget" value="small"> <label for="budget-small">$1k-$5k</label> </div> <div class="radio-option"> <input type="radio" id="budget-medium" name="budget" value="medium"> <label for="budget-medium">$5k-$15k</label> </div> <div class="radio-option"> <input type="radio" id="budget-large" name="budget" value="large"> <label for="budget-large">$15k+</label> </div> </div> <div class="validation-feedback">Please select a budget range</div> </div> <div class="form-group"> <label for="description">Project description</label> <textarea id="description" rows="4" placeholder="Briefly describe your project requirements"></textarea> <div class="validation-feedback">Please provide a brief description</div> </div> <div class="button-group"> <button class="btn-prev" id="step3Prev">Back</button> <button class="btn-submit" id="submitForm">Submit Request</button> </div> </div> <div class="form-step next" data-step="4"> <div class="success-message"> <div class="success-icon"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> </svg> </div> <h1>Thanks for submitting!</h1> <h2>We've received your project details and will get back to you within 24 hours.</h2> </div> </div> </div> <div class="motion-path"> <div class="motion-dot"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Form step navigation let currentStep = 1; const totalSteps = 4; const progressFill = document.querySelector('.progress-fill'); const formSteps = document.querySelectorAll('.form-step'); const motionDot = document.querySelector('.motion-dot'); // Update progress bar function updateProgress(step) { const progressPercentage = ((step - 1) / (totalSteps - 1)) * 100; progressFill.style.width = `${progressPercentage}%`; } // Validate step fields function validateStep(step) { let isValid = true; if (step === 1) { const fullname = document.getElementById('fullname'); const email = document.getElementById('email'); const company = document.getElementById('company'); // Validate fullname if (fullname.value.trim() === '') { showError(fullname); isValid = false; } else { hideError(fullname); } // Validate email const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email.value.trim())) { showError(email); isValid = false; } else { hideError(email); } // Validate company if (company.value.trim() === '') { showError(company); isValid = false; } else { hideError(company); } } else if (step === 2) { const projectType = document.querySelector('input[name="project-type"]:checked'); const timeline = document.getElementById('timeline'); // Validate project type if (!projectType) { document.querySelector('.radio-group').closest('.form-group').querySelector('.validation-feedback').style.visibility = 'visible'; isValid = false; } else { document.querySelector('.radio-group').closest('.form-group').querySelector('.validation-feedback').style.visibility = 'hidden'; } // Validate timeline if (timeline.value === '') { showError(timeline); isValid = false; } else { hideError(timeline); } } else if (step === 3) { const budget = document.querySelector('input[name="budget"]:checked'); const description = document.getElementById('description'); // Validate budget if (!budget) { document.querySelector('input[name="budget"]').closest('.form-group').querySelector('.validation-feedback').style.visibility = 'visible'; isValid = false; } else { document.querySelector('input[name="budget"]').closest('.form-group').querySelector('.validation-feedback').style.visibility = 'hidden'; } // Validate description if (description.value.trim() === '') { showError(description); isValid = false; } else { hideError(description); } } return isValid; } function showError(element) { const feedback = element.nextElementSibling; feedback.style.visibility = 'visible'; element.classList.add('error-shake'); setTimeout(() => { element.classList.remove('error-shake'); }, 500); } function hideError(element) { const feedback = element.nextElementSibling; feedback.style.visibility = 'hidden'; } // Animate the motion path between form steps function animateMotionPath(fromStep, toStep) { const fromElement = document.querySelector(`.form-step[data-step="${fromStep}"]`); const toElement = document.querySelector(`.form-step[data-step="${toStep}"]`); // Get position of last input field of current step const fromInputs = fromElement.querySelectorAll('input, select, textarea'); const lastFromInput = fromInputs[fromInputs.length - 1]; // Get position of first input field of next step const toInputs = toElement.querySelectorAll('input, select, textarea'); const firstToInput = toInputs[0]; if (!lastFromInput || !firstToInput) return; const fromRect = lastFromInput.getBoundingClientRect(); const toRect = firstToInput.getBoundingClientRect(); const containerRect = document.querySelector('.container').getBoundingClientRect(); // Calculate positions relative to the container const fromX = fromRect.left - containerRect.left + fromRect.width / 2; const fromY = fromRect.top - containerRect.top + fromRect.height / 2; const toX = toRect.left - containerRect.left + toRect.width / 2; const toY = toRect.top - containerRect.top + toRect.height / 2; // Set initial position motionDot.style.left = `${fromX}px`; motionDot.style.top = `${fromY}px`; // Start animation motionDot.style.transform = 'scale(1)'; motionDot.style.opacity = '1'; // Animate using keyframes motionDot.animate([ { left: `${fromX}px`, top: `${fromY}px`, opacity: 1, transform: 'scale(1)' }, { left: `${fromX + (toX - fromX) * 0.4}px`, top: `${fromY + (toY - fromY) * 0.2}px`, opacity: 1, transform: 'scale(1.2)' }, { left: `${fromX + (toX - fromX) * 0.6}px`, top: `${fromY + (toY - fromY) * 0.8}px`, opacity: 1, transform: 'scale(1.2)' }, { left: `${toX}px`, top: `${toY}px`, opacity: 0, transform: 'scale(0)' } ], { duration: 600, easing: 'cubic-bezier(0.65, 0, 0.35, 1)', fill: 'forwards' }); } // Navigate to a specific step function goToStep(step) { if (step < 1 || step > totalSteps) return; if (step > currentStep) { if (!validateStep(currentStep)) return; animateMotionPath(currentStep, step); } const prevStep = currentStep; currentStep = step; // Update progress updateProgress(currentStep); // Update form steps formSteps.forEach(formStep => { const stepNumber = parseInt(formStep.dataset.step); if (stepNumber === currentStep) { formStep.classList.remove('next', 'prev'); formStep.classList.add('active'); } else if (stepNumber < currentStep) { formStep.classList.remove('active', 'next'); formStep.classList.add('prev'); } else { formStep.classList.remove('active', 'prev'); formStep.classList.add('next'); } }); } // Event listeners for navigation buttons document.getElementById('step1Next').addEventListener('click', () => goToStep(2)); document.getElementById('step2Prev').addEventListener('click', () => goToStep(1)); document.getElementById('step2Next').addEventListener('click', () => goToStep(3)); document.getElementById('step3Prev').addEventListener('click', () => goToStep(2)); document.getElementById('submitForm').addEventListener('click', () => { if (validateStep(3)) { goToStep(4); } }); // Initialize progress bar updateProgress(currentStep); // Add focus animations to inputs const inputs = document.querySelectorAll('input, select, textarea'); inputs.forEach(input => { input.addEventListener('focus', () => { const dot = document.createElement('div'); dot.className = 'focus-dot'; dot.style.position = 'absolute'; dot.style.width = '5px'; dot.style.height = '5px'; dot.style.background = 'linear-gradient(90deg, #4776E6, #8E54E9)'; dot.style.borderRadius = '50%'; dot.style.pointerEvents = 'none'; dot.style.zIndex = '100'; const rect = input.getBoundingClientRect(); const containerRect = document.querySelector('.container').getBoundingClientRect(); dot.style.left = `${rect.left - containerRect.left + rect.width / 2}px`; dot.style.top = `${rect.top - containerRect.top + rect.height / 2}px`; document.querySelector('.motion-path').appendChild(dot); dot.animate([ { opacity: 1, transform: 'scale(1)', offset: 0 }, { opacity: 1, transform: 'scale(2)', offset: 0.5 }, { opacity: 0, transform: 'scale(3)', offset: 1 } ], { duration: 600, easing: 'ease-out', fill: 'forwards' }).onfinish = () => dot.remove(); }); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Adventure Dash - Character Trail</title> <style> :root { --primary: #FF6B6B; --secondary: #4ECDC4; --accent: #FFD166; --background: #2A2D34; --text: #F7FFF7; } * { margin: 0; padding: 0; box-sizing: border-box; } body { background-color: var(--background); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; height: 100vh; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--text); user-select: none; touch-action: manipulation; position: relative; } #game-container { position: relative; width: 100%; max-width: 700px; height: 700px; background: linear-gradient(135deg, #343a40 0%, #212529 100%); border-radius: 20px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; } #game-area { position: relative; width: 100%; height: 100%; cursor: pointer; } #character { position: absolute; width: 80px; height: 80px; background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" fill="%23FFD166"/><circle cx="35" cy="40" r="8" fill="%232A2D34"/><circle cx="65" cy="40" r="8" fill="%232A2D34"/><path d="M30,65 Q50,80 70,65" stroke="%232A2D34" stroke-width="5" fill="none"/></svg>') no-repeat center center; background-size: contain; transform: translate(-50%, -50%); z-index: 10; transition: transform 0.2s ease; } #character.active { transform: translate(-50%, -50%) scale(1.2); } .trail-particle { position: absolute; border-radius: 50%; pointer-events: none; opacity: 0.8; z-index: 5; transform: translate(-50%, -50%); } .start-burst { position: absolute; width: 120px; height: 120px; transform: translate(-50%, -50%); pointer-events: none; z-index: 4; background: radial-gradient(circle, var(--accent) 0%, transparent 70%); opacity: 0; } .end-burst { position: absolute; width: 150px; height: 150px; transform: translate(-50%, -50%); pointer-events: none; z-index: 4; background: radial-gradient(circle, var(--primary) 0%, transparent 70%); opacity: 0; } .star { position: absolute; width: 30px; height: 30px; transform: translate(-50%, -50%); opacity: 0; z-index: 3; background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" fill="%234ECDC4"/></svg>') no-repeat center center; background-size: contain; } .target { position: absolute; width: 40px; height: 40px; background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" stroke="%23FF6B6B" stroke-width="5" fill="none"/><circle cx="50" cy="50" r="25" stroke="%23FF6B6B" stroke-width="5" fill="none"/><circle cx="50" cy="50" r="10" fill="%23FF6B6B"/></svg>') no-repeat center center; background-size: contain; transform: translate(-50%, -50%); z-index: 2; } #instructions { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); font-size: 18px; background-color: rgba(42, 45, 52, 0.8); padding: 10px 20px; border-radius: 20px; text-align: center; z-index: 20; width: 80%; max-width: 500px; transition: opacity 0.5s ease; } #score { position: absolute; top: 20px; right: 20px; font-size: 24px; font-weight: bold; z-index: 20; } #time { position: absolute; top: 20px; left: 20px; font-size: 24px; font-weight: bold; z-index: 20; } #level { position: absolute; bottom: 20px; left: 20px; font-size: 18px; font-weight: bold; z-index: 20; } #game-over { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(42, 45, 52, 0.9); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 30; opacity: 0; pointer-events: none; transition: opacity 0.5s ease; } #game-over.show { opacity: 1; pointer-events: all; } #game-over h2 { font-size: 36px; margin-bottom: 20px; color: var(--accent); } #game-over p { font-size: 24px; margin-bottom: 10px; } #restart-btn { margin-top: 30px; padding: 12px 24px; background-color: var(--primary); color: white; border: none; border-radius: 30px; font-size: 18px; font-weight: bold; cursor: pointer; transition: transform 0.2s ease, background-color 0.3s ease; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } #restart-btn:hover { transform: scale(1.05); background-color: #ff5252; } @media (max-width: 700px) { #game-container { width: 100%; height: 100vw; max-height: 700px; border-radius: 0; } #character { width: 60px; height: 60px; } #instructions { font-size: 16px; padding: 8px 16px; } #score, #time { font-size: 20px; } } </style> </head> <body> <div id="game-container"> <div id="game-area"> <div id="character"></div> <div id="instructions">Trace the adventure path! Tap and drag to move your hero across the targets!</div> <div id="score">Score: 0</div> <div id="time">Time: 30s</div> <div id="level">Level: 1</div> </div> <div id="game-over"> <h2>Adventure Complete!</h2> <p>Final Score: <span id="final-score">0</span></p> <p>Highest Level: <span id="final-level">1</span></p> <button id="restart-btn">Play Again!</button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Game elements const gameArea = document.getElementById('game-area'); const character = document.getElementById('character'); const instructions = document.getElementById('instructions'); const scoreEl = document.getElementById('score'); const timeEl = document.getElementById('time'); const levelEl = document.getElementById('level'); const gameOverEl = document.getElementById('game-over'); const finalScoreEl = document.getElementById('final-score'); const finalLevelEl = document.getElementById('final-level'); const restartBtn = document.getElementById('restart-btn'); // Game variables let score = 0; let timeLeft = 30; let level = 1; let targets = []; let currentTargetIndex = 0; let isMoving = false; let timer; let gameActive = true; // Colors for trail particles const colors = ['#FF6B6B', '#4ECDC4', '#FFD166', '#F7FFF7']; // Initialize game function initGame() { // Reset game state score = 0; timeLeft = 30; level = 1; currentTargetIndex = 0; gameActive = true; // Update UI scoreEl.textContent = `Score: ${score}`; timeEl.textContent = `Time: ${timeLeft}s`; levelEl.textContent = `Level: ${level}`; // Remove old targets document.querySelectorAll('.target').forEach(target => target.remove()); // Hide game over screen gameOverEl.classList.remove('show'); // Show instructions instructions.style.opacity = 1; setTimeout(() => { instructions.style.opacity = 0; }, 3000); // Create new targets createTargets(); // Start timer clearInterval(timer); timer = setInterval(() => { timeLeft--; timeEl.textContent = `Time: ${timeLeft}s`; if (timeLeft <= 0) { endGame(); } }, 1000); } // Create targets based on current level function createTargets() { targets = []; const numTargets = 5 + level * 2; for (let i = 0; i < numTargets; i++) { const target = document.createElement('div'); target.classList.add('target'); // Calculate position (avoid edges) const padding = 50; const x = Math.random() * (gameArea.clientWidth - padding * 2) + padding; const y = Math.random() * (gameArea.clientHeight - padding * 2) + padding; target.style.left = `${x}px`; target.style.top = `${y}px`; gameArea.appendChild(target); targets.push({ el: target, x, y }); } // Position character at first target if (targets.length > 0) { character.style.left = targets[0].x + 'px'; character.style.top = targets[0].y + 'px'; // Create start burst createStartBurst(targets[0].x, targets[0].y); } } // Create start burst animation function createStartBurst(x, y) { const burst = document.createElement('div'); burst.classList.add('start-burst'); burst.style.left = x + 'px'; burst.style.top = y + 'px'; gameArea.appendChild(burst); // Animate burst.style.transition = 'transform 0.5s ease-out, opacity 0.5s ease-out'; setTimeout(() => { burst.style.transform = 'translate(-50%, -50%) scale(3)'; burst.style.opacity = '0.8'; setTimeout(() => { burst.remove(); }, 500); }, 0); } // Create end burst animation function createEndBurst(x, y) { const burst = document.createElement('div'); burst.classList.add('end-burst'); burst.style.left = x + 'px'; burst.style.top = y + 'px'; gameArea.appendChild(burst); // Animate burst.style.transition = 'transform 0.8s ease-out, opacity 0.8s ease-out'; setTimeout(() => { burst.style.transform = 'translate(-50%, -50%) scale(4)'; burst.style.opacity = '0.8'; setTimeout(() => { burst.remove(); }, 800); }, 0); // Create stars for (let i = 0; i < 6; i++) { createStar(x, y); } } // Create star particles function createStar(x, y) { const star = document.createElement('div'); star.classList.add('star'); star.style.left = x + 'px'; star.style.top = y + 'px'; gameArea.appendChild(star); // Random direction const angle = Math.random() * Math.PI * 2; const distance = 100 + Math.random() * 100; const destX = x + Math.cos(angle) * distance; const destY = y + Math.sin(angle) * distance; // Animate star.style.transition = 'left 0.8s ease-out, top 0.8s ease-out, opacity 0.8s ease-out, transform 0.8s ease-out'; setTimeout(() => { star.style.opacity = '1'; star.style.transform = 'translate(-50%, -50%) scale(1.5) rotate(180deg)'; star.style.left = destX + 'px'; star.style.top = destY + 'px'; setTimeout(() => { star.style.opacity = '0'; setTimeout(() => { star.remove(); }, 800); }, 400); }, 0); } // Create trail particle function createTrailParticle(x, y) { if (!gameActive) return; const particle = document.createElement('div'); particle.classList.add('trail-particle'); // Random size between 10 and 25 const size = 10 + Math.random() * 15; particle.style.width = `${size}px`; particle.style.height = `${size}px`; // Random color const color = colors[Math.floor(Math.random() * colors.length)]; particle.style.backgroundColor = color; // Position particle.style.left = `${x}px`; particle.style.top = `${y}px`; gameArea.appendChild(particle); // Animate and remove setTimeout(() => { particle.style.transition = 'transform 0.8s ease-out, opacity 0.8s ease-out'; particle.style.transform = 'translate(-50%, -50%) scale(0.2)'; particle.style.opacity = '0'; setTimeout(() => { particle.remove(); }, 800); }, 0); } // Handle character movement function moveCharacter(x, y) { if (!gameActive) return; character.style.left = `${x}px`; character.style.top = `${y}px`; // Create trail particles createTrailParticle(x, y); // Check if character reached current target if (currentTargetIndex < targets.length) { const target = targets[currentTargetIndex]; const dx = x - target.x; const dy = y - target.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < 30) { // Target reached // Remove this target target.el.style.transition = 'transform 0.3s ease-out, opacity 0.3s ease-out'; target.el.style.transform = 'translate(-50%, -50%) scale(1.5)'; target.el.style.opacity = '0'; // Update score and add time score += 10 * level; timeLeft += 2; scoreEl.textContent = `Score: ${score}`; timeEl.textContent = `Time: ${timeLeft}s`; // Move to next target currentTargetIndex++; // Check if all targets collected if (currentTargetIndex >= targets.length) { // Level complete! createEndBurst(x, y); setTimeout(() => { level++; levelEl.textContent = `Level: ${level}`; currentTargetIndex = 0; // Remove old targets document.querySelectorAll('.target').forEach(target => target.remove()); // Create new targets createTargets(); }, 1000); } } } } // Handle touch/mouse events function handleStart(e) { if (!gameActive) return; e.preventDefault(); isMoving = true; character.classList.add('active'); const x = e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const y = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; // Get position relative to game area const rect = gameArea.getBoundingClientRect(); const gameX = x - rect.left; const gameY = y - rect.top; moveCharacter(gameX, gameY); } function handleMove(e) { if (!isMoving || !gameActive) return; e.preventDefault(); const x = e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const y = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; // Get position relative to game area const rect = gameArea.getBoundingClientRect(); const gameX = x - rect.left; const gameY = y - rect.top; moveCharacter(gameX, gameY); } function handleEnd() { isMoving = false; character.classList.remove('active'); } // End game function endGame() { gameActive = false; clearInterval(timer); // Update final score finalScoreEl.textContent = score; finalLevelEl.textContent = level; // Show game over screen gameOverEl.classList.add('show'); } // Event listeners gameArea.addEventListener('mousedown', handleStart); gameArea.addEventListener('touchstart', handleStart); gameArea.addEventListener('mousemove', handleMove); gameArea.addEventListener('touchmove', handleMove); gameArea.addEventListener('mouseup', handleEnd); gameArea.addEventListener('touchend', handleEnd); gameArea.addEventListener('mouseleave', handleEnd); restartBtn.addEventListener('click', initGame); // Start the game initGame(); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>NeoVerse Explorer | Virtual Tour Navigation</title> <style> :root { --primary-neon: #0cffe1; --secondary-neon: #ff36f7; --tertiary-neon: #5d42ff; --background: #0a0a14; --surface: #131326; --text-primary: #ffffff; --text-secondary: #c7c7d1; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: var(--background); color: var(--text-primary); overflow: hidden; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; } .tour-container { position: relative; width: 700px; height: 700px; overflow: hidden; border-radius: 12px; box-shadow: 0 0 30px rgba(12, 255, 225, 0.15); } .scene { position: absolute; width: 100%; height: 100%; perspective: 1000px; transform-style: preserve-3d; transition: transform 1.5s cubic-bezier(0.34, 1.56, 0.64, 1); } .panorama { position: absolute; width: 100%; height: 100%; background-size: cover; background-position: center; opacity: 0; transition: opacity 1s ease; border-radius: 12px; overflow: hidden; } .panorama.active { opacity: 1; } .location-marker { position: absolute; width: 30px; height: 30px; border-radius: 50%; background-color: rgba(12, 255, 225, 0.2); border: 2px solid var(--primary-neon); transform: translate(-50%, -50%); cursor: pointer; z-index: 10; transition: all 0.3s ease; display: flex; justify-content: center; align-items: center; } .location-marker::before { content: ''; position: absolute; width: 10px; height: 10px; background-color: var(--primary-neon); border-radius: 50%; animation: pulse 2s infinite; } .location-marker::after { content: ''; position: absolute; width: 100%; height: 100%; border-radius: 50%; box-shadow: 0 0 15px var(--primary-neon); opacity: 0; transition: opacity 0.3s ease; } .location-marker:hover { transform: translate(-50%, -50%) scale(1.2); background-color: rgba(12, 255, 225, 0.4); } .location-marker:hover::after { opacity: 1; } .location-marker.district-2 { border-color: var(--secondary-neon); } .location-marker.district-2::before { background-color: var(--secondary-neon); } .location-marker.district-2:hover::after { box-shadow: 0 0 15px var(--secondary-neon); } .location-marker.district-3 { border-color: var(--tertiary-neon); } .location-marker.district-3::before { background-color: var(--tertiary-neon); } .location-marker.district-3:hover::after { box-shadow: 0 0 15px var(--tertiary-neon); } .motion-path { position: absolute; width: 100%; height: 100%; pointer-events: none; z-index: 5; } .neon-trail { position: absolute; border-radius: 50%; filter: blur(8px); transform: translate(-50%, -50%); opacity: 0; z-index: 2; pointer-events: none; } @keyframes pulse { 0% { transform: scale(0.7); opacity: 1; } 70% { transform: scale(1.3); opacity: 0; } 100% { transform: scale(0.7); opacity: 0; } } .info-panel { position: absolute; bottom: 30px; left: 30px; width: 320px; background: rgba(19, 19, 38, 0.75); backdrop-filter: blur(10px); border-radius: 12px; padding: 20px; z-index: 20; border-left: 3px solid var(--primary-neon); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); transform: translateY(20px); opacity: 0; transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } .info-panel.visible { transform: translateY(0); opacity: 1; } .info-panel h2 { font-size: 1.5rem; margin-bottom: 8px; font-weight: 600; } .info-panel p { font-size: 0.9rem; color: var(--text-secondary); line-height: 1.5; margin-bottom: 15px; } .explore-btn { background: transparent; color: var(--primary-neon); border: 1px solid var(--primary-neon); padding: 8px 15px; border-radius: 6px; font-size: 0.9rem; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; outline: none; } .explore-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(12, 255, 225, 0.2), transparent); transition: all 0.5s ease; } .explore-btn:hover::before { left: 100%; } .explore-btn:hover { background-color: rgba(12, 255, 225, 0.1); box-shadow: 0 0 10px rgba(12, 255, 225, 0.5); } .district-2 .explore-btn { color: var(--secondary-neon); border-color: var(--secondary-neon); } .district-2 .explore-btn:hover { background-color: rgba(255, 54, 247, 0.1); box-shadow: 0 0 10px rgba(255, 54, 247, 0.5); } .district-3 .explore-btn { color: var(--tertiary-neon); border-color: var(--tertiary-neon); } .district-3 .explore-btn:hover { background-color: rgba(93, 66, 255, 0.1); box-shadow: 0 0 10px rgba(93, 66, 255, 0.5); } .district-2.info-panel { border-left-color: var(--secondary-neon); } .district-3.info-panel { border-left-color: var(--tertiary-neon); } .navigation-controls { position: absolute; right: 30px; bottom: 30px; z-index: 20; display: flex; gap: 15px; } .nav-btn { width: 50px; height: 50px; border-radius: 50%; background: rgba(19, 19, 38, 0.75); backdrop-filter: blur(10px); border: none; color: var(--text-primary); display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } .nav-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4); } .nav-btn svg { width: 24px; height: 24px; fill: currentColor; } .tour-title { position: absolute; top: 30px; left: 30px; z-index: 20; font-size: 1.5rem; font-weight: 700; letter-spacing: 1px; text-shadow: 0 0 10px rgba(0, 0, 0, 0.5); } .tour-title span { color: var(--primary-neon); text-shadow: 0 0 10px var(--primary-neon); } .compass { position: absolute; top: 30px; right: 30px; width: 60px; height: 60px; border-radius: 50%; background: rgba(19, 19, 38, 0.75); backdrop-filter: blur(10px); display: flex; justify-content: center; align-items: center; z-index: 20; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } .compass-needle { width: 30px; height: 30px; transition: transform 1s cubic-bezier(0.34, 1.56, 0.64, 1); transform: rotate(0deg); } .loading-overlay { position: absolute; width: 100%; height: 100%; background-color: var(--background); z-index: 50; display: flex; justify-content: center; align-items: center; flex-direction: column; transition: opacity 1s ease, visibility 1s ease; } .loading-spinner { width: 60px; height: 60px; border: 3px solid transparent; border-top-color: var(--primary-neon); border-right-color: var(--secondary-neon); border-bottom-color: var(--tertiary-neon); border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 20px; } .loading-text { font-size: 1rem; letter-spacing: 2px; animation: pulse-text 1.5s infinite; } @keyframes spin { to { transform: rotate(360deg); } } @keyframes pulse-text { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .hidden { opacity: 0; visibility: hidden; } @media (max-width: 700px) { .info-panel { width: 85%; left: 50%; transform: translateX(-50%) translateY(20px); } .info-panel.visible { transform: translateX(-50%) translateY(0); } .navigation-controls { right: 20px; bottom: 20px; } .tour-title { top: 20px; left: 20px; font-size: 1.2rem; } .compass { top: 20px; right: 20px; width: 50px; height: 50px; } } </style> </head> <body> <div class="tour-container"> <div class="loading-overlay"> <div class="loading-spinner"></div> <div class="loading-text">INITIALIZING NEOVERSE</div> </div> <div class="tour-title">NEO<span>VERSE</span> EXPLORER</div> <div class="compass"> <svg class="compass-needle" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 2L8 12H16L12 22" stroke="#0cffe1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 22L16 12H8L12 2" stroke="#ff36f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </div> <div class="scene"> <div class="panorama" id="panorama-1" style="background-image: url('https://images.unsplash.com/photo-1536566482680-fca31930a0bd?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3')"></div> <div class="panorama" id="panorama-2" style="background-image: url('https://images.unsplash.com/photo-1507537297725-24a1c029d3ca?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3')"></div> <div class="panorama" id="panorama-3" style="background-image: url('https://images.unsplash.com/photo-1676406171203-fdf1c5084a65?q=80&w=2532&auto=format&fit=crop&ixlib=rb-4.0.3')"></div> </div> <div class="motion-path" id="motion-path"></div> <div class="info-panel" id="info-panel"> <h2>Techno Nexus Hub</h2> <p>The central district where advanced holographic interfaces merge with physical architecture. The neural-linked traffic system adapts to human movement patterns in real-time, creating a responsive cityscape.</p> <button class="explore-btn" id="explore-btn">EXPLORE DISTRICT</button> </div> <div class="navigation-controls"> <button class="nav-btn" id="prev-btn"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/> </svg> </button> <button class="nav-btn" id="next-btn"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/> </svg> </button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Simulating loading time setTimeout(() => { document.querySelector('.loading-overlay').classList.add('hidden'); initTour(); }, 2000); const locationData = [ { id: 'techno-nexus', panoramaId: 'panorama-1', title: 'Techno Nexus Hub', description: 'The central district where advanced holographic interfaces merge with physical architecture. The neural-linked traffic system adapts to human movement patterns in real-time, creating a responsive cityscape.', markers: [ { x: 20, y: 45, target: 'neo-gardens' }, { x: 75, y: 30, target: 'quantum-plaza' } ], color: 'primary' }, { id: 'neo-gardens', panoramaId: 'panorama-2', title: 'Neo-Botanical Gardens', description: 'Bioluminescent flora respond to human proximity, shifting colors and patterns. The centralized climate dome maintains perfect atmospheric conditions while filtering pollutants through genetically enhanced plant systems.', markers: [ { x: 35, y: 60, target: 'techno-nexus' }, { x: 85, y: 50, target: 'quantum-plaza' } ], color: 'district-2' }, { id: 'quantum-plaza', panoramaId: 'panorama-3', title: 'Quantum Plaza', description: 'The experimental zone where reality bends through quantum computing nodes. Visitors experience dimensional shifts as the plaza's architecture reconfigures based on collective consciousness data gathered through neural interfaces.', markers: [ { x: 25, y: 40, target: 'techno-nexus' }, { x: 65, y: 70, target: 'neo-gardens' } ], color: 'district-3' } ]; let currentLocationIndex = 0; let isAnimating = false; function initTour() { updateScene(0, false); document.getElementById('info-panel').classList.add('visible'); document.getElementById('next-btn').addEventListener('click', () => { if (!isAnimating) { const nextIndex = (currentLocationIndex + 1) % locationData.length; updateScene(nextIndex, true); } }); document.getElementById('prev-btn').addEventListener('click', () => { if (!isAnimating) { const prevIndex = (currentLocationIndex - 1 + locationData.length) % locationData.length; updateScene(prevIndex, true); } }); document.getElementById('explore-btn').addEventListener('click', () => { if (!isAnimating) { const nextIndex = (currentLocationIndex + 1) % locationData.length; updateScene(nextIndex, true); } }); } function updateScene(newIndex, animate) { if (isAnimating) return; isAnimating = animate; // Update info panel const infoPanel = document.getElementById('info-panel'); infoPanel.classList.remove('visible', 'district-2', 'district-3'); setTimeout(() => { const newLocation = locationData[newIndex]; infoPanel.querySelector('h2').textContent = newLocation.title; infoPanel.querySelector('p').textContent = newLocation.description; if(newLocation.color !== 'primary') { infoPanel.classList.add(newLocation.color); } // Update button class const exploreBtn = document.getElementById('explore-btn'); exploreBtn.className = 'explore-btn'; if(newLocation.color !== 'primary') { exploreBtn.classList.add(newLocation.color); } // Show info panel with delay setTimeout(() => { infoPanel.classList.add('visible'); }, 500); }, animate ? 300 : 0); // Clear existing markers document.querySelectorAll('.location-marker').forEach(marker => marker.remove()); // Update panorama document.querySelectorAll('.panorama').forEach(panorama => { panorama.classList.remove('active'); }); if (animate) { // Create motion path animation createMotionPathAnimation(currentLocationIndex, newIndex); // Rotate compass const rotation = (newIndex - currentLocationIndex) * 120; document.querySelector('.compass-needle').style.transform = `rotate(${rotation}deg)`; setTimeout(() => { document.getElementById(locationData[newIndex].panoramaId).classList.add('active'); addLocationMarkers(newIndex); currentLocationIndex = newIndex; isAnimating = false; }, 1500); } else { document.getElementById(locationData[newIndex].panoramaId).classList.add('active'); addLocationMarkers(newIndex); currentLocationIndex = newIndex; } } function addLocationMarkers(locationIndex) { const location = locationData[locationIndex]; location.markers.forEach((marker, i) => { const markerElement = document.createElement('div'); markerElement.className = `location-marker ${getTargetColor(marker.target)}`; markerElement.style.left = `${marker.x}%`; markerElement.style.top = `${marker.y}%`; markerElement.addEventListener('click', () => { if (!isAnimating) { const targetIndex = locationData.findIndex(loc => loc.id === marker.target); updateScene(targetIndex, true); } }); document.querySelector('.scene').appendChild(markerElement); }); } function getTargetColor(targetId) { const targetLocation = locationData.find(loc => loc.id === targetId); return targetLocation.color; } function createMotionPathAnimation(fromIndex, toIndex) { const motionPath = document.getElementById('motion-path'); motionPath.innerHTML = ''; const fromLocation = locationData[fromIndex]; const toLocation = locationData[toIndex]; const targetMarker = fromLocation.markers.find(marker => locationData[toIndex].id === marker.target ); if (!targetMarker) return; // Start position (center of screen) const startX = 50; const startY = 50; // End position (marker position) const endX = targetMarker.x; const endY = targetMarker.y; // Create bezier curve path // Control points for curve const cp1x = startX + (endX - startX) * 0.3; const cp1y = startY + (endY - startY) * 0.1; const cp2x = startX + (endX - startX) * 0.7; const cp2y = startY + (endY - startY) * 0.9; // Create neon trail effect const totalParticles = 100; const targetColor = getColorValue(toLocation.color); for (let i = 0; i < totalParticles; i++) { const particle = document.createElement('div'); particle.className = 'neon-trail'; particle.style.backgroundColor = targetColor; const t = i / totalParticles; const delay = i * 10; const size = Math.random() * 10 + 5; particle.style.width = `${size}px`; particle.style.height = `${size}px`; // Animation setTimeout(() => { particle.style.transition = `all 1.5s cubic-bezier(0.34, 1.56, 0.64, 1)`; // Calculate point on bezier curve const x = calculateBezierPoint(t, startX, cp1x, cp2x, endX); const y = calculateBezierPoint(t, startY, cp1y, cp2y, endY); particle.style.left = `${x}%`; particle.style.top = `${y}%`; particle.style.opacity = 0.8; setTimeout(() => { particle.style.opacity = 0; // Remove particle after animation setTimeout(() => { particle.remove(); }, 1000); }, 300); }, delay); motionPath.appendChild(particle); } } function calculateBezierPoint(t, p0, p1, p2, p3) { const cX = 3 * (p1 - p0); const bX = 3 * (p2 - p1) - cX; const aX = p3 - p0 - cX - bX; return aX * Math.pow(t, 3) + bX * Math.pow(t, 2) + cX * t + p0; } function getColorValue(colorClass) { switch(colorClass) { case 'district-2': return '#ff36f7'; case 'district-3': return '#5d42ff'; default: return '#0cffe1'; } } }); </script> </body> </html>