Hotspots are dynamic elements that can transform a static interface into an interactive experience. They serve as focal points, guiding users through content and enhancing engagement.
In this article, we will explore ten compelling examples of hotspots that demonstrate their versatility and impact. From e-commerce to educational platforms, these examples showcase the power of well-placed interactive elements.
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
Designers and developers love Subframe for its drag-and-drop interface and intuitive, responsive canvas. Create pixel-perfect UI effortlessly, ensuring every hotspot is perfectly placed.
Experience the ease and precision of Subframe. 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 power of Subframe and design stunning UIs with ease. Our drag-and-drop editor and beautifully crafted components make creating pixel-perfect interfaces a breeze.
Experience unparalleled efficiency and start creating immediately. Start for free today!
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Ancient Mesopotamia: Interactive Exhibit</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { background-color: #f5f2ed; display: flex; flex-direction: column; height: 700px; overflow: hidden; color: #3c3c3c; } .container { width: 100%; max-width: 700px; height: 100%; margin: 0 auto; padding: 20px; position: relative; display: flex; flex-direction: column; } header { text-align: center; margin-bottom: 15px; } h1 { font-size: 22px; margin-bottom: 5px; color: #63574E; font-weight: 600; } .subtitle { font-size: 14px; color: #887366; margin-bottom: 10px; } .map-container { position: relative; flex-grow: 1; overflow: hidden; border-radius: 12px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.05); background-color: #eae5db; } .map { width: 100%; height: 100%; background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAwIiBoZWlnaHQ9IjUwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwLDUwIEMxNTAsOTAgMjUwLDEwMCAzMDAsMTUwIEMzNTAsMjAwIDQwMCwzMDAgNDUwLDM1MCBDNTAwLDQwMCA2MDAsNDUwIDY1MCwzMDAiIHN0cm9rZT0iI2MwYjhhNSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIiBzdHJva2UtZGFzaGFycmF5PSI1LDUiLz48cGF0aCBkPSJNMTUwLDI1MCBDMjUwLDIwMCAzMDAsMzAwIDQwMCwzNTAgQzUwMCw0MDAgNTUwLDMwMCA2MDAsMjUwIiBzdHJva2U9IiNjMGI4YTUiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWRhc2hhcnJheT0iNSw1Ii8+PGNpcmNsZSBjeD0iMjAwIiBjeT0iMTUwIiByPSIyMCIgZmlsbD0iI2VhZTVkYiIgc3Ryb2tlPSIjYzBiOGE1IiBzdHJva2Utd2lkdGg9IjEiLz48Y2lyY2xlIGN4PSIzNTAiIGN5PSIyNTAiIHI9IjMwIiBmaWxsPSIjZWFlNWRiIiBzdHJva2U9IiNjMGI4YTUiIHN0cm9rZS13aWR0aD0iMSIvPjxjaXJjbGUgY3g9IjUwMCIgY3k9IjE1MCIgcj0iMjUiIGZpbGw9IiNlYWU1ZGIiIHN0cm9rZT0iI2MwYjhhNSIgc3Ryb2tlLXdpZHRoPSIxIi8+PGNpcmNsZSBjeD0iNDUwIiBjeT0iMzUwIiByPSIzNSIgZmlsbD0iI2VhZTVkYiIgc3Ryb2tlPSIjYzBiOGE1IiBzdHJva2Utd2lkdGg9IjEiLz48Y2lyY2xlIGN4PSIxNTAiIGN5PSIzMzAiIHI9IjI4IiBmaWxsPSIjZWFlNWRiIiBzdHJva2U9IiNjMGI4YTUiIHN0cm9rZS13aWR0aD0iMSIvPjwvc3ZnPg=='); background-size: cover; background-position: center; position: relative; } .hotspot { position: absolute; width: 30px; height: 30px; border-radius: 50%; background-color: rgba(180, 160, 140, 0.2); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; border: 2px solid rgba(180, 160, 140, 0.5); z-index: 10; } .hotspot::before { content: ''; position: absolute; width: 10px; height: 10px; border-radius: 50%; background-color: rgba(180, 160, 140, 0.8); transition: all 0.3s ease; } .hotspot:hover { transform: scale(1.2); background-color: rgba(180, 160, 140, 0.3); } .hotspot:hover::before { background-color: rgba(180, 160, 140, 1); } .hotspot.active { background-color: rgba(180, 160, 140, 0.5); transform: scale(1.2); } .hotspot.active::before { background-color: rgba(99, 87, 78, 1); } .artifact-panel { position: absolute; width: 0; height: 100%; top: 0; right: 0; background-color: #fff; box-shadow: -5px 0 20px rgba(0, 0, 0, 0.1); transition: width 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55); overflow: hidden; border-radius: 0 12px 12px 0; z-index: 5; } .artifact-panel.active { width: 60%; } .artifact-content { padding: 30px; height: 100%; opacity: 0; transition: opacity 0.3s ease 0.2s; overflow-y: auto; display: flex; flex-direction: column; } .artifact-panel.active .artifact-content { opacity: 1; } .artifact-title { font-size: 20px; font-weight: 600; margin-bottom: 5px; color: #63574E; } .artifact-period { font-size: 14px; color: #887366; margin-bottom: 15px; font-style: italic; } .artifact-image { width: 100%; height: 180px; margin-bottom: 20px; border-radius: 8px; object-fit: cover; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; } .artifact-image:hover { transform: scale(1.02); } .artifact-description { font-size: 14px; line-height: 1.6; color: #555; margin-bottom: 15px; } .artifact-facts { margin-top: 10px; } .fact-item { display: flex; margin-bottom: 8px; font-size: 13px; } .fact-label { flex: 0 0 100px; font-weight: 600; color: #887366; } .fact-value { flex: 1; color: #555; } .close-btn { position: absolute; top: 15px; right: 15px; width: 25px; height: 25px; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 50%; background-color: rgba(180, 160, 140, 0.1); transition: all 0.3s ease; } .close-btn:hover { background-color: rgba(180, 160, 140, 0.2); } .close-btn::before, .close-btn::after { content: ''; position: absolute; width: 12px; height: 2px; background-color: #887366; transition: all 0.3s ease; } .close-btn::before { transform: rotate(45deg); } .close-btn::after { transform: rotate(-45deg); } .instructions { text-align: center; margin-top: 15px; font-size: 13px; color: #887366; } .ripple { position: absolute; border-radius: 50%; transform: scale(0); background-color: rgba(180, 160, 140, 0.3); animation: ripple 1s ease-out; } @keyframes ripple { to { transform: scale(3); opacity: 0; } } .pulse { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(180, 160, 140, 0.5); } 70% { box-shadow: 0 0 0 10px rgba(180, 160, 140, 0); } 100% { box-shadow: 0 0 0 0 rgba(180, 160, 140, 0); } } @media (max-width: 600px) { .artifact-panel.active { width: 85%; } h1 { font-size: 18px; } .artifact-image { height: 150px; } } </style> </head> <body> <div class="container"> <header> <h1>Ancient Mesopotamia: Cradle of Civilization</h1> <div class="subtitle">Navigate the exhibition map and discover ancient treasures</div> </header> <div class="map-container"> <div class="map"> <div class="hotspot pulse" id="hotspot1" style="left: 25%; top: 30%;" data-id="1"></div> <div class="hotspot" id="hotspot2" style="left: 50%; top: 50%;" data-id="2"></div> <div class="hotspot" id="hotspot3" style="left: 70%; top: 30%;" data-id="3"></div> <div class="hotspot" id="hotspot4" style="left: 65%; top: 70%;" data-id="4"></div> <div class="hotspot" id="hotspot5" style="left: 20%; top: 65%;" data-id="5"></div> </div> <div class="artifact-panel" id="artifactPanel"> <div class="close-btn" id="closeBtn"></div> <div class="artifact-content" id="artifactContent"></div> </div> </div> <div class="instructions">Click on hotspots to explore artifacts</div> </div> <script> const artifacts = [ { id: 1, title: "Standard of Ur", period: "2600-2400 BCE, Early Dynastic III period", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2QyYjc5ZCIvPjxyZWN0IHg9IjIwIiB5PSIyMCIgd2lkdGg9IjM2MCIgaGVpZ2h0PSIxNjAiIGZpbGw9IiNlNGNlYjAiLz48cmVjdCB4PSI0MCIgeT0iNDAiIHdpZHRoPSIzMjAiIGhlaWdodD0iMTIwIiBmaWxsPSIjZDJiNzlkIi8+PGcgZmlsbD0iIzYzNTc0RSI+PHJlY3QgeD0iNjAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIxMDAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIxNDAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIxODAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIyMjAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIyNjAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIzMDAiIHk9IjUwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSI4MCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjEyMCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjE2MCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjIwMCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjI0MCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjI4MCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjMyMCIgeT0iODAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjYwIiB5PSIxMTAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjEwMCIgeT0iMTEwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIxNDAiIHk9IjExMCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIi8+PHJlY3QgeD0iMTgwIiB5PSIxMTAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjxyZWN0IHg9IjIyMCIgeT0iMTEwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiLz48cmVjdCB4PSIyNjAiIHk9IjExMCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIi8+PHJlY3QgeD0iMzAwIiB5PSIxMTAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIvPjwvZz48L3N2Zz4=", description: "The Standard of Ur is one of the most remarkable artifacts from ancient Mesopotamia. Discovered in the 1920s by Sir Leonard Woolley in the Royal Cemetery at Ur (in modern-day Iraq), this trapezoidal box features intricate mosaic scenes of war and peace in Sumerian society.", facts: [ { label: "Origin", value: "Royal Cemetery at Ur, present-day Iraq" }, { label: "Material", value: "Shell, red limestone, lapis lazuli on wood" }, { label: "Dimensions", value: "21.59 cm × 49.53 cm (8.5 in × 19.5 in)" }, { label: "Current location", value: "British Museum, London" } ] }, { id: 2, title: "Code of Hammurabi", period: "1754 BCE, Old Babylonian Period", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iIzRkNDQzOCIvPjxwYXRoIGQ9Ik0xMzAgMjAgTDI3MCAyMCBMMjgwIDQwIEwxMjAgNDAgWiIgZmlsbD0iIzYzNTc0RSIvPjxyZWN0IHg9IjEyMCIgeT0iNDAiIHdpZHRoPSIxNjAiIGhlaWdodD0iMTQwIiBmaWxsPSIjNjM1NzRFIi8+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDJiNzlkIiBzdHJva2Utd2lkdGg9IjAuNSI+PGxpbmUgeDE9IjEzMCIgeTE9IjUwIiB4Mj0iMjcwIiB5Mj0iNTAiLz48bGluZSB4MT0iMTMwIiB5MT0iNjAiIHgyPSIyNzAiIHkyPSI2MCIvPjxsaW5lIHgxPSIxMzAiIHkxPSI3MCIgeDI9IjI3MCIgeTI9IjcwIi8+PGxpbmUgeDE9IjEzMCIgeTE9IjgwIiB4Mj0iMjcwIiB5Mj0iODAiLz48bGluZSB4MT0iMTMwIiB5MT0iOTAiIHgyPSIyNzAiIHkyPSI5MCIvPjxsaW5lIHgxPSIxMzAiIHkxPSIxMDAiIHgyPSIyNzAiIHkyPSIxMDAiLz48bGluZSB4MT0iMTMwIiB5MT0iMTEwIiB4Mj0iMjcwIiB5Mj0iMTEwIi8+PGxpbmUgeDE9IjEzMCIgeTE9IjEyMCIgeDI9IjI3MCIgeTI9IjEyMCIvPjxsaW5lIHgxPSIxMzAiIHkxPSIxMzAiIHgyPSIyNzAiIHkyPSIxMzAiLz48bGluZSB4MT0iMTMwIiB5MT0iMTQwIiB4Mj0iMjcwIiB5Mj0iMTQwIi8+PGxpbmUgeDE9IjEzMCIgeTE9IjE1MCIgeDI9IjI3MCIgeTI9IjE1MCIvPjxsaW5lIHgxPSIxMzAiIHkxPSIxNjAiIHgyPSIyNzAiIHkyPSIxNjAiLz48bGluZSB4MT0iMTMwIiB5MT0iMTcwIiB4Mj0iMjcwIiB5Mj0iMTcwIi8+PC9nPjxjaXJjbGUgY3g9IjIwMCIgY3k9IjMwIiByPSIxNSIgZmlsbD0iI2QyYjc5ZCIvPjwvc3ZnPg==", description: "The Code of Hammurabi is one of the oldest deciphered writings of significant length in the world. Created during the reign of the Babylonian king Hammurabi, this basalt stele contains 282 laws with scaled punishments, often exemplifying the principle of 'an eye for an eye'.", facts: [ { label: "Origin", value: "Babylon, present-day Iraq" }, { label: "Material", value: "Basalt" }, { label: "Height", value: "2.25 meters (7.4 ft)" }, { label: "Script", value: "Akkadian in cuneiform script" }, { label: "Current location", value: "Louvre Museum, Paris" } ] }, { id: 3, title: "The Queen's Lyre", period: "2600-2400 BCE, Early Dynastic Period", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YyZTlkZiIvPjxwYXRoIGQ9Ik0xMzAgMzAgTDI3MCAzMCBMMjUwIDE3MCBMMTU1IDE3MCBaIiBmaWxsPSIjZDViYzgzIi8+PGNpcmNsZSBjeD0iMjAyIiBjeT0iNTAiIHI9IjIwIiBmaWxsPSIjZjJlOWRmIiBzdHJva2U9IiNkMmI3OWQiIHN0cm9rZS13aWR0aD0iMiIvPjxwYXRoIGQ9Ik0xNTAgNzAgTDE1NSAxNzAgTDEzNSAxNzAgWiIgZmlsbD0iI2QyYjc5ZCIvPjxwYXRoIGQ9Ik0yNTAgNzAgTDI0NSAxNzAgTDI2NSAxNzAgWiIgZmlsbD0iI2QyYjc5ZCIvPjxnIHN0cm9rZT0iIzYzNTc0RSIgc3Ryb2tlLXdpZHRoPSIxIj48bGluZSB4MT0iMTQwIiB5MT0iNzAiIHgyPSIyNjAiIHkyPSI3MCIvPjxsaW5lIHgxPSIxNDUiIHkxPSI4MCIgeDI9IjI1NSIgeTI9IjgwIi8+PGxpbmUgeDE9IjE0NyIgeTE9IjkwIiB4Mj0iMjUzIiB5Mj0iOTAiLz48bGluZSB4MT0iMTQ5IiB5MT0iMTAwIiB4Mj0iMjUxIiB5Mj0iMTAwIi8+PGxpbmUgeDE9IjE1MSIgeTE9IjExMCIgeDI9IjI0OSIgeTI9IjExMCIvPjxsaW5lIHgxPSIxNTIiIHkxPSIxMjAiIHgyPSIyNDgiIHkyPSIxMjAiLz48bGluZSB4MT0iMTUzIiB5MT0iMTMwIiB4Mj0iMjQ3IiB5Mj0iMTMwIi8+PGxpbmUgeDE9IjE1NCIgeTE9IjE0MCIgeDI9IjI0NiIgeTI9IjE0MCIvPjxsaW5lIHgxPSIxNTQiIHkxPSIxNTAiIHgyPSIyNDYiIHkyPSIxNTAiLz48bGluZSB4MT0iMTU1IiB5MT0iMTYwIiB4Mj0iMjQ1IiB5Mj0iMTYwIi8+PC9nPjxwYXRoIGQ9Ik0xNzAgMTcwIEwxNzAgMTkwIEwyMzAgMTkwIEwyMzAgMTcwIFoiIGZpbGw9IiNkMmI3OWQiLz48L3N2Zz4=", description: "The Queen's Lyre is one of the most spectacular musical instruments recovered from the Royal Cemetery at Ur. Part of a collection of lyres discovered by Sir Leonard Woolley, this ornate instrument provides remarkable insight into Sumerian musical culture and ceremonial practices.", facts: [ { label: "Origin", value: "Royal Cemetery at Ur, present-day Iraq" }, { label: "Material", value: "Wood, gold leaf, lapis lazuli, shell" }, { label: "Strings", value: "Originally had 11 strings" }, { label: "Sound box", value: "Decorated with gold bull's head and inlaid panels" }, { label: "Current location", value: "British Museum, London (partially reconstructed)" } ] }, { id: 4, title: "Ishtar Gate", period: "575 BCE, Neo-Babylonian Empire", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iIzI5NjA5MSIvPjxwYXRoIGQ9Ik0xMjAgMjAgTDI4MCAyMCBMMjgwIDE4MCBMMTI1IDE4MCBaIiBmaWxsPSIjMmY3MmFkIi8+PHBhdGggZD0iTTE1MCA1MCBMMTU1IDU1IEwxNjUgNTAgTDE3NCA1NSBMMTU2IDcwIEwxODAgNzUgTDE4NSA5MCBMMTY0IDEwMCBMMTU0IDkwIEwxNTAgMTAwIEwxNDYgOTAgTDEzNiAxMDAgTDExNSA5MCBMMTI1IDc1IEwxNDUgNzAgWiIgZmlsbD0iI2ZmY2MwMCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMjUwIDUwIEwyNTUgNTUgTDI2NSA1MCBMMjc0IDU1IEwyNTYgNzAgTDI4MCA3NSBMMjg1IDkwIEwyNjQgMTAwIEwyNTQgOTAgTDI1MCAxMDAgTDI0NiA5MCBMMjM2IDEwMCBMMjE1IDkwIEwyMjUgNzUgTDI0NSA3MCBaIiBmaWxsPSIjZmZjYzAwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMSIvPjxwYXRoIGQ9Ik0xNTAgMTIwIEwxNTUgMTI1IEwxNjUgMTIwIEwxNzQgMTI1IEwxNTYgMTQwIEwxODAgMTQ1IEwxODUgMTYwIEwxNjQgMTcwIEwxNTQgMTYwIEwxNTAgMTcwIEwxNDYgMTYwIEwxMzYgMTcwIEwxMTUgMTYwIEwxMjUgMTQ1IEwxNDUgMTQwIFoiIGZpbGw9IiNmZmNjMDAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxIi8+PHBhdGggZD0iTTI1MCAxMjAgTDI1NSAxMjUgTDI2NSAxMjAgTDI3NCAxMjUgTDI1NiAxNDAgTDI4MCAxNDUgTDI4NSAxNjAgTDI2NCAxNzAgTDI1NCAxNjAgTDI1MCAxNzAgTDI0NiAxNjAgTDIzNiAxNzAgTDIxNSAxNjAgTDIyNSAxNDUgTDI0NSAxNDAgWiIgZmlsbD0iI2ZmY2MwMCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMTQwIDIwIEwxNDAgMTgwIE0xNjAgMjAgTDE2MCAxODAgTTE4MCAyMCBMMTgwIDE4MCBNMjAwIDIwIEwyMDAgMTgwIE0yMjAgMjAgTDIyMCAxODAgTTI0MCAyMCBMMjQwIDE4MCBNMjYwIDIwIEwyNjAgMTgwIiBzdHJva2U9IiMxZDVjOGQiIHN0cm9rZS13aWR0aD0iMSIvPjxwYXRoIGQ9Ik0xMjUgNDAgTDI4MCA0MCBNMTIwIDYwIEwyODAgNjAgTTEyMCA4MCBMMjgwIDgwIE0xMjAgMTAwIEwyODAgMTAwIE0xMjAgMTIwIEwyODAgMTIwIE0xMjAgMTQwIEwyODAgMTQwIE0xMjAgMTYwIEwyODAgMTYwIiBzdHJva2U9IiMxZDVjOGQiIHN0cm9rZS13aWR0aD0iMSIvPjwvc3ZnPg==", description: "The Ishtar Gate was the eighth gate to the inner city of Babylon, constructed in 575 BCE by order of King Nebuchadnezzar II. This magnificent structure was dedicated to the Babylonian goddess Ishtar and formed part of the processional way leading to the temple of Marduk.", facts: [ { label: "Origin", value: "Babylon, present-day Iraq" }, { label: "Builder", value: "King Nebuchadnezzar II" }, { label: "Material", value: "Glazed brick with blue lapis lazuli glaze" }, { label: "Features", value: "Rows of dragons and aurochs in glazed relief" }, { label: "Current location", value: "Pergamon Museum, Berlin (reconstructed)" } ] }, { id: 5, title: "Warka Vase", period: "3200-3000 BCE, Uruk Period", image: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YyZTlkZiIvPjxwYXRoIGQ9Ik0xNTAgMTgwIEMxNTAgMTUwIDE1MCAxMzAgMTcwIDExMCBDMTgwIDEwMCAyMjAgMTAwIDIzMCAxMTAgQzI1MCAxMzAgMjUwIDE1MCAyNTAgMTgwIFoiIGZpbGw9IiNkMmI3OWQiLz48cGF0aCBkPSJNMTcwIDExMCBDMTgwIDkwIDE4MCA1MCAyMDAgMzAgQzIyMCA1MCAyMjAgOTAgMjMwIDExMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzBiOG
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Luxury Timepiece Showcase</title> <style> @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Playfair+Display:wght@400;700&display=swap'); :root { --primary: #121212; --secondary: #f8f8f8; --accent: #e6c270; --accent-dark: #d4a942; --text: #333333; --light-gray: #e0e0e0; --transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Montserrat', sans-serif; background-color: var(--secondary); color: var(--text); height: 100%; overflow-x: hidden; line-height: 1.6; } .container { width: 100%; max-width: 700px; height: 700px; margin: 0 auto; position: relative; overflow: hidden; display: flex; flex-direction: column; } .header { background-color: var(--primary); color: var(--secondary); padding: 1.5rem; text-align: center; position: relative; } .header h1 { font-family: 'Playfair Display', serif; font-size: 1.8rem; letter-spacing: 1px; margin-bottom: 0.5rem; } .header p { font-size: 0.9rem; opacity: 0.9; max-width: 80%; margin: 0 auto; } .product-showcase { flex: 1; display: flex; flex-direction: column; position: relative; background-color: var(--secondary); overflow-y: auto; } .product-image-container { position: relative; width: 100%; height: 60%; overflow: hidden; background-color: var(--primary); } .product-image { width: 100%; height: 100%; object-fit: cover; transition: transform 0.8s ease; } .hotspot { position: absolute; width: 24px; height: 24px; background-color: var(--accent); border-radius: 50%; transform: translate(-50%, -50%); cursor: pointer; z-index: 2; box-shadow: 0 0 0 rgba(230, 194, 112, 0.6); animation: pulse 2s infinite; transition: var(--transition); } .hotspot:hover { background-color: var(--accent-dark); transform: translate(-50%, -50%) scale(1.2); } .hotspot::after { content: "+"; color: var(--primary); position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-weight: bold; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(230, 194, 112, 0.6); } 70% { box-shadow: 0 0 0 10px rgba(230, 194, 112, 0); } 100% { box-shadow: 0 0 0 0 rgba(230, 194, 112, 0); } } .product-details { padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; background-color: var(--secondary); height: 40%; overflow-y: auto; } .detail-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--light-gray); padding-bottom: 0.5rem; } .product-name { font-family: 'Playfair Display', serif; font-size: 1.5rem; font-weight: 700; } .product-price { font-size: 1.25rem; font-weight: 600; color: var(--accent-dark); } .ratings { display: flex; align-items: center; gap: 0.5rem; } .stars { color: var(--accent); letter-spacing: 2px; } .review-count { font-size: 0.9rem; color: #777; } .product-description { font-size: 0.95rem; line-height: 1.5; } .cta-buttons { display: flex; gap: 1rem; margin-top: 0.5rem; } .btn { padding: 0.75rem 1.25rem; border: none; border-radius: 4px; font-family: 'Montserrat', sans-serif; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: var(--transition); flex: 1; display: flex; justify-content: center; align-items: center; gap: 0.5rem; } .btn-primary { background-color: var(--accent); color: var(--primary); } .btn-primary:hover { background-color: var(--accent-dark); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(230, 194, 112, 0.3); } .btn-secondary { background-color: transparent; color: var(--primary); border: 1px solid var(--primary); } .btn-secondary:hover { background-color: var(--primary); color: var(--secondary); transform: translateY(-2px); } .btn-icon { font-size: 1.1rem; } .tooltip { position: absolute; background-color: rgba(18, 18, 18, 0.95); color: var(--secondary); padding: 1rem; border-radius: 4px; font-size: 0.85rem; max-width: 220px; z-index: 10; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); opacity: 0; pointer-events: none; transition: var(--transition); transform: translateY(10px); } .tooltip.active { opacity: 1; pointer-events: auto; transform: translateY(0); } .tooltip h3 { font-family: 'Playfair Display', serif; margin-bottom: 0.5rem; font-size: 1rem; color: var(--accent); } .tooltip p { margin-bottom: 0.5rem; line-height: 1.4; } .tooltip::after { content: ""; position: absolute; width: 12px; height: 12px; background-color: rgba(18, 18, 18, 0.95); transform: rotate(45deg); } .tooltip-top::after { bottom: -6px; left: 50%; margin-left: -6px; } .tooltip-bottom::after { top: -6px; left: 50%; margin-left: -6px; } .tooltip-left::after { right: -6px; top: 50%; margin-top: -6px; } .tooltip-right::after { left: -6px; top: 50%; margin-top: -6px; } .review-preview { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; } .review-avatar { width: 32px; height: 32px; border-radius: 50%; background-color: var(--light-gray); display: flex; align-items: center; justify-content: center; font-weight: bold; color: var(--primary); } .review-text { font-style: italic; font-size: 0.85rem; } .zoom-view { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(18, 18, 18, 0.95); z-index: 100; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: var(--transition); } .zoom-view.active { opacity: 1; pointer-events: auto; } .zoom-image { max-width: 90%; max-height: 90%; object-fit: contain; transform: scale(0.9); transition: transform 0.4s ease; } .zoom-view.active .zoom-image { transform: scale(1); } .zoom-close { position: absolute; top: 20px; right: 20px; color: var(--secondary); font-size: 2rem; cursor: pointer; transition: var(--transition); } .zoom-close:hover { color: var(--accent); transform: rotate(90deg); } .color-options { display: flex; gap: 0.5rem; margin: 0.5rem 0; } .color-option { width: 24px; height: 24px; border-radius: 50%; cursor: pointer; transition: var(--transition); position: relative; } .color-option:hover { transform: scale(1.2); } .color-option.selected { border: 2px solid var(--primary); } .color-option.selected::after { content: "✓"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 12px; text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); } .feature-tag { display: inline-block; background-color: var(--light-gray); color: var(--text); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; margin-right: 0.5rem; margin-bottom: 0.5rem; } .product-tabs { display: flex; border-bottom: 1px solid var(--light-gray); margin-bottom: 1rem; } .tab { padding: 0.5rem 1rem; cursor: pointer; font-size: 0.9rem; transition: var(--transition); } .tab.active { border-bottom: 2px solid var(--accent); color: var(--accent-dark); font-weight: 600; } .tab-content { display: none; } .tab-content.active { display: block; } /* Responsive adjustments */ @media (max-width: 500px) { .header h1 { font-size: 1.5rem; } .header p { font-size: 0.8rem; } .product-image-container { height: 50%; } .product-details { height: 50%; } .product-name { font-size: 1.2rem; } .cta-buttons { flex-direction: column; gap: 0.5rem; } .btn { padding: 0.6rem 1rem; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>Precision Luxury Timepieces</h1> <p>Explore detailed craftsmanship through interactive elements</p> </div> <div class="product-showcase"> <div class="product-image-container"> <img src="https://images.unsplash.com/photo-1523170335258-f5ed11844a49?ixlib=rb-1.2.1&auto=format&fit=crop&w=1080&q=80" alt="Luxury Watch" class="product-image" id="main-product-image"> <!-- Hotspots --> <div class="hotspot" style="top: 50%; left: 50%;" data-tooltip="dial"></div> <div class="hotspot" style="top: 30%; left: 70%;" data-tooltip="crown"></div> <div class="hotspot" style="top: 65%; left: 35%;" data-tooltip="strap"></div> </div> <div class="product-details"> <div class="detail-header"> <div class="product-name">Chronograph Edition VII</div> <div class="product-price">$2,195</div> </div> <div class="ratings"> <div class="stars">★★★★★</div> <div class="review-count">42 reviews</div> </div> <div class="product-tabs"> <div class="tab active" data-tab="overview">Overview</div> <div class="tab" data-tab="specs">Specifications</div> <div class="tab" data-tab="reviews">Reviews</div> </div> <div class="tab-content active" id="overview"> <p class="product-description"> Hand-crafted in Switzerland with meticulous attention to detail, this timepiece combines vintage aesthetics with modern precision. The anti-reflective sapphire crystal ensures perfect readability in any environment. </p> <div class="feature-tags"> <span class="feature-tag">Swiss Movement</span> <span class="feature-tag">Sapphire Crystal</span> <span class="feature-tag">100m Water-Resistant</span> <span class="feature-tag">Italian Leather</span> </div> <div class="color-options"> <div class="color-option selected" style="background-color: #703F37;"></div> <div class="color-option" style="background-color: #0F0F0F;"></div> <div class="color-option" style="background-color: #1F2937;"></div> </div> </div> <div class="tab-content" id="specs"> <ul class="specs-list"> <li><strong>Case:</strong> 41mm stainless steel</li> <li><strong>Movement:</strong> Swiss Automatic (ETA 2824-2)</li> <li><strong>Power Reserve:</strong> 42 hours</li> <li><strong>Water Resistance:</strong> 100 meters</li> <li><strong>Crystal:</strong> Sapphire with anti-reflective coating</li> <li><strong>Strap:</strong> Handmade Italian leather with quick-release</li> </ul> </div> <div class="tab-content" id="reviews"> <div class="review-item"> <div class="review-preview"> <div class="review-avatar">JL</div> <div class="stars">★★★★★</div> </div> <p class="review-text">"The attention to detail is phenomenal. The dial catches light beautifully, and the movement is incredibly precise."</p> </div> <div class="review-item"> <div class="review-preview"> <div class="review-avatar">MK</div> <div class="stars">★★★★★</div> </div> <p class="review-text">"Easily rivals watches three times its price. The leather strap aged beautifully after six months of daily wear."</p> </div> </div> <div class="cta-buttons"> <button class="btn btn-primary"> <span class="btn-icon">↓</span> Add to Cart </button> <button class="btn btn-secondary"> <span class="btn-icon">♡</span> Save </button> </div> </div> <!-- Tooltips --> <div class="tooltip" id="dial-tooltip"> <h3>Guilloché Dial</h3> <p>Hand-engraved with a traditional rose engine for a subtle, textured pattern that catches light from every angle.</p> <div class="review-preview"> <div class="review-avatar">TM</div> <p class="review-text">"The dial pattern is hypnotic in direct sunlight."</p> </div> </div> <div class="tooltip" id="crown-tooltip"> <h3>Fluted Crown</h3> <p>Precision-engineered crown with 24 flutes for optimal grip when setting time or winding. Engraved with our signature motif.</p> <div class="review-preview"> <div class="review-avatar">SC</div> <p class="review-text">"The crown action is buttery smooth, unlike any other watch I've owned."</p> </div> </div> <div class="tooltip" id="strap-tooltip"> <h3>Shell Cordovan Strap</h3> <p>Handcrafted from premium horse leather known for its durability and distinctive patina development over time.</p> <div class="review-preview"> <div class="review-avatar">RL</div> <p class="review-text">"After a year, the strap has developed a beautiful character that makes it even more special."</p> </div> </div> </div> <!-- Zoom View --> <div class="zoom-view" id="zoom-view"> <img src="" alt="Detailed View" class="zoom-image" id="zoom-image"> <div class="zoom-close" id="zoom-close">×</div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Hotspots and tooltips functionality const hotspots = document.querySelectorAll('.hotspot'); const tooltips = document.querySelectorAll('.tooltip'); const productImage = document.getElementById('main-product-image'); const zoomView = document.getElementById('zoom-view'); const zoomImage = document.getElementById('zoom-image'); const zoomClose = document.getElementById('zoom-close'); const colorOptions = document.querySelectorAll('.color-option'); const tabs = document.querySelectorAll('.tab'); const tabContents = document.querySelectorAll('.tab-content'); // Watch color variations const watchColors = { '#703F37': 'https://images.unsplash.com/photo-1523170335258-f5ed11844a49?ixlib=rb-1.2.1&auto=format&fit=crop&w=1080&q=80', '#0F0F0F': 'https://images.unsplash.com/photo-1587836374828-4dbafa94cf0e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1080&q=80', '#1F2937': 'https://images.unsplash.com/photo-1548169874-53e85f753f1e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1080&q=80' }; // Position tooltips relative to hotspots function positionTooltip(hotspot, tooltip) { const hotspotRect = hotspot.getBoundingClientRect(); const containerRect = document.querySelector('.product-image-container').getBoundingClientRect(); const hotspotCenterX = hotspotRect.left + (hotspotRect.width / 2) - containerRect.left; const hotspotCenterY = hotspotRect.top + (hotspotRect.height / 2) - containerRect.top; const containerWidth = containerRect.width; const containerHeight = containerRect.height; const tooltipWidth = 220; // max-width of tooltip const tooltipHeight = 150; // approximate height let tooltipClass = ''; let tooltipX, tooltipY; // Position tooltip based on hotspot location in the container if (hotspotCenterY < containerHeight / 2) { // Hotspot is in the top half tooltipY = hotspotCenterY + 20; tooltipClass = 'tooltip-top'; } else { // Hotspot is in the bottom half tooltipY = hotspotCenterY - tooltipHeight - 20; tooltipClass = 'tooltip-bottom'; } if (hotspotCenterX < containerWidth / 2) { // Hotspot is in the left half tooltipX = hotspotCenterX + 20; tooltipClass += ' tooltip-left'; } else { // Hotspot is in the right half tooltipX = hotspotCenterX - tooltipWidth - 20; tooltipClass += ' tooltip-right'; } // Apply positioning tooltip.style.left = `${tooltipX}px`; tooltip.style.top = `${tooltipY}px`; // Clear existing classes and add new ones tooltip.className = 'tooltip ' + tooltipClass; } // Hotspot event listeners hotspots.forEach(hotspot => { const tooltipId = `${hotspot.dataset.tooltip}-tooltip`; const tooltip = document.getElementById(tooltipId); hotspot.addEventListener('mouseenter', () => { // Hide all tooltips first tooltips.forEach(t => t.classList.remove('active')); // Position and show this tooltip positionTooltip(hotspot, tooltip); tooltip.classList.add('active'); }); hotspot.addEventListener('mouseleave', () => { tooltip.classList.remove('active'); }); hotspot.addEventListener('click', () => { // Set zoom image to the current product image zoomImage.src = productImage.src; zoomView.classList.add('active'); }); }); // Close zoom view zoomClose.addEventListener('click', () => { zoomView.classList.remove('active'); }); zoomView.addEventListener('click', (e) => { if (e.target === zoomView) { zoomView.classList.remove('active'); } }); // Color options colorOptions.forEach(option => { option.addEventListener('click', () => { // Update selected state colorOptions.forEach(opt => opt.classList.remove('selected')); option.classList.add('selected'); // Change product image const color = option.style.backgroundColor; if (watchColors[color]) { productImage.src = watchColors[color]; } }); }); // Tabs functionality tabs.forEach(tab => { tab.addEventListener('click', () => { // Update active tab tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Show corresponding content const tabId = tab.dataset.tab; tabContents.forEach(content => { content.classList.remove('active'); if (content.id === tabId) { content.classList.add('active'); } }); }); }); // Image zoom effect on hover productImage.addEventListener('mouseenter', () => { productImage.style.transform = 'scale(1.05)'; }); productImage.addEventListener('mouseleave', () => { productImage.style.transform = 'scale(1)'; }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Wanderlust Explorer: Interactive Travel Map</title> <style> :root { --primary: #ff7e5f; --primary-light: #feb47b; --secondary: #2c3e50; --accent: #3498db; --light: #ecf0f1; --dark: #1a252f; --success: #2ecc71; --text: #34495e; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: var(--light); color: var(--text); width: 100%; height: 100%; overflow: hidden; } .container { width: 700px; height: 700px; margin: 0 auto; position: relative; overflow: hidden; background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); border-radius: 20px; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); } .map-container { position: relative; width: 100%; height: 100%; overflow: hidden; transition: transform 0.5s ease; } .map { width: 100%; height: 100%; 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"><rect width="700" height="700" fill="%23e8f4f8"/><path d="M100,200 Q250,150 350,250 T600,300 T450,450 T200,400 T100,200" fill="%23c9e4ca" stroke="%2384b59f" stroke-width="3"/><path d="M250,100 Q300,150 400,170 T500,150 T550,250 T450,350 T300,300 T250,100" fill="%23f6c28b" stroke="%23ed9e6c" stroke-width="3"/><circle cx="200" cy="150" r="40" fill="%23b9e0ff" stroke="%235d8aa8" stroke-width="2"/><circle cx="500" cy="400" r="60" fill="%23b9e0ff" stroke="%235d8aa8" stroke-width="2"/><path d="M150,350 Q200,400 300,450 T450,500 T550,450" fill="none" stroke="%235d8aa8" stroke-width="3" stroke-dasharray="5,5"/></svg>'); background-size: cover; position: relative; transition: transform 0.3s ease-out; } .hotspot { position: absolute; width: 40px; height: 40px; border-radius: 50%; background: var(--primary); cursor: pointer; transform: translate(-50%, -50%); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 10; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; border: 3px solid white; } .hotspot::after { content: ""; position: absolute; width: 100%; height: 100%; border-radius: 50%; background-color: var(--primary-light); z-index: -1; opacity: 0.6; animation: pulse 2s infinite; } .hotspot:hover { transform: translate(-50%, -50%) scale(1.1); background: var(--accent); } .hotspot:active { transform: translate(-50%, -50%) scale(0.95); } .popup { position: absolute; background: white; width: 320px; border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); z-index: 20; opacity: 0; transform: translateY(20px); pointer-events: none; transition: all 0.3s ease; overflow: hidden; max-height: 70%; overflow-y: auto; } .popup.active { opacity: 1; transform: translateY(0); pointer-events: all; } .popup-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid var(--primary-light); } .popup-title { font-size: 22px; font-weight: 700; color: var(--secondary); margin: 0; } .close-btn { background: none; border: none; font-size: 20px; color: var(--secondary); cursor: pointer; transition: color 0.2s; } .close-btn:hover { color: var(--primary); } .popup-img { width: 100%; height: 150px; object-fit: cover; border-radius: 10px; margin-bottom: 15px; } .tab-navigation { display: flex; margin-bottom: 15px; border-bottom: 1px solid #e1e1e1; } .tab-btn { padding: 8px 15px; background: none; border: none; font-weight: 600; color: var(--text); cursor: pointer; position: relative; transition: all 0.2s; } .tab-btn.active { color: var(--primary); } .tab-btn.active::after { content: ''; position: absolute; bottom: -1px; left: 0; width: 100%; height: 3px; background-color: var(--primary); } .tab-content { display: none; } .tab-content.active { display: block; animation: fadeIn 0.3s ease forwards; } .itinerary-day { margin-bottom: 15px; } .day-title { font-weight: 600; color: var(--secondary); margin-bottom: 8px; display: flex; align-items: center; } .day-title::before { content: ''; display: inline-block; width: 12px; height: 12px; background: var(--primary); border-radius: 50%; margin-right: 8px; } .itinerary-item { padding-left: 20px; position: relative; margin-bottom: 8px; } .itinerary-item::before { content: ''; position: absolute; left: 6px; top: 8px; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); } .local-tip { background: rgba(255, 126, 95, 0.1); border-left: 3px solid var(--primary); padding: 12px 15px; margin-bottom: 15px; border-radius: 0 8px 8px 0; } .tip-title { font-weight: 600; color: var(--primary); margin-bottom: 5px; display: flex; align-items: center; } .tip-title svg { margin-right: 5px; } .controls { position: absolute; bottom: 20px; right: 20px; display: flex; gap: 10px; z-index: 15; } .control-btn { width: 40px; height: 40px; border-radius: 50%; background: white; border: none; display: flex; align-items: center; justify-content: center; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); cursor: pointer; transition: all 0.2s; } .control-btn:hover { background: var(--primary-light); transform: translateY(-2px); } .control-btn:active { transform: translateY(0); } .map-title { position: absolute; top: 20px; left: 20px; z-index: 15; background: rgba(255, 255, 255, 0.9); padding: 10px 15px; border-radius: 8px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); font-size: 18px; font-weight: 700; color: var(--secondary); transition: all 0.3s; cursor: default; } .map-title:hover { background: white; transform: translateY(-2px); } .legend { position: absolute; bottom: 20px; left: 20px; background: rgba(255, 255, 255, 0.9); padding: 10px; border-radius: 8px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); z-index: 15; font-size: 14px; } .legend-item { display: flex; align-items: center; margin-bottom: 5px; } .legend-color { width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; } @keyframes pulse { 0% { transform: scale(1); opacity: 0.6; } 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(1); opacity: 0; } } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } @keyframes bounce { 0%, 100% { transform: translate(-50%, -50%); } 50% { transform: translate(-50%, -60%); } } /* Responsive adjustments */ @media (max-width: 700px) { .popup { width: 280px; max-height: 60%; } .popup-img { height: 120px; } .hotspot { width: 30px; height: 30px; } .map-title { font-size: 16px; padding: 8px 12px; } .legend { padding: 8px; font-size: 12px; } } </style> </head> <body> <div class="container"> <div class="map-container"> <div class="map" id="map"> <!-- Hotspots --> <div class="hotspot" style="top: 30%; left: 20%;" data-destination="kyoto"> <span>1</span> </div> <div class="hotspot" style="top: 45%; left: 35%;" data-destination="santorini"> <span>2</span> </div> <div class="hotspot" style="top: 60%; left: 65%;" data-destination="costa-rica"> <span>3</span> </div> <div class="hotspot" style="top: 28%; left: 75%;" data-destination="new-zealand"> <span>4</span> </div> <div class="hotspot" style="top: 70%; left: 25%;" data-destination="marrakech"> <span>5</span> </div> </div> <div class="map-title"> <span>Wanderlust Explorer</span> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background: #c9e4ca;"></div> <span>Forests & Parks</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #f6c28b;"></div> <span>Desert & Mountains</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #b9e0ff;"></div> <span>Lakes & Oceans</span> </div> </div> <div class="controls"> <button class="control-btn" id="zoom-in"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 5V19M5 12H19" stroke="#2c3e50" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <button class="control-btn" id="zoom-out"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M5 12H19" stroke="#2c3e50" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <button class="control-btn" id="reset"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z" stroke="#2c3e50" stroke-width="2"/> <path d="M15 9L9 15M9 9L15 15" stroke="#2c3e50" stroke-width="2" stroke-linecap="round"/> </svg> </button> </div> </div> <!-- Popups --> <div class="popup" id="popup-kyoto"> <div class="popup-header"> <h3 class="popup-title">Kyoto, Japan</h3> <button class="close-btn">×</button> </div> <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='150' viewBox='0 0 320 150'><rect width='320' height='150' fill='%23f3d2c1'/><path d='M0,100 Q80,80 160,100 T320,90' fill='%23fcd5ce' stroke='%23f8edeb'/><rect x='50' y='70' width='40' height='30' fill='%23f5cac3'/><rect x='90' y='65' width='30' height='35' fill='%23f28482'/><path d='M60,70 L80,50 L100,70' fill='%23f28482' stroke='%23f28482'/><rect x='200' y='60' width='50' height='40' fill='%23f5cac3'/><path d='M200,60 L225,30 L250,60' fill='%23f28482' stroke='%23f28482'/><circle cx='40' cy='30' r='15' fill='%23fcd5ce'/></svg>" alt="Kyoto, Japan" class="popup-img"> <div class="tab-navigation"> <button class="tab-btn active" data-tab="itinerary-kyoto">Itinerary</button> <button class="tab-btn" data-tab="tips-kyoto">Local Tips</button> </div> <div class="tab-content active" id="itinerary-kyoto"> <div class="itinerary-day"> <h4 class="day-title">Day 1: Eastern Kyoto</h4> <div class="itinerary-item">Kiyomizu-dera Temple at sunrise</div> <div class="itinerary-item">Walk through Higashiyama District</div> <div class="itinerary-item">Tea ceremony in Gion</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 2: Arashiyama & Bamboo Forest</h4> <div class="itinerary-item">Bamboo Grove walk (early morning)</div> <div class="itinerary-item">Tenryu-ji Temple gardens</div> <div class="itinerary-item">Monkey Park Iwatayama</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 3: Northern Temples</h4> <div class="itinerary-item">Golden Pavilion (Kinkaku-ji)</div> <div class="itinerary-item">Ryoan-ji Zen Rock Garden</div> <div class="itinerary-item">Fushimi Inari Shrine evening visit</div> </div> </div> <div class="tab-content" id="tips-kyoto"> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Timing is Everything </h4> <p>Visit Fushimi Inari after 4pm when tour groups leave. The vermillion gates are magical at dusk with fewer crowds.</p> </div> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Transportation Hack </h4> <p>Purchase a 1-day bus pass (¥600) at Kyoto Station — most temples are far apart but well-connected by bus network.</p> </div> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Hidden Gem </h4> <p>Explore Pontocho Alley at night for authentic local dining. Look for restaurants with counter seating to watch chefs prepare Kyoto specialties.</p> </div> </div> </div> <div class="popup" id="popup-santorini"> <div class="popup-header"> <h3 class="popup-title">Santorini, Greece</h3> <button class="close-btn">×</button> </div> <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='150' viewBox='0 0 320 150'><rect width='320' height='150' fill='%233b83bd'/><path d='M0,100 Q80,95 160,105 T320,100' fill='%23f2f4f5' stroke='%23fff'/><rect x='40' y='70' width='30' height='30' fill='%23fff'/><rect x='70' y='65' width='25' height='35' fill='%23fff'/><rect x='95' y='75' width='35' height='25' fill='%23fff'/><path d='M75,65 Q85,55 95,65' fill='%23fff' stroke='%23fff'/><path d='M105,75 Q112,65 120,75' fill='%23fff' stroke='%23fff'/><rect x='200' y='60' width='70' height='40' fill='%23fff'/><path d='M220,60 Q235,50 250,60' fill='%23fff' stroke='%23fff'/><rect x='235' y='80' width='10' height='20' fill='%231a508b'/><rect x='210' y='70' width='10' height='15' fill='%231a508b'/><circle cx='270' cy='40' r='15' fill='%23fcf6bd'/></svg>" alt="Santorini, Greece" class="popup-img"> <div class="tab-navigation"> <button class="tab-btn active" data-tab="itinerary-santorini">Itinerary</button> <button class="tab-btn" data-tab="tips-santorini">Local Tips</button> </div> <div class="tab-content active" id="itinerary-santorini"> <div class="itinerary-day"> <h4 class="day-title">Day 1: Oia & Sunset</h4> <div class="itinerary-item">Morning stroll through Oia's marble streets</div> <div class="itinerary-item">Visit Byzantine Castle ruins</div> <div class="itinerary-item">Famous sunset viewing at Oia Castle</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 2: Volcanic Adventures</h4> <div class="itinerary-item">Catamaran sail to volcanic islands</div> <div class="itinerary-item">Hot springs swim at Palea Kameni</div> <div class="itinerary-item">Dinner in Ammoudi Bay's tavernas</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 3: Ancient History & Wine</h4> <div class="itinerary-item">Ancient Akrotiri archaeological site</div> <div class="itinerary-item">Red Beach swimming</div> <div class="itinerary-item">Wine tasting tour through volcanic vineyards</div> </div> </div> <div class="tab-content" id="tips-santorini"> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Sunset Alternative </h4> <p>Skip the crowds at Oia Castle and head to Skaros Rock in Imerovigli for equally stunning sunset views with a fraction of the crowds.</p> </div> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Wine Insight </h4> <p>Don't miss Assyrtiko, Santorini's indigenous grape. The volcanic soil gives it a distinct minerality. Santo Wines offers the best tasting experience with caldera views.</p> </div> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Local Dining </h4> <p>For authentic food without the tourist markup, eat in Megalochori village. Try tomato keftedes (fritters) and fava (split pea purée) — Santorini specialties.</p> </div> </div> </div> <div class="popup" id="popup-costa-rica"> <div class="popup-header"> <h3 class="popup-title">Costa Rica</h3> <button class="close-btn">×</button> </div> <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='150' viewBox='0 0 320 150'><rect width='320' height='150' fill='%2357cc99'/><path d='M0,100 Q80,85 160,100 T320,90' fill='%2338a3a5' stroke='%2322577a'/><rect x='20' y='50' width='80' height='50' fill='%2322577a'/><path d='M20,50 Q60,20 100,50' fill='%2322577a' stroke='%2322577a'/><path d='M220,80 Q240,70 260,80 Q280,90 300,70' fill='none' stroke='%23c7f9cc' stroke-width='2'/><circle cx='280' cy='30' r='15' fill='%23fcf7bb'/><path d='M240,85 L250,65 L260,85 Z' fill='%2380ed99'/><path d='M200,95 L200,75 L215,75 L215,95 Z' fill='%2380ed99'/><path d='M150,100 L155,70 L180,70 L180,100 Z' fill='%2380ed99'/></svg>" alt="Costa Rica" class="popup-img"> <div class="tab-navigation"> <button class="tab-btn active" data-tab="itinerary-costa-rica">Itinerary</button> <button class="tab-btn" data-tab="tips-costa-rica">Local Tips</button> </div> <div class="tab-content active" id="itinerary-costa-rica"> <div class="itinerary-day"> <h4 class="day-title">Day 1-2: Arenal Volcano</h4> <div class="itinerary-item">La Fortuna Waterfall hike</div> <div class="itinerary-item">Arenal hanging bridges canopy tour</div> <div class="itinerary-item">Evening soak in natural hot springs</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 3-4: Monteverde Cloud Forest</h4> <div class="itinerary-item">Selvatura Park zipline adventure</div> <div class="itinerary-item">Guided night hike to spot nocturnal wildlife</div> <div class="itinerary-item">Visit hummingbird gardens</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 5-7: Manuel Antonio</h4> <div class="itinerary-item">Manuel Antonio National Park guided tour</div> <div class="itinerary-item">Catamaran sunset sail with snorkeling</div> <div class="itinerary-item">Beach day at Playa Espadilla</div> </div> </div> <div class="tab-content" id="tips-costa-rica"> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Wildlife Viewing </h4> <p>Visit Manuel Antonio on Tuesdays when the park is least crowded. Hire a guide with a spotting scope — they find camouflaged sloths and tiny frogs visitors miss.</p> </div> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Seasonal Advice </h4> <p>Green season (May-November) offers better wildlife viewing and fewer tourists. Morning activities are best as afternoon rain showers are common but brief.</p> </div> <div class="local-tip"> <h4 class="tip-title"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ff7e5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Local Cuisine </h4> <p>Try a "soda" - a small family-run restaurant serving casado (rice, beans, protein, plantains). In La Fortuna, Soda Viquez offers the most authentic version for half the tourist price.</p> </div> </div> </div> <div class="popup" id="popup-new-zealand"> <div class="popup-header"> <h3 class="popup-title">New Zealand</h3> <button class="close-btn">×</button> </div> <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='150' viewBox='0 0 320 150'><rect width='320' height='150' fill='%23aad576'/><path d='M0,90 Q80,60 160,90 T320,80' fill='%23543d18' stroke='%23543d18'/><path d='M20,60 L50,20 L80,60' fill='%2373a942' stroke='%2373a942'/><path d='M100,50 L130,15 L160,50' fill='%2373a942' stroke='%2373a942'/><path d='M200,45 L240,10 L280,45' fill='%2373a942' stroke='%2373a942'/><rect x='185' y='80' width='80' height='10' fill='%23543d18'/><circle cx='250' cy='30' r='15' fill='%23f8f8f8'/><path d='M150,90 L145,70 L155,70 Z' fill='%2358641d'/><path d='M170,90 L165,55 L180,55 Z' fill='%2358641d'/><path d='M40,90 L30,40 L50,40 Z' fill='%2358641d'/></svg>" alt="New Zealand" class="popup-img"> <div class="tab-navigation"> <button class="tab-btn active" data-tab="itinerary-new-zealand">Itinerary</button> <button class="tab-btn" data-tab="tips-new-zealand">Local Tips</button> </div> <div class="tab-content active" id="itinerary-new-zealand"> <div class="itinerary-day"> <h4 class="day-title">Day 1-3: Queenstown Region</h4> <div class="itinerary-item">Milford Sound day cruise</div> <div class="itinerary-item">Glenorchy Lord of the Rings locations</div> <div class="itinerary-item">Arrowtown historic gold mining village</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 4-5: West Coast Glaciers</h4> <div class="itinerary-item">Fox Glacier guided hike</div> <div class="itinerary-item">Lake Matheson mirror reflections at dawn</div> <div class="itinerary-item">Hokitika Gorge turquoise waters</div> </div> <div class="itinerary-day"> <h4 class="day-title">Day 6-8: Marlborough &
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive Architectural Blueprint</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; background-color: #f0f4f9; overflow: hidden; padding: 0.5rem; } .container { width: 700px; height: 700px; background-color: #fff; box-shadow: rgba(99, 99, 99, 0.1) 0px 2px 8px 0px; border-radius: 8px; display: flex; flex-direction: column; position: relative; overflow: hidden; } .header { padding: 1.5rem; border-bottom: 1px solid #e0e6ed; display: flex; justify-content: space-between; align-items: center; } .title { font-size: 1.5rem; font-weight: 600; color: #2c3e50; } .floor-selector { display: flex; gap: 0.5rem; } .floor-btn { padding: 0.5rem 1rem; background-color: #e0e6ed; border: none; border-radius: 4px; color: #5a6b80; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .floor-btn.active { background-color: #3498db; color: white; } .blueprint-container { flex: 1; position: relative; overflow: hidden; padding: 1.5rem; } .blueprint { width: 100%; height: 100%; position: relative; transform-origin: center; transition: transform 0.5s ease; } .room { position: absolute; border: 2px solid #3498db; background-color: rgba(52, 152, 219, 0.05); cursor: pointer; transition: all 0.3s ease; } .room:hover { background-color: rgba(52, 152, 219, 0.15); box-shadow: 0 0 0 4px rgba(52, 152, 219, 0.2); } .room.selected { border-color: #2ecc71; background-color: rgba(46, 204, 113, 0.15); } .room-label { position: absolute; font-size: 0.8rem; font-weight: 600; color: #2c3e50; text-transform: uppercase; pointer-events: none; } .wall { position: absolute; background-color: #34495e; } .details-panel { position: absolute; top: 0; right: -350px; width: 350px; height: 100%; background-color: white; box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1); padding: 1.5rem; transition: right 0.3s ease; z-index: 100; } .details-panel.open { right: 0; } .close-details { position: absolute; top: 1rem; right: 1rem; background: none; border: none; font-size: 1.5rem; color: #95a5a6; cursor: pointer; } .details-header { margin-bottom: 1.5rem; } .details-title { font-size: 1.2rem; font-weight: 600; color: #2c3e50; margin-bottom: 0.5rem; } .details-subtitle { font-size: 0.9rem; color: #7f8c8d; margin-bottom: 1.5rem; } .details-section { margin-bottom: 1.5rem; } .details-section-title { font-size: 1rem; font-weight: 600; color: #2c3e50; margin-bottom: 0.8rem; display: flex; align-items: center; } .details-section-title svg { margin-right: 0.5rem; color: #3498db; } .details-list { list-style: none; } .details-list li { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #ecf0f1; } .details-list li span:first-child { font-weight: 500; color: #34495e; } .details-list li span:last-child { color: #7f8c8d; } .technical-toggle { margin-top: 2rem; padding: 0.7rem 1rem; background-color: #ecf0f1; border: none; border-radius: 4px; font-weight: 500; color: #34495e; cursor: pointer; width: 100%; text-align: left; display: flex; justify-content: space-between; align-items: center; } .technical-toggle svg { transition: transform 0.3s ease; } .technical-info { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; } .technical-info.open { max-height: 500px; margin-top: 1rem; } .tech-note { background-color: #f8f9fa; padding: 0.8rem 1rem; border-radius: 4px; margin-bottom: 0.8rem; border-left: 3px solid #3498db; font-size: 0.9rem; color: #34495e; } .controls { position: absolute; bottom: 1.5rem; left: 1.5rem; display: flex; gap: 0.5rem; z-index: 50; } .control-btn { width: 40px; height: 40px; border-radius: 50%; background-color: white; border: none; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); display: flex; justify-content: center; align-items: center; cursor: pointer; color: #34495e; transition: all 0.2s ease; } .control-btn:hover { background-color: #3498db; color: white; } .tooltip { position: absolute; background-color: #2c3e50; color: white; padding: 0.5rem 0.7rem; border-radius: 4px; font-size: 0.8rem; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; z-index: 200; white-space: nowrap; } @media (max-width: 700px) { .header { padding: 1rem; } .title { font-size: 1.2rem; } .blueprint-container { padding: 1rem; } .details-panel { width: 100%; right: -100%; } .details-panel.open { right: 0; } } .watermark { position: absolute; bottom: 1rem; right: 1.5rem; font-size: 0.8rem; color: #bdc3c7; z-index: 50; pointer-events: none; } .loading-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.9); display: flex; justify-content: center; align-items: center; z-index: 1000; transition: opacity 0.5s ease; } .loader { width: 48px; height: 48px; border: 5px solid #3498db; border-bottom-color: transparent; border-radius: 50%; animation: rotation 1s linear infinite; } @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .grid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: repeating-linear-gradient( 0deg, rgba(203, 213, 224, 0.1), rgba(203, 213, 224, 0.1) 1px, transparent 1px, transparent 25px ), repeating-linear-gradient( 90deg, rgba(203, 213, 224, 0.1), rgba(203, 213, 224, 0.1) 1px, transparent 1px, transparent 25px ); pointer-events: none; } .compass { position: absolute; top: 1.5rem; left: 1.5rem; width: 60px; height: 60px; z-index: 50; } .compass-circle { width: 100%; height: 100%; border-radius: 50%; background-color: white; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); display: flex; justify-content: center; align-items: center; position: relative; } .compass-needle { position: absolute; width: 2px; height: 40%; background-color: #e74c3c; bottom: 50%; left: 50%; transform: translateX(-50%); transform-origin: bottom center; } .compass-needle:after { content: ''; position: absolute; top: -5px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 8px solid #e74c3c; } .compass-needle.south { background-color: #34495e; transform: translateX(-50%) rotate(180deg); } .compass-needle.south:after { border-bottom-color: #34495e; } .cardinal { position: absolute; font-size: 0.7rem; font-weight: 600; } .cardinal.n { top: 5px; left: 50%; transform: translateX(-50%); } .cardinal.e { right: 5px; top: 50%; transform: translateY(-50%); } .cardinal.s { bottom: 5px; left: 50%; transform: translateX(-50%); } .cardinal.w { left: 5px; top: 50%; transform: translateY(-50%); } .door { position: absolute; width: 20px; height: 2px; background-color: #7f8c8d; transform-origin: 0 0; } .door:before { content: ''; position: absolute; width: 20px; height: 20px; border: 2px solid #7f8c8d; border-radius: 20px 0 0 0; border-right: none; border-bottom: none; transform-origin: 0 0; transform: rotate(0deg); animation: doorSwing 4s ease-in-out infinite; } @keyframes doorSwing { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(90deg); } } .pulse { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(46, 204, 113, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(46, 204, 113, 0); } 100% { box-shadow: 0 0 0 0 rgba(46, 204, 113, 0); } } </style> </head> <body> <div class="container"> <div class="loading-overlay"> <div class="loader"></div> </div> <div class="header"> <div class="title">Elliot Office Tower<span style="font-weight: 400; color: #95a5a6; font-size: 1rem; margin-left: 0.5rem;">Blueprint View</span></div> <div class="floor-selector"> <button class="floor-btn" data-floor="1">1F</button> <button class="floor-btn active" data-floor="2">2F</button> <button class="floor-btn" data-floor="3">3F</button> </div> </div> <div class="blueprint-container"> <div class="compass"> <div class="compass-circle"> <div class="compass-needle"></div> <div class="compass-needle south"></div> <span class="cardinal n">N</span> <span class="cardinal e">E</span> <span class="cardinal s">S</span> <span class="cardinal w">W</span> </div> </div> <div class="grid"></div> <div class="blueprint" id="blueprint"> <!-- Walls and rooms will be dynamically created by JavaScript --> </div> <div class="controls"> <button class="control-btn" id="zoom-in"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg> </button> <button class="control-btn" id="zoom-out"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg> </button> <button class="control-btn" id="reset-view"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path><path d="M3 3v5h5"></path></svg> </button> </div> <div class="tooltip" id="tooltip"></div> <div class="watermark">ELLIOT ARCHITECTS © 2024</div> </div> <div class="details-panel" id="details-panel"> <button class="close-details" id="close-details">×</button> <div class="details-header"> <div class="details-title" id="room-title">Conference Room A</div> <div class="details-subtitle" id="room-subtitle">Floor 2 • East Wing</div> </div> <div class="details-section"> <div class="details-section-title"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg> Room Specifications </div> <ul class="details-list" id="room-specs"> <li><span>Area</span><span>48 m²</span></li> <li><span>Dimensions</span><span>8m × 6m</span></li> <li><span>Ceiling Height</span><span>3.2m</span></li> <li><span>Capacity</span><span>16 people</span></li> </ul> </div> <div class="details-section"> <div class="details-section-title"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2"></circle></svg> Features </div> <ul class="details-list" id="room-features"> <li><span>Windows</span><span>North-facing</span></li> <li><span>Lighting</span><span>LED Recessed</span></li> <li><span>Flooring</span><span>Hardwood</span></li> <li><span>Wall Finish</span><span>Acoustic panels</span></li> </ul> </div> <button class="technical-toggle" id="tech-toggle"> Technical Information <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg> </button> <div class="technical-info" id="tech-info"> <div class="tech-note" id="tech-note-1">HVAC: VAV system with zone control. Supply diffusers on north wall, return grilles near south wall.</div> <div class="tech-note" id="tech-note-2">Power: 16 floor outlets with 120V/20A circuits. 8 wall outlets on dedicated circuits.</div> <div class="tech-note" id="tech-note-3">Data: Cat-6a cabling to floor boxes. WiFi access point mounted centrally on ceiling.</div> <div class="tech-note" id="tech-note-4">A/V: Integrated projection system. Motorized screen on east wall. Control panel by main entrance.</div> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Blueprint data for different floors const floors = { 1: { rooms: [ { id: 'lobby', name: 'Main Lobby', x: 100, y: 200, width: 300, height: 280, type: 'common' }, { id: 'reception', name: 'Reception', x: 420, y: 200, width: 150, height: 120, type: 'office' }, { id: 'security', name: 'Security', x: 420, y: 340, width: 150, height: 140, type: 'utility' }, { id: 'elevator1', name: 'Elevator', x: 100, y: 100, width: 100, height: 80, type: 'transport' }, { id: 'elevator2', name: 'Elevator', x: 220, y: 100, width: 100, height: 80, type: 'transport' }, { id: 'stairs', name: 'Stairs', x: 340, y: 100, width: 100, height: 80, type: 'transport' }, { id: 'cafe', name: 'Café', x: 460, y: 100, width: 100, height: 80, type: 'amenity' } ], doors: [ { x: 250, y: 200, rotation: 0 }, { x: 420, y: 250, rotation: 270 }, { x: 490, y: 340, rotation: 270 } ] }, 2: { rooms: [ { id: 'conf-a', name: 'Conference Room A', x: 100, y: 100, width: 200, height: 150, type: 'meeting' }, { id: 'conf-b', name: 'Conference Room B', x: 320, y: 100, width: 150, height: 150, type: 'meeting' }, { id: 'office-1', name: 'Executive Office', x: 100, y: 270, width: 120, height: 150, type: 'office' }, { id: 'office-2', name: 'Senior Office', x: 240, y: 270, width: 120, height: 150, type: 'office' }, { id: 'open-space', name: 'Open Workspace', x: 100, y: 440, width: 320, height: 150, type: 'office' }, { id: 'kitchen', name: 'Kitchen', x: 490, y: 100, width: 100, height: 120, type: 'amenity' }, { id: 'restroom-m', name: 'Restroom (M)', x: 440, y: 440, width: 70, height: 70, type: 'utility' }, { id: 'restroom-w', name: 'Restroom (W)', x: 520, y: 440, width: 70, height: 70, type: 'utility' }, { id: 'elevator', name: 'Elevators', x: 380, y: 270, width: 130, height: 150, type: 'transport' }, { id: 'storage', name: 'Storage', x: 530, y: 270, width: 60, height: 150, type: 'utility' } ], doors: [ { x: 200, y: 100, rotation: 0 }, { x: 320, y: 180, rotation: 270 }, { x: 150, y: 270, rotation: 0 }, { x: 300, y: 270, rotation: 0 }, { x: 250, y: 440, rotation: 0 }, { x: 490, y: 160, rotation: 270 }, { x: 475, y: 440, rotation: 0 }, { x: 555, y: 440, rotation: 0 } ] }, 3: { rooms: [ { id: 'training', name: 'Training Room', x: 100, y: 100, width: 250, height: 200, type: 'meeting' }, { id: 'server', name: 'Server Room', x: 370, y: 100, width: 120, height: 100, type: 'utility' }, { id: 'it-office', name: 'IT Office', x: 370, y: 220, width: 120, height: 80, type: 'office' }, { id: 'break', name: 'Break Room', x: 510, y: 100, width: 100, height: 200, type: 'amenity' }, { id: 'creative', name: 'Creative Studio', x: 100, y: 320, width: 160, height: 180, type: 'office' }, { id: 'meeting', name: 'Meeting Room', x: 280, y: 320, width: 140, height: 110, type: 'meeting' }, { id: 'phone1', name: 'Phone Booth 1', x: 440, y: 320, width: 80, height: 80, type: 'meeting' }, { id: 'phone2', name: 'Phone Booth 2', x: 530, y: 320, width: 80, height: 80, type: 'meeting' }, { id: 'lounge', name: 'Lounge', x: 280, y: 450, width: 230, height: 100, type: 'amenity' }, { id: 'storage', name: 'Storage', x: 530, y: 420, width: 80, height: 130, type: 'utility' } ], doors: [ { x: 220, y: 100, rotation: 0 }, { x: 430, y: 100, rotation: 0 }, { x: 430, y: 220, rotation: 0 }, { x: 510, y: 150, rotation: 270 }, { x: 180, y: 320, rotation: 0 }, { x: 350, y: 320, rotation: 0 }, { x: 440, y: 360, rotation: 270 }, { x: 530, y: 360, rotation: 270 }, { x: 390, y: 450, rotation: 0 } ] } }; // Room details const roomDetails = { 'conf-a': { title: 'Conference Room A', subtitle: 'Floor 2 • East Wing', specs: [ { label: 'Area', value: '30 m²' }, { label: 'Dimensions', value: '6m × 5m' }, { label: 'Ceiling Height', value: '3.2m' }, { label: 'Capacity', value: '12 people' } ], features: [ { label: 'Windows', value: 'North-facing' }, { label: 'Lighting', value: 'LED Recessed' }, { label: 'Flooring', value: 'Hardwood' }, { label: 'Wall Finish', value: 'Acoustic panels' } ], technical: [ 'HVAC: VAV system with zone control. Supply diffusers on north wall, return grilles near south wall.', 'Power: 12 floor outlets with 120V/20A circuits. 6 wall outlets on dedicated circuits.', 'Data: Cat-6a cabling to floor boxes. WiFi access point mounted centrally on ceiling.', 'A/V: Integrated projection system. Motorized screen on east wall. Control panel by main entrance.' ] }, 'conf-b': { title: 'Conference Room B', subtitle: 'Floor 2 • West Wing', specs: [ { label: 'Area', value: '22.5 m²' }, { label: 'Dimensions', value: '5m × 4.5m' }, { label: 'Ceiling Height', value: '3.2m' }, { label: 'Capacity', value: '8 people' } ], features: [ { label: 'Windows', value: 'West-facing' }, { label: 'Lighting', value: 'LED Pendant' }, { label: 'Flooring', value: 'Carpet tile' }, { label: 'Wall Finish', value: 'Writeable paint' } ], technical: [ 'HVAC: Dedicated air handler with 4 ceiling diffusers. Thermostat located on south wall.', 'Power: 8 floor outlets with 120V/20A circuits. Wall charging stations on north wall.', 'Data: Wireless presentation system. 2 data ports per floor box.', 'A/V: 55" wall-mounted display. Video conferencing system with ceiling microphone array.' ] }, 'office-1': { title: 'Executive Office', subtitle: 'Floor 2 • South Wing', specs: [ { label: 'Area', value: '18 m²' }, { label: 'Dimensions', value: '4m × 4.5m' }, { label: 'Ceiling Height', value: '3m' }, { label: 'Capacity', value: '3 people' } ], features: [ { label: 'Windows', value: 'South-facing' }, { label: 'Lighting', value: 'Task + Ambient' }, { label: 'Flooring', value: 'Hardwood' }, { label: 'Wall Finish', value: 'Fabric panels' } ], technical: [ 'HVAC: Fan coil unit with dedicated thermostat. Supplemental baseboard heating along exterior wall.', 'Power: Executive desk power hub. 4 wall outlets with surge protection.', 'Data: Dedicated 1Gbps connection. Enhanced WiFi coverage. 4 RJ-45 data ports.', 'A/V: Sound-masking system. Wall-mounted video conferencing screen with integrated controls.' ] }, 'open-space': { title: 'Open Workspace', subtitle: 'Floor 2 • Central Area', specs: [ { label: 'Area', value: '48 m²' }, { label: 'Dimensions', value: '8m × 6m' }, { label: 'Ceiling Height', value: '3.2m' }, { label: 'Capacity', value: '16 workstations' } ], features: [ { label: 'Windows', value: 'None (Interior)' }, { label: 'Lighting', value: 'Adjustable LED Grid' }, { label: 'Flooring', value: 'Raised access floor' }, { label: 'Wall Finish', value: 'Partitions, pinboards' } ], technical: [ 'HVAC: Underfloor air distribution. 16 adjustable floor diffusers with personal controls.', 'Power: Modular power distribution system under raised floor. 32A capacity per workstation pod.', 'Data: Structured cabling via raised floor. Central network switch with PoE capability.', 'A/V: Zoned audio system. Digital signage displays. Occupancy sensors tied to BMS.' ] }, 'kitchen': { title: 'Kitchen', subtitle: 'Floor 2 • North Corner', specs: [ { label: 'Area', value: '12 m²' }, { label: 'Dimensions', value: '4m × 3m' }, { label: 'Ceiling Height', value: '2.8m' }, { label: 'Capacity', value: '4-6 people' } ], features: [ { label: 'Windows', value: 'None (Interior)' }, { label: 'Lighting', value: 'Under-cabinet LED' }, { label: 'Flooring', value: 'Porcelain tile' }, { label: 'Wall Finish', value: 'Washable paint, backsplash' } ], technical: [ 'HVAC: Dedicated exhaust system. Higher air exchange rate (12 ACH).', 'Power: GFCI outlets at counters. Dedicated circuits for appliances. Emergency shutoff.', 'Plumbing: Hot/cold supply lines with point-of-use heater. Drain with grease trap.', 'Fire Safety: Heat detector tied to main alarm. Type K extinguisher mounted by exit.' ] } }; // Type colors const typeColors = { 'meeting': '#3498db', 'office': '#2ecc71', 'common': '#9b59b6', 'utility': '#e67e22', 'transport': '#e74c3c', 'amenity': '#f1c40f' }; // DOM elements const blueprint = document.getElementById('blueprint'); const detailsPanel = document.getElementById('details-panel'); const closeDetails = document.getElementById('close-details'); const roomTitle = document.getElementById('room-title'); const roomSubtitle = document.getElementById('room-subtitle'); const roomSpecs = document.getElementById('room-specs'); const roomFeatures = document.getElementById('room-features'); const techToggle = document.getElementById('tech-toggle'); const techInfo = document.getElementById('tech-info'); const techNotes = [ document.getElementById('tech-note-1'), document.getElementById('tech-note-2'), document.getElementById('tech-note-3'), document.getElementById('tech-note-4') ]; const zoomIn = document.getElementById('zoom-in'); const zoomOut = document.getElementById('zoom-out'); const resetView = document.getElementById('reset-view'); const tooltip = document.getElementById('tooltip'); const floorButtons = document.querySelectorAll('.floor-btn'); const loadingOverlay = document.querySelector('.loading-overlay'); // Blueprint state let currentFloor = '2'; let currentScale = 1; let isDragging = false; let dragStart = { x: 0, y: 0 }; let dragOffset = { x: 0, y: 0 }; let selectedRoom = null; // Initialize the blueprint function initBlueprint() { // Clear the blueprint blueprint.innerHTML = ''; // Add rooms for current floor floors[currentFloor].rooms.forEach(room => { const roomElement = document.createElement('div'); roomElement.className = 'room'; roomElement.id = room.id; roomElement.dataset.type = room.type; roomElement.style.left = `${room.x}px`; roomElement.style.top = `${room.y}px`; roomElement.style.width = `${room.width}px`; roomElement.style.height = `${room.height}px`; roomElement.style.borderColor = typeColors[room.type]; roomElement.addEventListener('click', () => { showRoomDetails(room.id); if (selectedRoom) { document.getElementById(selectedRoom).classList.remove('selected', 'pulse'); } roomElement.classList.add('selected', 'pulse'); selectedRoom = room.id; }); roomElement.addEventListener('mouseenter', (e) => { tooltip.textContent = room.name; tooltip.style.left = `${e.pageX + 10}px`; tooltip.style.top = `${e.pageY + 10}px`; tooltip.style.opacity = '1'; }); roomElement.addEventListener('mousemove', (e) => { tooltip.style.left = `${e.pageX + 10}px`; tooltip.style.top = `${e.pageY + 10}px`; }); roomElement.addEventListener('mouseleave', () => { tooltip.style.opacity = '0'; }); blueprint.appendChild(roomElement); // Add room label const labelElement = document.createElement('div'); labelElement.className = 'room-label'; labelElement.textContent = room.name; labelElement.style.left = `${room.x + room.width / 2}px`; labelElement.style.top = `${room.y + room.height / 2}px`; labelElement.style.transform = 'translate(-50%, -50%)'; blueprint.appendChild(labelElement); }); // Add doors floors[currentFloor].doors.forEach(door => { const doorElement = document.createElement('div'); doorElement.className = 'door'; doorElement.style.left = `${door.x}px`; doorElement.style.top = `${door.y}px`; doorElement.style.transform = `rotate(${door.rotation}deg)`; blueprint.appendChild(doorElement); }); } // Show room details function
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>NeoPulse Map Interface</title> <style> :root { --primary: #00f2ff; --primary-glow: #00f2ff40; --accent: #ff00e6; --accent-glow: #ff00e640; --neutral: #1e1e3f; --neutral-dark: #141428; --neutral-light: #2d2d5a; --neutral-panel: rgba(14, 14, 32, 0.85); --text: #e0e0ff; --text-dim: #9393c2; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Rajdhani', 'Segoe UI', sans-serif; } body { width: 100%; height: 100vh; background-color: var(--neutral-dark); color: var(--text); overflow: hidden; display: flex; justify-content: center; align-items: center; } .map-interface { width: 700px; height: 700px; position: relative; overflow: hidden; box-shadow: 0 0 20px var(--primary-glow); border-radius: 8px; backdrop-filter: blur(5px); } .map-container { width: 100%; height: 100%; position: relative; background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAwIiBoZWlnaHQ9IjcwMCIgdmlld0JveD0iMCAwIDcwMCA3MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnM+CiAgICA8cGF0dGVybiBpZD0iZ3JpZCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHBhdGggZD0iTSAyMCAwIEwgMCAwIDAgMjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzJkMmQ1YSIgc3Ryb2tlLXdpZHRoPSIxIi8+CiAgICA8L3BhdHRlcm4+CiAgPC9kZWZzPgogIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiMxNDE0MjgiLz4KICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyaWQpIi8+Cjwvc3ZnPg=='); background-size: cover; } .map-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at center, transparent 40%, var(--neutral-dark) 100%); pointer-events: none; } .hotspot { position: absolute; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; transform: translate(-50%, -50%); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .hotspot::before { content: ''; position: absolute; top: 50%; left: 50%; width: 100%; height: 100%; border-radius: 50%; transform: translate(-50%, -50%); animation: pulse 2s infinite; } .quest-hotspot { background-color: var(--accent); box-shadow: 0 0 10px var(--accent-glow); } .quest-hotspot::before { background-color: var(--accent-glow); } .ally-hotspot { background-color: var(--primary); box-shadow: 0 0 10px var(--primary-glow); } .ally-hotspot::before { background-color: var(--primary-glow); } .hotspot.active { transform: translate(-50%, -50%) scale(1.2); z-index: 10; } .info-panel { position: absolute; background: var(--neutral-panel); border: 1px solid var(--primary); border-radius: 8px; padding: 15px; width: 250px; opacity: 0; transform: translateY(10px); transition: all 0.3s ease; box-shadow: 0 0 15px var(--primary-glow); backdrop-filter: blur(5px); pointer-events: none; z-index: 5; } .info-panel.active { opacity: 1; transform: translateY(0); pointer-events: all; } .info-panel h3 { color: var(--primary); font-size: 18px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; } .info-panel p { color: var(--text); font-size: 14px; margin-bottom: 10px; line-height: 1.4; } .info-panel .details { color: var(--text-dim); font-size: 12px; margin-bottom: 10px; } .info-panel .action-btn { background: linear-gradient(45deg, var(--primary), var(--accent)); color: var(--neutral-dark); border: none; padding: 6px 12px; border-radius: 4px; font-weight: bold; cursor: pointer; transition: all 0.2s ease; margin-top: 5px; font-size: 14px; width: 100%; } .info-panel .action-btn:hover { transform: translateY(-2px); box-shadow: 0 2px 5px var(--primary-glow); } .action-btn:active { transform: translateY(0); } .close-btn { background: none; border: none; color: var(--primary); cursor: pointer; font-size: 18px; } .interface-header { position: absolute; top: 0; left: 0; width: 100%; padding: 15px; display: flex; justify-content: space-between; align-items: center; background: var(--neutral-panel); border-bottom: 1px solid var(--primary); z-index: 4; } .interface-header h2 { color: var(--primary); text-transform: uppercase; font-size: 18px; letter-spacing: 1px; } .status-info { display: flex; gap: 15px; } .status-item { display: flex; align-items: center; gap: 5px; } .status-dot { width: 8px; height: 8px; border-radius: 50%; } .status-dot.quest { background-color: var(--accent); box-shadow: 0 0 5px var(--accent); } .status-dot.ally { background-color: var(--primary); box-shadow: 0 0 5px var(--primary); } .zoom-controls { position: absolute; bottom: 15px; right: 15px; display: flex; flex-direction: column; gap: 5px; z-index: 4; } .zoom-btn { background: var(--neutral-panel); color: var(--primary); border: 1px solid var(--primary); width: 30px; height: 30px; border-radius: 4px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-weight: bold; transition: all 0.2s ease; } .zoom-btn:hover { background-color: var(--primary); color: var(--neutral-dark); } .coordinate-display { position: absolute; bottom: 15px; left: 15px; background: var(--neutral-panel); padding: 5px 10px; border-radius: 4px; border: 1px solid var(--primary); font-size: 12px; color: var(--primary); z-index: 4; } .radar-sweep { position: absolute; top: 50%; left: 50%; width: 200%; height: 200%; border-radius: 50%; transform: translate(-50%, -50%); background: linear-gradient(90deg, transparent, var(--primary-glow), transparent); opacity: 0.2; pointer-events: none; animation: radar-sweep 6s linear infinite; z-index: 2; } .legend { position: absolute; top: 70px; right: 15px; background: var(--neutral-panel); border: 1px solid var(--primary); border-radius: 4px; padding: 8px; z-index: 4; } .legend-item { display: flex; align-items: center; margin-bottom: 5px; font-size: 12px; } .legend-icon { width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; } .legend-icon.quest { background-color: var(--accent); } .legend-icon.ally { background-color: var(--primary); } .progress-bar { position: absolute; bottom: 0; left: 0; width: 100%; height: 3px; background: var(--neutral-light); z-index: 4; } .progress-fill { height: 100%; width: 25%; background: linear-gradient(to right, var(--primary), var(--accent)); position: relative; } .progress-fill::before { content: ''; position: absolute; top: 0; right: 0; width: 5px; height: 100%; background: white; animation: progress-pulse 1s ease infinite; } @keyframes pulse { 0% { opacity: 0.7; transform: translate(-50%, -50%) scale(0.8); } 70% { opacity: 0; transform: translate(-50%, -50%) scale(1.5); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(1.8); } } @keyframes radar-sweep { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } } @keyframes progress-pulse { 0%, 100% { opacity: 0.8; } 50% { opacity: 0.4; } } @keyframes hotspot-discover { 0% { transform: translate(-50%, -50%) scale(0); opacity: 0; } 50% { transform: translate(-50%, -50%) scale(1.3); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; } } .hotspot.discovered { animation: hotspot-discover 0.6s ease-out forwards; } .draggable-map { cursor: grab; } .draggable-map:active { cursor: grabbing; } /* Responsive adjustments */ @media (max-width: 700px) { .info-panel { width: 200px; } } @font-face { font-family: 'Rajdhani'; src: url('data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAAeUAA4AAAAAEVAAAAc+AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAglIIBBEICqIInTYLgQgAATYCJANkBCAFgwQHIBvnDLMRFWwcAPLPg+z/cMCNIfgfrNFYdU+7JdojutVZ9a7O9RST5RRTlnhCEpFIgc7P5eTJw/8f+bfvzMz+XdKmadI0TpbVJCFJhMRLglAjlUqh42nW3ICN+JC+eSLSTUnaKEtWp/i5vcC7PwMcbLABNoIM/n/r9d69oFrKhZEZz5tJIyMwkQd4wv8nZPPNY//XWs23xWX9/a//N1MCXLAGsSirMiuzJJmldz3Lvt6X9Q79/0001e0s3V0JlWYFZGsUlGgKFFQwqnZTAMRZk6vdj8IZjclOQPX0j0/5ChwzHqQGXBWIJwFyF4xSpJgFawGsC2vZCMxydPLsfEJDFFVYxFdoSzpIYXapw9FRgvmFxhLaYCIFKkEJY+CyTKEAAxZFLYwVRLQ1RUWh9gVlIxLK1B45aoYYQqaISiVYRJoERtDDC3CsSc+xZMCd0nUGbhOg2iiS9pnQjQgVlAQxPtbXgpOdAqnl8thlNtLaxe2DaJvTKTfXZJ7SxRKjVgdqVeNgbSaOajwzuuScQZxvG1VaXKRZbpWbtlbA7e7wVvbpcpncrW65AV+dMTK/TwmvzoWIgpyc+EScxBVF+jGRV4OYzKdFR3FOMYGFZrK6kTxHWn4WL2eekxUdXU6pNXrPgXLnBG7Bk5kZRvJ7k3gV83L8Iubn8otJpT6kSX+qPn8d9fYSX9/bR/nfb5GdpcQlKNLzqMVVJIWVRkKvzOKEBZLxFKkcT20mY4kLGHaJcyyTFxNfAUvMr8SPRMOlLk5UYHR0uXWwpYdNyGDZfQV4X8GATrcK3nXoI4hEL0iNSYnLZGlCJe4U8YuZn5fO5z92HbRtunv7gX0RzK9Jd5bbPvLNON4rF4z+p+ydu3TFzQUiGcdbXV27CuIrbbHvvGXoO6OkOqpd3PVkS2JxwxhT9b0mNrfTQsaR89nXnYvw9tIbzVJ1tH7oVuPAvJ7KZHykJTxFynHQJ2DvumSYd8Y9qbWzH2ZFVN1tUdY1tpdK7JA8QsXElQlXJSqXpLXpbIYttSrT/v/uDdMfR2wV13dGNNmAG0n1aOTugJ3KeWzdXoetgDpGK6ASQ5KlKMn0tJQ4JLN/TNk/7TXr0b3q43uMG2foDU5WfTzUdHBctHnCrM7RVbbY0q7G1MHR4TLb4TGRmEivjE8UclLj43C8wLxAIcX9ZKqQyO9h/B6xORtKyXkRi8qX91l5Q/WsLGY2IcEYeXw6B9PzRKnlmNmKXyxJLMK1BRRFULqHJiilmNeXnAJJk/LyMCPNYmI+SRZxXGI0A43R5FI2YlmCWCnWZIYl8cqwg7w7TLezujBTdOdgDNrOukO1Y93I1bMUvV4xWwEKvZ6MQM/bznJodM7+RCPvbtAbrB2D/VEMhnxXDKZzzvoHI1bnvICR4HQVDtSHjWXpvLjDGIxZWmA/vixFX6zf0rLRFGBXcGZjnIf0j5qWF1x+e+aeG3W0X3vQJzf29W3aLgw32oPXLV25tW7/vq0b164KgrtCpOGXj7lufnfdsYLXDfva6Np+oSmoWFi+pC2jOnZP3OqBJzHPXg+2JcSvs6zTHW7Yt3+1K/fFlqDt53Jzf5r/8+a4ZXXxq9oO99HuQ9uj3at3uMpjN1WtHJsqGG1bHRvb1Hs0+HhcfFPdulyheF8R2xDW7ivp4NyZHg9EcdlnZ0s08/C90LUgVnGaRaK7yZyh0CvYXFWb2x0pqpYhSiXKTouGhb77j/2YMJUz2tnOjhvs7+OuS5cUr1sVsVTfzlq9bhlxu53eB35yePnOTrQqF8fOr+tYfsPVfv07J7x1PfC4W93X+nw//3pF29/7ZN/lzwfcKW1t8a/1gY52fUNg5kxDYLtx8OA5deOQvl+w/qI+mPUVnPYbLzq9vv3bv+Nn2vfJgdnzQxgCa+b+mXvvnUVrlxwEb9Ejw8OMHwQXPNjX6p+P78RyUTZF9EK3K2AqGsQwg1lAQmkgUlg1LCpoDWY2A2khgcOC0RgGcEPO/7tCZNIHNxGANGKaDRHMMAC54bJXyNwQ5IHLfpFpTRAL+6eMqZjghGv2QkUCEKQDIANcDoqcCdB2Nrg05Wx3MvPGACRaT5wCCDgIoOOahNkPkCdfWyJwBCCWLbYXoLABQB29aClwDg/PVecCQEJ2R7y8K+RScYwANdLNkMALX1L+9/+NRmQIEmIXNXsAAAeYJzC26gAAAyeABD4Fif3HRXMTSAyPQ/r4MBT0+uBwPAZJ07NQsToBrTIXYexYGizuT3Y0z0WpgCzLK0Pzwx0KDIYxDFOqVIZoEEjhY1A0/xOO+UUoc8ygYv0hWqV/wpizf0FxXn/I0SQYE4oSalh5FRTdNWhKPnSFuG6k3LhMHGtbEYlJjAYIjw9mIK50u3o+t6YswUcZvnvUCw69DL83WhsjKCGOVrhC8Wk2kJPtgkYik/blyoXrsKKjEfvWQi6bktBRXrRlNUX77fYhiLlkn3SdEVd0MdlYxkpLZCyG0yjXZVfOvJxWnFSMeuhh9nCUVF8rrZ+vfhG0EgAA') format('woff2'); font-weight: normal; font-style: normal; } </style> </head> <body> <div class="map-interface"> <div class="map-container draggable-map"> <div class="radar-sweep"></div> <div class="map-overlay"></div> </div> <div class="interface-header"> <h2>NeoPulse Tactical Display</h2> <div class="status-info"> <div class="status-item"> <span class="status-dot quest"></span> <span>Quests (4)</span> </div> <div class="status-item"> <span class="status-dot ally"></span> <span>Allies (3)</span> </div> </div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-icon quest"></div> <span>Active Missions</span> </div> <div class="legend-item"> <div class="legend-icon ally"></div> <span>Squad Members</span> </div> </div> <div class="coordinate-display">X: 245 Y: 378</div> <div class="zoom-controls"> <button class="zoom-btn" id="zoom-in">+</button> <button class="zoom-btn" id="zoom-out">-</button> </div> <div class="progress-bar"> <div class="progress-fill"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const mapContainer = document.querySelector('.map-container'); const coordDisplay = document.querySelector('.coordinate-display'); let scale = 1; let isDragging = false; let startX, startY, translateX = 0, translateY = 0; let currentInfoPanel = null; // Quest and ally data const questData = [ { id: 'quest1', title: 'Neural Infiltration', position: { x: 150, y: 220 }, description: 'Hack into Neurocorp\'s mainframe and extract classified neural-link schematics.', difficulty: 'High', reward: '5000 Credits + Stealth Tech', timeLimit: '45 minutes', discovered: true }, { id: 'quest2', title: 'Synthetic Rescue', position: { x: 480, y: 180 }, description: 'Extract the rogue AI companion from Sector 7 before security forces find it.', difficulty: 'Medium', reward: '3500 Credits + AI Fragment', timeLimit: '30 minutes', discovered: true }, { id: 'quest3', title: 'Chrome Heist', position: { x: 350, y: 500 }, description: 'Acquire experimental augmentation tech from the abandoned CyberMed facility.', difficulty: 'Extreme', reward: '7800 Credits + Rare Augment', timeLimit: '60 minutes', discovered: false }, { id: 'quest4', title: 'Data Dead Drop', position: { x: 580, y: 380 }, description: 'Deliver the encrypted data package to the contact in the Edge district.', difficulty: 'Low', reward: '2500 Credits', timeLimit: '20 minutes', discovered: true } ]; const allyData = [ { id: 'ally1', name: 'Pulse-7', position: { x: 220, y: 330 }, role: 'Combat Specialist', status: 'Available', health: '92%', skills: 'Heavy Weapons, Explosives', discovered: true }, { id: 'ally2', name: 'Spectra', position: { x: 410, y: 250 }, role: 'Infiltration Expert', status: 'On Mission', health: '78%', skills: 'Hacking, Stealth', discovered: true }, { id: 'ally3', name: 'Nexus', position: { x: 520, y: 460 }, role: 'Tech Support', status: 'Available', health: '100%', skills: 'Repair, Drone Control', discovered: false } ]; // Initialize the map function initMap() { // Create quest hotspots questData.forEach(quest => { createHotspot(quest, 'quest'); }); // Create ally hotspots allyData.forEach(ally => { createHotspot(ally, 'ally'); }); // Add map interaction events initMapInteractions(); // Simulate discovery of new hotspots setTimeout(() => { const hiddenQuest = document.getElementById('quest3'); hiddenQuest.classList.add('discovered'); hiddenQuest.style.display = 'block'; }, 3000); setTimeout(() => { const hiddenAlly = document.getElementById('ally3'); hiddenAlly.classList.add('discovered'); hiddenAlly.style.display = 'block'; }, 6000); } function createHotspot(data, type) { const hotspot = document.createElement('div'); hotspot.id = data.id; hotspot.className = `hotspot ${type}-hotspot`; hotspot.style.left = `${data.position.x}px`; hotspot.style.top = `${data.position.y}px`; if (!data.discovered) { hotspot.style.display = 'none'; } // Create info panel for hotspot const infoPanel = document.createElement('div'); infoPanel.className = 'info-panel'; infoPanel.id = `info-${data.id}`; let infoContent = ''; if (type === 'quest') { infoContent = ` <h3>${data.title} <button class="close-btn">×</button></h3> <p>${data.description}</p> <div class="details"> <div>Difficulty: <strong>${data.difficulty}</strong></div> <div>Reward: ${data.reward}</div> <div>Time Limit: ${data.timeLimit}</div> </div> <button class="action-btn">Accept Mission</button> `; } else { infoContent = ` <h3>${data.name} <button class="close-btn">×</button></h3> <p><strong>${data.role}</strong> • ${data.status}</p> <div class="details"> <div>Health: ${data.health}</div> <div>Skills: ${data.skills}</div> </div> <button class="action-btn">Request Backup</button> `; } infoPanel.innerHTML = infoContent; // Position the info panel const xPos = data.position.x; const yPos = data.position.y; if (xPos > 350) { infoPanel.style.right = `${700 - xPos + 20}px`; } else { infoPanel.style.left = `${xPos + 20}px`; } if (yPos > 350) { infoPanel.style.bottom = `${700 - yPos + 20}px`; } else { infoPanel.style.top = `${yPos + 20}px`; } // Add event listeners hotspot.addEventListener('click', (e) => { e.stopPropagation(); toggleInfoPanel(hotspot, infoPanel); }); mapContainer.appendChild(hotspot); mapContainer.appendChild(infoPanel); // Add close button functionality const closeBtn = infoPanel.querySelector('.close-btn'); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); infoPanel.classList.remove('active'); hotspot.classList.remove('active'); currentInfoPanel = null; }); // Add action button functionality const actionBtn = infoPanel.querySelector('.action-btn'); actionBtn.addEventListener('click', (e) => { e.stopPropagation(); // Visual feedback actionBtn.textContent = type === 'quest' ? 'Mission Accepted' : 'Backup Requested'; actionBtn.style.background = '#00f2ff'; // Close after a delay setTimeout(() => { infoPanel.classList.remove('active'); hotspot.classList.remove('active'); currentInfoPanel = null; }, 1500); }); } function toggleInfoPanel(hotspot, infoPanel) { if (currentInfoPanel) { currentInfoPanel.classList.remove('active'); document.querySelector('.hotspot.active')?.classList.remove('active'); } if (currentInfoPanel === infoPanel) { currentInfoPanel = null; return; } infoPanel.classList.add('active'); hotspot.classList.add('active'); currentInfoPanel = infoPanel; } function initMapInteractions() { // Close any open panel when clicking the map mapContainer.addEventListener('click', (e) => { if (e.target === mapContainer || e.target.classList.contains('map-overlay')) { if (currentInfoPanel) { currentInfoPanel.classList.remove('active'); document.querySelector('.hotspot.active')?.classList.remove('active'); currentInfoPanel = null; } } }); // Draggable map mapContainer.addEventListener('mousedown', startDrag); mapContainer.addEventListener('touchstart', (e) => { startDrag(e.touches[0]); }, { passive: false }); document.addEventListener('mousemove', drag); document.addEventListener('touchmove', (e) => { drag(e.touches[0]); if (isDragging) e.preventDefault(); }, { passive: false }); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); // Mouse move to update coordinates mapContainer.addEventListener('mousemove', updateCoordinates); // Zoom controls document.getElementById('zoom-in').addEventListener('click', () => { zoomMap(0.1); }); document.getElementById('zoom-out').addEventListener('click', () => { zoomMap(-0.1); }); } function startDrag(e) { isDragging = true; startX = e.clientX - translateX; startY = e.clientY - translateY; mapContainer.style.cursor = 'grabbing'; } function drag(e) { if (!isDragging) return; translateX = e.clientX - startX; translateY = e.clientY - startY; // Limit dragging const maxTranslate = 150 * scale; translateX = Math.max(-maxTranslate, Math.min(translateX, maxTranslate)); translateY = Math.max(-maxTranslate, Math.min(translateY, maxTranslate)); mapContainer.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } function endDrag() { isDragging = false; mapContainer.style.cursor = 'grab'; } function updateCoordinates(e) { const rect = mapContainer.getBoundingClientRect(); const x = Math.round(((e.clientX - rect.left) / scale) - (translateX / scale)); const y = Math.round(((e.clientY - rect.top) / scale) - (translateY / scale)); // Only update if within map bounds if (x >= 0 && x <= 700 && y >= 0 && y <= 700) { coordDisplay.textContent = `X: ${x} Y: ${y}`; } } function zoomMap(delta) { scale = Math.max(0.8, Math.min(scale + delta, 1.5)); mapContainer.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } // Initialize the map initMap(); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Climate Crisis: The Cost of Inaction</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f0f2f5; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; } .container { width: 700px; height: 700px; position: relative; background-color: #fff; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); overflow: hidden; display: flex; flex-direction: column; } .header { padding: 25px 30px 10px; background: linear-gradient(to right, #1E3D59, #0B2545); color: #fff; } .header h1 { font-size: 28px; margin-bottom: 5px; font-weight: 700; letter-spacing: -0.5px; } .header p { font-size: 14px; opacity: 0.9; line-height: 1.4; max-width: 90%; } .visualization { flex: 1; position: relative; overflow: hidden; padding: 20px; display: flex; flex-direction: column; } .map-container { flex: 1; position: relative; overflow: hidden; } .world-map { width: 100%; height: 100%; background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQwIiBoZWlnaHQ9IjM2MCIgdmlld0JveD0iMCAwIDY0MCAzNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE5MiAxMzBDMTkyIDEyNi4xMyAxOTUuMTMgMTIzIDE5OSAxMjNIMjk4QzMwMS44NyAxMjMgMzA1IDEyNi4xMyAzMDUgMTMwVjE5OUMzMDUgMjAyLjg3IDMwMS44NyAyMDYgMjk4IDIwNkgxOTlDMTk1LjEzIDIwNiAxOTIgMjAyLjg3IDE5MiAxOTlWMTMwWiIgZmlsbD0iI0UwRjNGRiIgc3Ryb2tlPSIjNjdBM0IzIiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNMzE2IDcwQzMxNiA2Ni4xMzQgMzE5LjEzIDYzIDMyMyA2M0g0MzVDNDM4Ljg3IDYzIDQ0MiA2Ni4xMzQgNDQyIDcwVjE3OUM0NDIgMTgyLjg3IDQzOC44NyAxODYgNDM1IDE4NkgzMjNDMzE5LjEzIDE4NiAzMTYgMTgyLjg3IDMxNiAxNzlWNzBaIiBmaWxsPSIjRThGOEYzIiBzdHJva2U9IiM3OUMxQTEiIHN0cm9rZS13aWR0aD0iMiIvPjxwYXRoIGQ9Ik0yODAgMjIwQzI4MCAyMTYuMTMgMjgzLjEzIDIxMyAyODcgMjEzSDQwMEM0MDMuODcgMjEzIDQwNyAyMTYuMTMgNDA3IDIyMFYyOTBDNDA3IDI5My44NyA0MDMuODcgMjk3IDQwMCAyOTdIMjg3QzI4My4xMyAyOTcgMjgwIDI5My44NyAyODAgMjkwVjIyMFoiIGZpbGw9IiNGREY0RUMiIHN0cm9rZT0iI0RDQzA4QiIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggZD0iTTEzMCAyMzBDMTMwIDIyNi4xMyAxMzMuMTMgMjIzIDEzNyAyMjNIMjYyQzI2NS44NyAyMjMgMjY5IDIyNi4xMyAyNjkgMjMwVjI5M0MyNjkgMjk2Ljg3IDI2NS44NyAzMDAgMjYyIDMwMEgxMzdDMTMzLjEzIDMwMCAxMzAgMjk2Ljg3IDEzMCAyOTNWMjMwWiIgZmlsbD0iI0ZGRUNFQyIgc3Ryb2tlPSIjRTU4QzhDIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4='); background-size: contain; background-repeat: no-repeat; background-position: center; } .hotspot { position: absolute; width: 24px; height: 24px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.8); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); cursor: pointer; display: flex; justify-content: center; align-items: center; transition: transform 0.3s ease, box-shadow 0.3s ease; z-index: 10; } .hotspot::after { content: ''; position: absolute; width: 10px; height: 10px; border-radius: 50%; transition: background-color 0.3s ease; } .hotspot:hover { transform: scale(1.2); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); } .hotspot.north-america { top: 35%; left: 25%; } .hotspot.north-america::after { background-color: #1E3D59; } .hotspot.europe { top: 30%; left: 52%; } .hotspot.europe::after { background-color: #3CAEA3; } .hotspot.asia { top: 38%; left: 70%; } .hotspot.asia::after { background-color: #F6D55C; } .hotspot.africa { top: 55%; left: 52%; } .hotspot.africa::after { background-color: #ED553B; } .pulse { position: absolute; width: 100%; height: 100%; border-radius: 50%; background-color: rgba(255, 255, 255, 0.4); z-index: -1; animation: pulse 2s infinite; } @keyframes pulse { 0% { transform: scale(1); opacity: 0.8; } 100% { transform: scale(2.5); opacity: 0; } } .tooltip { position: absolute; width: 260px; background-color: #fff; border-radius: 8px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); padding: 15px; font-size: 13px; z-index: 100; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; transform: translateY(10px); } .tooltip.visible { opacity: 1; visibility: visible; transform: translateY(0); } .tooltip h3 { margin-bottom: 8px; font-size: 16px; font-weight: 600; color: #333; } .tooltip p { margin-bottom: 8px; line-height: 1.4; color: #555; } .stat { display: flex; align-items: baseline; margin-top: 12px; } .stat-number { font-size: 28px; font-weight: 700; margin-right: 10px; line-height: 1; } .stat-label { font-size: 12px; color: #777; font-weight: 500; } .north-america .stat-number { color: #1E3D59; } .europe .stat-number { color: #3CAEA3; } .asia .stat-number { color: #F6D55C; } .africa .stat-number { color: #ED553B; } .chart-container { margin-top: 15px; height: 8px; background-color: #eee; border-radius: 4px; overflow: hidden; } .chart-bar { height: 100%; width: 0; border-radius: 4px; transition: width 1s ease-out; } .north-america .chart-bar { background-color: #1E3D59; } .europe .chart-bar { background-color: #3CAEA3; } .asia .chart-bar { background-color: #F6D55C; } .africa .chart-bar { background-color: #ED553B; } .tooltip-close { position: absolute; top: 10px; right: 10px; font-size: 16px; color: #999; cursor: pointer; background: none; border: none; padding: 0; } .legend { display: flex; justify-content: space-around; padding: 15px 25px; background-color: #f8f9fa; border-top: 1px solid #e9ecef; } .legend-item { display: flex; align-items: center; font-size: 12px; color: #555; } .legend-color { width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; } .legend-color.north-america { background-color: #1E3D59; } .legend-color.europe { background-color: #3CAEA3; } .legend-color.asia { background-color: #F6D55C; } .legend-color.africa { background-color: #ED553B; } .meta-info { font-size: 11px; color: #999; text-align: center; padding: 5px 0; background-color: #f8f9fa; } @media (max-width: 700px) { .header h1 { font-size: 22px; } .header p { font-size: 12px; } .tooltip { width: 220px; font-size: 12px; } .stat-number { font-size: 24px; } .legend { flex-wrap: wrap; padding: 10px; } .legend-item { font-size: 10px; margin: 5px; flex-basis: 45%; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>Climate Crisis: The Cost of Inaction</h1> <p>Regional climate impacts and projected economic costs through 2050. Click on hotspots to explore key data points.</p> </div> <div class="visualization"> <div class="map-container"> <div class="world-map"></div> <div class="hotspot north-america" data-region="north-america"> <div class="pulse"></div> </div> <div class="hotspot europe" data-region="europe"> <div class="pulse"></div> </div> <div class="hotspot asia" data-region="asia"> <div class="pulse"></div> </div> <div class="hotspot africa" data-region="africa"> <div class="pulse"></div> </div> <div class="tooltip north-america"> <button class="tooltip-close">×</button> <h3>North America</h3> <p>Rising sea levels and increasing frequency of extreme weather events are projected to disrupt major coastal economies.</p> <div class="stat"> <div class="stat-number">$494B</div> <div class="stat-label">Annual economic losses by 2050</div> </div> <div class="chart-container"> <div class="chart-bar" style="width: 68%;"></div> </div> </div> <div class="tooltip europe"> <button class="tooltip-close">×</button> <h3>Europe</h3> <p>Increasingly frequent heatwaves and drought conditions threaten agriculture and critical infrastructure.</p> <div class="stat"> <div class="stat-number">€395B</div> <div class="stat-label">Annual economic losses by 2050</div> </div> <div class="chart-container"> <div class="chart-bar" style="width: 55%;"></div> </div> </div> <div class="tooltip asia"> <button class="tooltip-close">×</button> <h3>Asia</h3> <p>Rising temperatures and changing monsoon patterns are affecting food security and increasing displacement.</p> <div class="stat"> <div class="stat-number">$1.2T</div> <div class="stat-label">Annual economic losses by 2050</div> </div> <div class="chart-container"> <div class="chart-bar" style="width: 89%;"></div> </div> </div> <div class="tooltip africa"> <button class="tooltip-close">×</button> <h3>Africa</h3> <p>Extreme heat and drought conditions are decreasing agricultural yields and threatening water security.</p> <div class="stat"> <div class="stat-number">$415B</div> <div class="stat-label">Annual economic losses by 2050</div> </div> <div class="chart-container"> <div class="chart-bar" style="width: 60%;"></div> </div> </div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color north-america"></div> <span>North America</span> </div> <div class="legend-item"> <div class="legend-color europe"></div> <span>Europe</span> </div> <div class="legend-item"> <div class="legend-color asia"></div> <span>Asia</span> </div> <div class="legend-item"> <div class="legend-color africa"></div> <span>Africa</span> </div> </div> <div class="meta-info"> Data sources: IPCC AR6, World Bank Climate Change Knowledge Portal, 2023 </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const hotspots = document.querySelectorAll('.hotspot'); const tooltips = document.querySelectorAll('.tooltip'); const closeButtons = document.querySelectorAll('.tooltip-close'); // Position tooltips based on their related hotspot function positionTooltips() { hotspots.forEach(hotspot => { const region = hotspot.dataset.region; const tooltip = document.querySelector(`.tooltip.${region}`); const hotspotRect = hotspot.getBoundingClientRect(); const containerRect = document.querySelector('.map-container').getBoundingClientRect(); // Calculate position relative to container let left = hotspotRect.left - containerRect.left + hotspotRect.width + 15; let top = hotspotRect.top - containerRect.top - (tooltip.offsetHeight / 2) + (hotspotRect.height / 2); // Ensure tooltip stays within container if (left + tooltip.offsetWidth > containerRect.width) { left = hotspotRect.left - containerRect.left - tooltip.offsetWidth - 15; } if (top < 0) { top = 10; } else if (top + tooltip.offsetHeight > containerRect.height) { top = containerRect.height - tooltip.offsetHeight - 10; } tooltip.style.left = `${left}px`; tooltip.style.top = `${top}px`; }); } // Show tooltip for the clicked hotspot function showTooltip(event) { // First, hide all tooltips tooltips.forEach(tooltip => { tooltip.classList.remove('visible'); }); const region = this.dataset.region; const tooltip = document.querySelector(`.tooltip.${region}`); positionTooltips(); // Show the selected tooltip with delay to allow position calculation setTimeout(() => { tooltip.classList.add('visible'); }, 50); event.stopPropagation(); } // Hide tooltip when close button is clicked function hideTooltip() { const tooltip = this.closest('.tooltip'); tooltip.classList.remove('visible'); } // Hide all tooltips when clicking outside function hideAllTooltips(event) { if (!event.target.closest('.tooltip') && !event.target.closest('.hotspot')) { tooltips.forEach(tooltip => { tooltip.classList.remove('visible'); }); } } // Add event listeners hotspots.forEach(hotspot => { hotspot.addEventListener('click', showTooltip); }); closeButtons.forEach(button => { button.addEventListener('click', hideTooltip); }); document.addEventListener('click', hideAllTooltips); // Ensure tooltips are positioned correctly on load and resize window.addEventListener('resize', positionTooltips); positionTooltips(); // Animate chart bars with slight delay for visual effect setTimeout(() => { const chartBars = document.querySelectorAll('.chart-bar'); chartBars.forEach(bar => { const width = bar.style.width; bar.style.width = '0'; setTimeout(() => { bar.style.width = width; }, 300); }); }, 500); }); </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 { background-color: #f8f9fc; height: 700px; width: 700px; display: flex; justify-content: center; align-items: center; overflow: hidden; } .product-tour { width: 100%; height: 100%; position: relative; display: flex; flex-direction: column; overflow: hidden; } .tour-header { background: linear-gradient(135deg, #2d3748, #1a202c); padding: 20px; color: white; text-align: center; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); z-index: 10; } .tour-header h1 { font-size: 24px; font-weight: 700; margin-bottom: 5px; } .tour-header p { font-size: 14px; opacity: 0.8; } .tour-progress { display: flex; justify-content: center; margin-top: 15px; } .progress-dot { width: 10px; height: 10px; background-color: rgba(255, 255, 255, 0.3); border-radius: 50%; margin: 0 5px; cursor: pointer; transition: all 0.3s ease; } .progress-dot.active { background-color: white; transform: scale(1.2); } .tour-container { flex: 1; position: relative; overflow: hidden; background-color: #f8f9fc; } .product-screenshot { position: absolute; width: 100%; height: 100%; opacity: 0; transform: translateX(100%); transition: opacity 0.5s ease, transform 0.5s ease; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 20px; } .product-screenshot.active { opacity: 1; transform: translateX(0); } .screenshot-container { position: relative; width: 90%; max-width: 600px; border-radius: 8px; overflow: hidden; box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); } .screenshot { width: 100%; background-color: #fff; border-radius: 8px; border: 1px solid #e2e8f0; } .hotspot { position: absolute; width: 38px; height: 38px; border-radius: 50%; background-color: rgba(99, 102, 241, 0.9); display: flex; justify-content: center; align-items: center; cursor: pointer; box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.5); animation: pulse 2s infinite; z-index: 2; transition: all 0.3s ease; } .hotspot:hover { transform: scale(1.1); background-color: rgba(79, 70, 229, 1); } .hotspot::after { content: ''; display: block; width: 14px; height: 14px; background-color: white; border-radius: 50%; } .tooltip { position: absolute; background-color: white; border-radius: 8px; padding: 15px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); width: 250px; transition: all 0.3s ease; opacity: 0; pointer-events: none; z-index: 3; transform: translateY(10px); } .tooltip.visible { opacity: 1; transform: translateY(0); pointer-events: auto; } .tooltip h3 { font-size: 16px; margin-bottom: 8px; color: #2d3748; } .tooltip p { font-size: 14px; color: #4a5568; line-height: 1.5; } .tour-navigation { display: flex; justify-content: space-between; margin-top: 20px; padding: 0 20px; } .nav-btn { background-color: #4f46e5; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 600; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .nav-btn:hover { background-color: #4338ca; transform: translateY(-2px); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .nav-btn:disabled { background-color: #cbd5e0; cursor: not-allowed; transform: none; box-shadow: none; } .nav-btn svg { width: 18px; height: 18px; margin: 0 5px; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(99, 102, 241, 0); } 100% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0); } } .overlay { position: absolute; width: 100%; height: 100%; background-color: rgba(26, 32, 44, 0.7); z-index: 1; pointer-events: none; } .screenshot-label { position: absolute; bottom: 15px; left: 15px; background-color: rgba(45, 55, 72, 0.9); color: white; padding: 5px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; } /* Responsive adjustments */ @media (max-width: 600px) { .tour-header h1 { font-size: 20px; } .tour-header p { font-size: 12px; } .tooltip { width: 200px; } .nav-btn { padding: 8px 15px; font-size: 14px; } } /* Custom cursor */ .custom-cursor { position: fixed; width: 20px; height: 20px; border-radius: 50%; background-color: rgba(99, 102, 241, 0.2); pointer-events: none; mix-blend-mode: multiply; z-index: 9999; transition: transform 0.1s ease, width 0.3s ease, height 0.3s ease; } /* Feature indicator animation */ .feature-indicator { position: absolute; border: 2px solid #4f46e5; border-radius: 6px; z-index: 2; pointer-events: none; opacity: 0; transition: all 0.5s ease; } .feature-indicator.active { opacity: 1; animation: highlight 2s ease-in-out; } @keyframes highlight { 0%, 100% { box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.3); } 50% { box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.6); } } </style> </head> <body> <div class="custom-cursor"></div> <div class="product-tour"> <div class="tour-header"> <h1>DataFlowPro</h1> <p>Interactive Tour of Key Features</p> <div class="tour-progress"> <div class="progress-dot active" data-index="0"></div> <div class="progress-dot" data-index="1"></div> <div class="progress-dot" data-index="2"></div> <div class="progress-dot" data-index="3"></div> </div> </div> <div class="tour-container"> <!-- Dashboard Overview --> <div class="product-screenshot active" data-index="0"> <div class="screenshot-container"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400' fill='none'%3E%3Crect width='600' height='400' fill='white'/%3E%3Crect x='0' y='0' width='600' height='60' fill='%232D3748'/%3E%3Crect x='20' y='20' width='120' height='20' rx='4' fill='%234F46E5'/%3E%3Crect x='450' y='15' width='30' height='30' rx='15' fill='%23A3BFFA'/%3E%3Crect x='500' y='15' width='30' height='30' rx='15' fill='%23A3BFFA'/%3E%3Crect x='550' y='15' width='30' height='30' rx='15' fill='%23A3BFFA'/%3E%3Crect x='20' y='80' width='180' height='140' rx='6' fill='%23EBF4FF'/%3E%3Crect x='40' y='100' width='140' height='10' rx='2' fill='%234F46E5'/%3E%3Crect x='40' y='120' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='40' y='160' width='120' height='40' rx='4' fill='%234F46E5'/%3E%3Crect x='210' y='80' width='180' height='140' rx='6' fill='%23EBF4FF'/%3E%3Crect x='230' y='100' width='140' height='10' rx='2' fill='%234F46E5'/%3E%3Crect x='230' y='120' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='230' y='160' width='120' height='40' rx='4' fill='%234F46E5'/%3E%3Crect x='400' y='80' width='180' height='140' rx='6' fill='%23EBF4FF'/%3E%3Crect x='420' y='100' width='140' height='10' rx='2' fill='%234F46E5'/%3E%3Crect x='420' y='120' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='420' y='160' width='120' height='40' rx='4' fill='%234F46E5'/%3E%3Crect x='20' y='240' width='560' height='140' rx='6' fill='%23EBF4FF'/%3E%3Crect x='40' y='260' width='200' height='15' rx='2' fill='%234F46E5'/%3E%3Crect x='40' y='290' width='520' height='2' fill='%23CBD5E0'/%3E%3Crect x='40' y='310' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='200' y='310' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='360' y='310' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='40' y='340' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='200' y='340' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='360' y='340' width='100' height='10' rx='2' fill='%23A3BFFA'/%3E%3C/svg%3E" alt="Dashboard Overview" class="screenshot"> <div class="screenshot-label">Dashboard Overview</div> <div class="hotspot" style="top: 30px; right: 70px;" data-tooltip-id="notification-center"></div> <div class="tooltip" id="notification-center" style="top: 70px; right: 30px;"> <h3>Notification Center</h3> <p>Get real-time alerts on system updates, workflow completions, and team mentions—all customizable to your priority levels.</p> </div> <div class="hotspot" style="top: 150px; left: 100px;" data-tooltip-id="data-pipeline"></div> <div class="tooltip" id="data-pipeline" style="top: 190px; left: 30px;"> <h3>Data Pipeline Builder</h3> <p>Create powerful data workflows with our drag-and-drop interface. Connect data sources and transformations visually—no coding required.</p> </div> <div class="hotspot" style="top: 290px; left: 120px;" data-tooltip-id="analytics-dashboard"></div> <div class="tooltip" id="analytics-dashboard" style="top: 200px; left: 120px;"> <h3>Analytics Dashboard</h3> <p>Monitor key metrics with interactive visualizations that update in real-time as your data changes.</p> </div> <div class="feature-indicator" id="dashboard-indicator" style="top: 240px; left: 20px; width: 560px; height: 140px;"></div> </div> </div> <!-- Data Connector Interface --> <div class="product-screenshot" data-index="1"> <div class="screenshot-container"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400' fill='none'%3E%3Crect width='600' height='400' fill='white'/%3E%3Crect x='0' y='0' width='600' height='60' fill='%232D3748'/%3E%3Crect x='20' y='20' width='120' height='20' rx='4' fill='%234F46E5'/%3E%3Crect x='550' y='15' width='30' height='30' rx='15' fill='%23A3BFFA'/%3E%3Crect x='0' y='60' width='180' height='340' fill='%23F7FAFC'/%3E%3Crect x='20' y='80' width='140' height='40' rx='4' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='20' y='130' width='140' height='40' rx='4' fill='%234F46E5'/%3E%3Crect x='20' y='180' width='140' height='40' rx='4' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='20' y='230' width='140' height='40' rx='4' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='200' y='80' width='380' height='60' rx='6' fill='%23EBF4FF'/%3E%3Crect x='220' y='100' width='200' height='15' rx='2' fill='%234F46E5'/%3E%3Crect x='220' y='125' width='340' height='2' fill='%23CBD5E0'/%3E%3Crect x='200' y='160' width='180' height='180' rx='6' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Ccircle cx='290' cy='200' r='30' fill='%23EBF4FF'/%3E%3Crect x='250' y='250' width='80' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='230' y='280' width='120' height='40' rx='4' fill='%234F46E5'/%3E%3Crect x='400' y='160' width='180' height='180' rx='6' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Ccircle cx='490' cy='200' r='30' fill='%23EBF4FF'/%3E%3Crect x='450' y='250' width='80' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='430' y='280' width='120' height='40' rx='4' fill='%234F46E5'/%3E%3Cline x1='350' y1='200' x2='430' y2='200' stroke='%234F46E5' stroke-width='2' stroke-dasharray='5 5'/%3E%3C/svg%3E" alt="Data Connector Interface" class="screenshot"> <div class="screenshot-label">Data Connectors</div> <div class="hotspot" style="top: 150px; left: 150px;" data-tooltip-id="connector-menu"></div> <div class="tooltip" id="connector-menu" style="top: 140px; left: 180px;"> <h3>Connector Library</h3> <p>Access 200+ pre-built connectors for databases, APIs, and cloud services. Our blue connectors indicate verified enterprise integrations.</p> </div> <div class="hotspot" style="top: 200px; left: 290px;" data-tooltip-id="source-connector"></div> <div class="tooltip" id="source-connector" style="top: 230px; left: 250px;"> <h3>Source Configuration</h3> <p>Configure authentication, rate limits, and data mapping with our intuitive interface. No more wrestling with API docs.</p> </div> <div class="hotspot" style="top: 200px; right: 200px;" data-tooltip-id="destination-connector"></div> <div class="tooltip" id="destination-connector" style="top: 160px; right: 100px;"> <h3>Destination Setup</h3> <p>Customize how data lands in your target systems with field mapping, schema evolution controls, and error handling options.</p> </div> <div class="feature-indicator" id="connector-indicator" style="top: 160px; left: 200px; width: 180px; height: 180px;"></div> </div> </div> <!-- Workflow Builder --> <div class="product-screenshot" data-index="2"> <div class="screenshot-container"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400' fill='none'%3E%3Crect width='600' height='400' fill='white'/%3E%3Crect x='0' y='0' width='600' height='60' fill='%232D3748'/%3E%3Crect x='20' y='20' width='120' height='20' rx='4' fill='%234F46E5'/%3E%3Crect x='550' y='15' width='30' height='30' rx='15' fill='%23A3BFFA'/%3E%3Crect x='0' y='60' width='180' height='340' fill='%23F7FAFC'/%3E%3Crect x='20' y='80' width='140' height='30' rx='4' fill='%23E2E8F0'/%3E%3Crect x='40' y='130' width='100' height='30' rx='4' fill='%23EBF4FF'/%3E%3Crect x='40' y='170' width='100' height='30' rx='4' fill='%23EBF4FF'/%3E%3Crect x='40' y='210' width='100' height='30' rx='4' fill='%23EBF4FF'/%3E%3Crect x='40' y='250' width='100' height='30' rx='4' fill='%23EBF4FF'/%3E%3Crect x='200' y='80' width='380' height='300' rx='6' fill='%23F7FAFC'/%3E%3Crect x='220' y='100' width='80' height='80' rx='6' fill='%234F46E5'/%3E%3Cline x1='300' y1='140' x2='350' y2='140' stroke='%234F46E5' stroke-width='2' stroke-dasharray='5 5'/%3E%3Crect x='350' y='100' width='80' height='80' rx='6' fill='white' stroke='%234F46E5' stroke-width='2'/%3E%3Cline x1='390' y1='180' x2='390' y2='230' stroke='%234F46E5' stroke-width='2' stroke-dasharray='5 5'/%3E%3Crect x='350' y='230' width='80' height='80' rx='6' fill='white' stroke='%234F46E5' stroke-width='2'/%3E%3Cline x1='350' y1='270' x2='300' y2='270' stroke='%234F46E5' stroke-width='2' stroke-dasharray='5 5'/%3E%3Crect x='220' y='230' width='80' height='80' rx='6' fill='white' stroke='%234F46E5' stroke-width='2'/%3E%3Cline x1='260' y1='230' x2='260' y2='180' stroke='%234F46E5' stroke-width='2' stroke-dasharray='5 5'/%3E%3Crect x='460' y='100' width='100' height='210' rx='6' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='470' y='120' width='80' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='470' y='140' width='60' height='10' rx='2' fill='%23CBD5E0'/%3E%3Crect x='470' y='170' width='80' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='470' y='190' width='60' height='10' rx='2' fill='%23CBD5E0'/%3E%3Crect x='470' y='220' width='80' height='10' rx='2' fill='%23A3BFFA'/%3E%3Crect x='470' y='240' width='60' height='10' rx='2' fill='%23CBD5E0'/%3E%3Crect x='470' y='270' width='80' height='25' rx='4' fill='%234F46E5'/%3E%3C/svg%3E" alt="Workflow Builder" class="screenshot"> <div class="screenshot-label">Workflow Builder</div> <div class="hotspot" style="top: 140px; left: 260px;" data-tooltip-id="data-source"></div> <div class="tooltip" id="data-source" style="top: 100px; left: 290px;"> <h3>Data Source Node</h3> <p>Starting point for all workflows. Connect to any data source like PostgreSQL, MongoDB, API endpoints, or streaming services like Kafka.</p> </div> <div class="hotspot" style="top: 140px; right: 210px;" data-tooltip-id="transformation-node"></div> <div class="tooltip" id="transformation-node" style="top: 170px; right: 180px;"> <h3>Transformation Node</h3> <p>Apply SQL, Python or no-code transformations to clean, enrich, and restructure your data. Built-in data quality validation included.</p> </div> <div class="hotspot" style="top: 270px; right: 130px;" data-tooltip-id="workflow-properties"></div> <div class="tooltip" id="workflow-properties" style="bottom: 100px; right: 40px;"> <h3>Workflow Properties</h3> <p>Set triggers, schedules, and alerting rules for your workflow. Configure retry policies and error handling behaviors.</p> </div> <div class="feature-indicator" id="workflow-indicator" style="top: 100px; left: 220px; width: 210px; height: 210px;"></div> </div> </div> <!-- Monitoring Dashboard --> <div class="product-screenshot" data-index="3"> <div class="screenshot-container"> <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400' fill='none'%3E%3Crect width='600' height='400' fill='white'/%3E%3Crect x='0' y='0' width='600' height='60' fill='%232D3748'/%3E%3Crect x='20' y='20' width='120' height='20' rx='4' fill='%234F46E5'/%3E%3Crect x='550' y='15' width='30' height='30' rx='15' fill='%23A3BFFA'/%3E%3Crect x='20' y='80' width='560' height='60' rx='6' fill='%23EBF4FF'/%3E%3Crect x='40' y='100' width='120' height='20' rx='2' fill='%234F46E5'/%3E%3Crect x='440' y='90' width='120' height='40' rx='4' fill='%234F46E5'/%3E%3Crect x='20' y='160' width='270' height='220' rx='6' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='40' y='180' width='120' height='15' rx='2' fill='%234F46E5'/%3E%3Cpath d='M40 220 L290 220' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M40 260 L290 260' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M40 300 L290 300' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M40 340 L290 340' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M40 210 L40 350' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M100 210 L100 350' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M160 210 L160 350' stroke='%23E2E8F0' stroke-width='1'/%3E%3Cpath d='M220 210 L220 350' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='45' y='240' width='10' height='20' fill='%234F46E5'/%3E%3Crect x='105' y='225' width='10' height='35' fill='%234F46E5'/%3E%3Crect x='165' y='265' width='10' height='35' fill='%234F46E5'/%3E%3Crect x='225' y='280' width='10' height='20' fill='%234F46E5'/%3E%3Crect x='310' y='160' width='270' height='220' rx='6' fill='white' stroke='%23E2E8F0' stroke-width='1'/%3E%3Crect x='330' y='180' width='120' height='15' rx='2' fill='%234F46E5'/%3E%3Ccircle cx='445' cy='270' r='90' fill='none' stroke='%23E2E8F0' stroke-width='20'/%3E%3Cpath d='M445 270 m-90 0 a90 90 0 1 0 180 0 a90 90 0 1 0 -180 0' fill='none' stroke='%234F46E5' stroke-width='20' stroke-dasharray='424.1 565.48'/%3E%3Ctext x='425' y='290' font-family='Arial' font-size='30' font-weight='bold' fill='%232D3748'%3E75%25%3C/text%3E%3C/svg%3E" alt="Monitoring Dashboard" class="screenshot"> <div class="screenshot-label">Performance Monitoring</div> <div class="hotspot" style="top: 110px; right: 140px;" data-tooltip-id="alert-settings"></div> <div class="tooltip" id="alert-settings" style="top: 70px; right: 100px;"> <h3>Alert Configuration</h3> <p>Create custom alerting thresholds for latency, error rates, and data quality. Set up team notifications via Slack, email, or PagerDuty.</p> </div> <div class="hotspot" style="top: 250px; left: 150px;" data-tooltip-id="runtime-metrics"></div> <div class="tooltip" id="runtime-metrics" style="top: 200px; left: 40px;"> <h3>Runtime Metrics</h3> <p>Track processing times, throughput, and resource utilization with fine-grained metrics. Identify bottlenecks before they impact production.</p> </div> <div class="hotspot" style="top: 270px; right: 170px;" data-tooltip-id="success-rate"></div> <div class="tooltip" id="success-rate" style="top: 230px; right: 70px;"> <h3>Pipeline Health Score</h3> <p>Our AI-powered health score combines reliability, performance, and data quality metrics into a single indicator of system health.</p> </div> <div class="feature-indicator" id="monitoring-indicator" style="top: 160px; left: 20px; width: 270px; height: 220px;"></div> </div> </div> </div> <div class="tour-navigation"> <button class="nav-btn prev-btn" disabled> <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="M15 19l-7-7 7-7" /> </svg> Previous </button> <button class="nav-btn next-btn"> Next <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="M9 5l7 7-7 7" /> </svg> </button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Initialize variables let currentIndex = 0; const screenshots = document.querySelectorAll('.product-screenshot'); const progressDots = document.querySelectorAll('.progress-dot'); const prevBtn = document.querySelector('.prev-btn'); const nextBtn = document.querySelector('.next-btn'); const hotspots = document.querySelectorAll('.hotspot'); const indicators = document.querySelectorAll('.feature-indicator'); const customCursor = document.querySelector('.custom-cursor'); // Update UI based on current index function updateUI() { // Update screenshots screenshots.forEach((screenshot, index) => { if (index === currentIndex) { screenshot.classList.add('active'); } else { screenshot.classList.remove('active'); } }); // Update progress dots progressDots.forEach((dot, index) => { if (index === currentIndex) { dot.classList.add('active'); } else { dot.classList.remove('active'); } }); // Update navigation buttons prevBtn.disabled = currentIndex === 0; nextBtn.disabled = currentIndex === screenshots.length - 1; // If it's the last screen, change next button text if (currentIndex === screenshots.length - 1) { nextBtn.innerHTML = `Finish <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>`; } else { nextBtn.innerHTML = `Next <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="M9 5l7 7-7 7" /> </svg>`; } // Animate feature indicator after a small delay setTimeout(() => { indicators.forEach((indicator, index) => { indicator.classList.remove('active'); if (index === currentIndex) { indicator.classList.add('active'); } }); }, 500); } // Navigate to previous screen function goToPrevious() { if (currentIndex > 0) { currentIndex--; updateUI(); } } // Navigate to next screen function goToNext() { if (currentIndex < screenshots.length - 1) { currentIndex++; updateUI(); } } // Navigate to specific screen by index function goToScreen(index) { if (index >= 0 && index < screenshots.length) { currentIndex = index; updateUI(); } } // Toggle tooltip visibility function toggleTooltip(tooltipId, show) { const tooltip = document.getElementById(tooltipId); if (tooltip) { if (show) { tooltip.classList.add('visible'); } else { tooltip.classList.remove('visible'); } } } // Custom cursor movement document.addEventListener('mousemove', (e) => { if (customCursor) { customCursor.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`; } }); // Event listeners prevBtn.addEventListener('click', goToPrevious); nextBtn.addEventListener('click', goToNext); progressDots.forEach(dot => { dot.addEventListener('click', () => { goToScreen(parseInt(dot.dataset.index)); }); }); hotspots.forEach(hotspot => { hotspot.addEventListener('mouseenter', () => { toggleTooltip(hotspot.dataset.tooltipId, true); if (customCursor) {
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Photosynthesis Interactive Learning</title> <style> :root { --primary: #2dd4bf; --primary-light: #a7f3ec; --secondary: #8b5cf6; --secondary-light: #ddd6fe; --dark: #334155; --light: #f8fafc; --success: #22c55e; --warning: #eab308; --info: #3b82f6; --danger: #ef4444; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); height: 700px; width: 700px; overflow: hidden; position: relative; } .container { width: 100%; height: 100%; padding: 20px; display: flex; flex-direction: column; align-items: center; position: relative; } .header { text-align: center; margin-bottom: 20px; width: 100%; } h1 { color: var(--dark); font-size: 28px; margin-bottom: 10px; background: linear-gradient(90deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); } .subtitle { color: var(--dark); font-size: 16px; opacity: 0.8; } .diagram-container { position: relative; width: 85%; height: 450px; background: white; border-radius: 24px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05), 0 6px 6px rgba(0, 0, 0, 0.07); overflow: hidden; transition: all 0.5s ease; display: flex; justify-content: center; align-items: center; } .diagram { position: relative; width: 85%; height: 85%; background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIiB2aWV3Qm94PSIwIDAgNTAwIDUwMCIgZmlsbD0ibm9uZSI+CiAgPCEtLSBTdW4gLS0+CiAgPGNpcmNsZSBjeD0iMzAwIiBjeT0iODAiIHI9IjUwIiBmaWxsPSIjZmNkMzRkIiAvPgogIDxjaXJjbGUgY3g9IjMwMCIgY3k9IjgwIiByPSI2MCIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1kYXNoYXJyYXk9IjEwLDEwIiBmaWxsPSJub25lIiAvPgogIAogIDwhLS0gVHJlZSAtLT4KICA8cGF0aCBkPSJNMjAwLDM5MCBDMjAwLDI1MCAxNTAsMjUwIDIwMCwyMDAgQzI1MCwyNTAgMzAwLDIwMCAyNTAsMzUwIFoiIGZpbGw9IiMyMmMwNWUiIC8+CiAgPHJlY3QgeD0iMjQwIiB5PSIzNTAiIHdpZHRoPSIyMCIgaGVpZ2h0PSI1MCIgZmlsbD0iIzkyNGMwMCIgLz4KICA8cmVjdCB4PSIyNTAiIHk9IjM4MCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjEwIiByeD0iNSIgZmlsbD0iIzkyNGMwMCIgLz4KICA8cmVjdCB4PSIyMTAiIHk9IjM2MCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjEwIiByeD0iNSIgZmlsbD0iIzkyNGMwMCIgLz4KICA8cmVjdCB4PSIxOTAiIHk9IjMyMCIgd2lkdGg9IjMwIiBoZWlnaHQ9IjgiIHJ4PSI0IiBmaWxsPSIjOTI0YzAwIiAvPgogIAogIDwhLS0gQXJyb3dzIC0tPgogIDxsaW5lIHgxPSIyODAiIHkxPSIxMDAiIHgyPSIyNDAiIHkyPSIxODAiIHN0cm9rZT0iIzMzNDE1NSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtZGFzaGFycmF5PSI1LDUiIC8+CiAgPHBvbHlnb24gcG9pbnRzPSIyNDAsMTgwIDIzMCwxNjUgMjUwLDE2NSIgZmlsbD0iIzMzNDE1NSIgLz4KICA8bGluZSB4MT0iMTgwIiB5MT0iMzAwIiB4Mj0iMTIwIiB5Mj0iMzUwIiBzdHJva2U9IiMzMzQxNTUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWRhc2hhcnJheT0iNSw1IiAvPgogIDxwb2x5Z29uIHBvaW50cz0iMTIwLDM1MCAxMTUsMzMwIDEzNSwzNDAiIGZpbGw9IiMzMzQxNTUiIC8+CiAgPGxpbmUgeDE9IjI4MCIgeTE9IjMwMCIgeDI9IjM0MCIgeTI9IjM1MCIgc3Ryb2tlPSIjMzM0MTU1IiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1kYXNoYXJyYXk9IjUsNSIgLz4KICA8cG9seWdvbiBwb2ludHM9IjM0MCwzNTAgMzI1LDMzNSAzNDUsMzM1IiBmaWxsPSIjMzM0MTU1IiAvPgogIAogIDwhLS0gV2F0ZXIgLS0+CiAgPHBhdGggZD0iTTEyMCw0MDAgQzEwMCwzODAgODAsMzkwIDgwLDQwMCBDODAsNDEwIDEwMCw0MjAgMTIwLDQwMCBaIiBmaWxsPSIjM2I4MmY2IiAvPgogIDxwYXRoIGQ9Ik0xMDAsMzgwIEMxMjAsMzYwIDE0MCwzNzAgMTQwLDM4MCBDMTQwLDM5MCAxMjAsNDAwIDEwMCwzODAgWiIgZmlsbD0iIzNiODJmNiIgLz4KICA8cGF0aCBkPSJNMTAwLDQxMCBDMTIwLDM5MCAxNDAsNDAwIDE0MCw0MTAgQzE0MCw0MjAgMTIwLDQzMCAxMDAsNDEwIFoiIGZpbGw9IiMzYjgyZjYiIC8+CiAgCiAgPCEtLSBDTzIgLS0+CiAgPGNpcmNsZSBjeD0iMzYwIiBjeT0iMzgwIiByPSIyMCIgZmlsbD0iI2VmNDQ0NCIgZmlsbC1vcGFjaXR5PSIwLjIiIC8+CiAgPHRleHQgeD0iMzQ1IiB5PSIzODUiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiMzMzQxNTUiPkNPMjwvdGV4dD4KICA8Y2lyY2xlIGN4PSIzODAiIGN5PSIzNTAiIHI9IjE1IiBmaWxsPSIjZWY0NDQ0IiBmaWxsLW9wYWNpdHk9IjAuMiIgLz4KICA8dGV4dCB4PSIzNzAiIHk9IjM1NSIgZm9udC1zaXplPSIxMCIgZmlsbD0iIzMzNDE1NSI+Q08yPC90ZXh0PgogIDxjaXJjbGUgY3g9IjM0MCIgY3k9IjQwMCIgcj0iMTUiIGZpbGw9IiNlZjQ0NDQiIGZpbGwtb3BhY2l0eT0iMC4yIiAvPgogIDx0ZXh0IHg9IjMzMCIgeT0iNDA1IiBmb250LXNpemU9IjEwIiBmaWxsPSIjMzM0MTU1Ij5DTzI8L3RleHQ+Cjwvc3ZnPg==') center/contain no-repeat; } .hotspot { position: absolute; width: 32px; height: 32px; border-radius: 50%; background: white; display: flex; justify-content: center; align-items: center; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border: 2px solid; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); transform: scale(1); z-index: 5; } .hotspot:hover { transform: scale(1.15); } .hotspot::after { content: attr(data-number); font-weight: bold; font-size: 16px; } .hotspot-1 { top: 15%; right: 35%; border-color: var(--warning); color: var(--warning); } .hotspot-2 { top: 40%; left: 35%; border-color: var(--success); color: var(--success); } .hotspot-3 { bottom: 25%; left: 20%; border-color: var(--info); color: var(--info); } .hotspot-4 { bottom: 25%; right: 20%; border-color: var(--danger); color: var(--danger); } .info-panel { position: absolute; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.97); border-radius: 24px; padding: 30px; display: flex; flex-direction: column; transform: translateY(100%); opacity: 0; transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 3; } .info-panel.active { transform: translateY(0); opacity: 1; } .info-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .info-title { font-size: 24px; font-weight: bold; color: var(--dark); } .close-btn { background: var(--light); border: none; width: 36px; height: 36px; border-radius: 50%; cursor: pointer; display: flex; justify-content: center; align-items: center; transition: all 0.3s ease; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } .close-btn:hover { background: #f1f5f9; transform: rotate(90deg); } .info-content { flex: 1; overflow-y: auto; padding-right: 10px; } .info-content p { margin-bottom: 15px; line-height: 1.6; color: var(--dark); } .info-content ul { padding-left: 20px; margin-bottom: 15px; } .info-content li { margin-bottom: 8px; line-height: 1.5; } .info-footer { display: flex; justify-content: space-between; margin-top: 15px; } .info-tag { padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; display: inline-block; } /* Custom scrollbar */ .info-content::-webkit-scrollbar { width: 5px; } .info-content::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 10px; } .info-content::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } .info-content::-webkit-scrollbar-thumb:hover { background: #94a3b8; } .progress-container { width: 85%; height: 6px; background: #e2e8f0; border-radius: 3px; margin-top: 20px; overflow: hidden; } .progress-bar { height: 100%; background: linear-gradient(90deg, var(--primary), var(--secondary)); width: 0; transition: width 0.5s ease; border-radius: 3px; } .progress-text { margin-top: 10px; font-size: 14px; color: var(--dark); text-align: center; } /* Animations */ @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(45, 212, 191, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(45, 212, 191, 0); } 100% { box-shadow: 0 0 0 0 rgba(45, 212, 191, 0); } } .pulse { animation: pulse 2s infinite; } .illustration { width: 100%; height: 150px; margin: 15px 0; border-radius: 12px; background-size: cover; background-position: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); } .illustration-1 { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iMTUwIiB2aWV3Qm94PSIwIDAgNDAwIDE1MCIgZmlsbD0ibm9uZSI+CiAgPGNpcmNsZSBjeD0iMjAwIiBjeT0iNzUiIHI9IjYwIiBmaWxsPSIjZmNkMzRkIiBmaWxsLW9wYWNpdHk9IjAuNyIvPgogIDxjaXJjbGUgY3g9IjIwMCIgY3k9Ijc1IiByPSI3MCIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1kYXNoYXJyYXk9IjEwLDEwIiBmaWxsPSJub25lIiAvPgogIDwhLS0gU3VuIHJheXMgLS0+CiAgPGxpbmUgeDE9IjIwMCIgeTE9IjAiIHgyPSIyMDAiIHkyPSIyMCIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjMiLz4KICA8bGluZSB4MT0iMjAwIiB5MT0iMTMwIiB4Mj0iMjAwIiB5Mj0iMTUwIiBzdHJva2U9IiNmY2QzNGQiIHN0cm9rZS13aWR0aD0iMyIvPgogIDxsaW5lIHgxPSIyNzAiIHkxPSI3NSIgeDI9IjI5MCIgeTI9Ijc1IiBzdHJva2U9IiNmY2QzNGQiIHN0cm9rZS13aWR0aD0iMyIvPgogIDxsaW5lIHgxPSIxMTAiIHkxPSI3NSIgeDI9IjEzMCIgeTI9Ijc1IiBzdHJva2U9IiNmY2QzNGQiIHN0cm9rZS13aWR0aD0iMyIvPgogIDxsaW5lIHgxPSIyNTAiIHkxPSIyNSIgeDI9IjI2NSIgeTI9IjQwIiBzdHJva2U9IiNmY2QzNGQiIHN0cm9rZS13aWR0aD0iMyIvPgogIDxsaW5lIHgxPSIxMzUiIHkxPSIxMTAiIHgyPSIxNTAiIHkyPSIxMjUiIHN0cm9rZT0iI2ZjZDM0ZCIgc3Ryb2tlLXdpZHRoPSIzIi8+CiAgPGxpbmUgeDE9IjI1MCIgeTE9IjEyNSIgeDI9IjI2NSIgeTI9IjExMCIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjMiLz4KICA8bGluZSB4MT0iMTM1IiB5MT0iNDAiIHgyPSIxNTAiIHkyPSIyNSIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjMiLz4KICA8IS0tIEVuZXJneSBhcnJvd3MgLS0+CiAgPHBhdGggZD0iTTMyMCw1MCBDMzQwLDYwIDM0MCw5MCAzMjAsMTAwIiBzdHJva2U9IiNmY2QzNGQiIHN0cm9rZS13aWR0aD0iMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLWRhc2hhcnJheT0iNSw1Ii8+CiAgPHBvbHlnb24gcG9pbnRzPSIzMjAsMTAwIDMxMCw4NSAzMzAsOTAiIGZpbGw9IiNmY2QzNGQiLz4KICA8cGF0aCBkPSJNODAsNTAgQzYwLDYwIDYwLDkwIDgwLDEwMCIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9Im5vbmUiIHN0cm9rZS1kYXNoYXJyYXk9IjUsNSIvPgogIDxwb2x5Z29uIHBvaW50cz0iODAsMTAwIDcwLDkwIDkwLDg1IiBmaWxsPSIjZmNkMzRkIi8+CiAgPHRleHQgeD0iMTgwIiB5PSI3NSIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzMzNDE1NSIgZm9udC13ZWlnaHQ9ImJvbGQiPlNvbGFyIEVuZXJneTwvdGV4dD4KPC9zdmc+'); } .illustration-2 { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iMTUwIiB2aWV3Qm94PSIwIDAgNDAwIDE1MCIgZmlsbD0ibm9uZSI+CiAgPCEtLSBMZWFmIGNyb3NzLXNlY3Rpb24gLS0+CiAgPGVsbGlwc2UgY3g9IjIwMCIgY3k9Ijc1IiByeD0iMTUwIiByeT0iNDAiIGZpbGw9IiMyMmMwNWUiIGZpbGwtb3BhY2l0eT0iMC4yIi8+CiAgPGVsbGlwc2UgY3g9IjIwMCIgY3k9Ijc1IiByeD0iMTUwIiByeT0iNDAiIHN0cm9rZT0iIzIyYzA1ZSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIi8+CiAgPGVsbGlwc2UgY3g9IjIwMCIgY3k9Ijc1IiByeD0iMTAwIiByeT0iMzAiIGZpbGw9IiMyMmMwNWUiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CiAgPGVsbGlwc2UgY3g9IjIwMCIgY3k9Ijc1IiByeD0iNTAiIHJ5PSIyMCIgZmlsbD0iIzIyYzA1ZSIgZmlsbC1vcGFjaXR5PSIwLjYiLz4KICA8IS0tIENobG9yb3BsYXN0cyAtLT4KICA8Y2lyY2xlIGN4PSIxNTAiIGN5PSI2NSIgcj0iOCIgZmlsbD0iIzIyYzA1ZSIvPgogIDxjaXJjbGUgY3g9IjE3MCIgY3k9Ijg1IiByPSI4IiBmaWxsPSIjMjJjMDVlIi8+CiAgPGNpcmNsZSBjeD0iMjAwIiBjeT0iNjAiIHI9IjgiIGZpbGw9IiMyMmMwNWUiLz4KICA8Y2lyY2xlIGN4PSIyMjAiIGN5PSI4MCIgcj0iOCIgZmlsbD0iIzIyYzA1ZSIvPgogIDxjaXJjbGUgY3g9IjI1MCIgY3k9IjY1IiByPSI4IiBmaWxsPSIjMjJjMDVlIi8+CiAgPGNpcmNsZSBjeD0iMjMwIiBjeT0iOTAiIHI9IjgiIGZpbGw9IiMyMmMwNWUiLz4KICA8Y2lyY2xlIGN4PSIxODAiIGN5PSI5MCIgcj0iOCIgZmlsbD0iIzIyYzA1ZSIvPgogIDxjaXJjbGUgY3g9IjE4MCIgY3k9IjYwIiByPSI4IiBmaWxsPSIjMjJjMDVlIi8+CiAgPCEtLSBTdW4gcmF5IHBhdGhzIC0tPgogIDxwYXRoIGQ9Ik0xNjAsNDAgQzE3MCw0NSAxNjUsNTUgMTUwLDY1IiBzdHJva2U9IiNmY2QzNGQiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWRhc2hhcnJheT0iMiwxIi8+CiAgPHBhdGggZD0iTTIwMCwzMCBDMjA1LDQwIDIwNSw1MCAyMDAsNjAiIHN0cm9rZT0iI2ZjZDM0ZCIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIiBzdHJva2UtZGFzaGFycmF5PSIyLDEiLz4KICA8cGF0aCBkPSJNMjQwLDQwIEMyMzAsNDUgMjM1LDU1IDI1MCw2NSIgc3Ryb2tlPSIjZmNkMzRkIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiIHN0cm9rZS1kYXNoYXJyYXk9IjIsMSIvPgogIDwhLS0gQ08yIGVudGVyaW5nIC0tPgogIDxwYXRoIGQ9Ik02MCw3MCBDOCA5MCw4MCwxMzAsODAiIHN0cm9rZT0iIzMzNDE1NSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIiBzdHJva2UtZGFzaGFycmF5PSIzLDMiLz4KICA8Y2lyY2xlIGN4PSI0MCIgY3k9IjYwIiByPSIxNCIgZmlsbD0iI2VmNDQ0NCIgZmlsbC1vcGFjaXR5PSIwLjIiIC8+CiAgPHRleHQgeD0iMzAiIHk9IjY1IiBmb250LXNpemU9IjEyIiBmaWxsPSIjMzM0MTU1Ij5DTzI8L3RleHQ+CiAgPGNpcmNsZSBjeD0iNzAiIGN5PSI3MCIgcj0iMTAiIGZpbGw9IiNlZjQ0NDQiIGZpbGwtb3BhY2l0eT0iMC4yIiAvPgogIDx0ZXh0IHg9IjYyIiB5PSI3NSIgZm9udC1zaXplPSI4IiBmaWxsPSIjMzM0MTU1Ij5DTzI8L3RleHQ+CiAgPHBvbHlnb24gcG9pbnRzPSI4MCw4MCA3MCw3NSA4NSw3MCIgZmlsbD0iIzMzNDE1NSIvPgogIDwhLS0gT3h5Z2VuIGV4aXRpbmcgLS0+CiAgPHBhdGggZD0iTTM0MCw3MCBDQzgwLDkwLDMyMCwxMzAsOTAiIHN0cm9rZT0iIzMzNDE1NSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIiBzdHJva2UtZGFzaGFycmF5PSIzLDMiLz4KICA8Y2lyY2xlIGN4PSIzNjAiIGN5PSI2MCIgcj0iMTQiIGZpbGw9IiMzYjgyZjYiIGZpbGwtb3BhY2l0eT0iMC4yIiAvPgogIDx0ZXh0IHg9IjM1MCIgeT0iNjUiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiMzMzQxNTUiPk8yPC90ZXh0PgogIDxjaXJjbGUgY3g9IjMzMCIgY3k9IjcwIiByPSIxMCIgZmlsbD0iIzNiODJmNiIgZmlsbC1vcGFjaXR5PSIwLjIiIC8+CiAgPHRleHQgeD0iMzIyIiB5PSI3NSIgZm9udC1zaXplPSI4IiBmaWxsPSIjMzM0MTU1Ij5PMjwvdGV4dD4KICA8cG9seWdvbiBwb2ludHM9IjMyMCw4MCAzMTUsNzAgMzMwLDc1IiBmaWxsPSIjMzM0MTU1Ii8+CiAgPHRleHQgeD0iMTkwIiB5PSI3NSIgZm9udC1zaXplPSIxMCIgZmlsbD0iIzMzNDE1NSIgZm9udC13ZWlnaHQ9ImJvbGQiPkNobG9yb3BsYXN0PC90ZXh0Pgo8L3N2Zz4='); } .illustration-3 { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iMTUwIiB2aWV3Qm94PSIwIDAgNDAwIDE1MCIgZmlsbD0ibm9uZSI+CiAgPCEtLSBSb290cyBhbmQgd2F0ZXIgLS0+CiAgPHBhdGggZD0iTTE1MCwxMCBDMTUwLDgwIDE4MCwxMjAgMjAwLDEyMCBDMjIwLDEyMCAyNTAsODAgMjUwLDEwIiBzdHJva2U9IiM5MjRjMDAiIHN0cm9rZS13aWR0aD0iNCIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Ik0xNzAsMTAwIEMxNTAsMTEwIDE2MCwxMzAgMTcwLDE0MCBDMTgwLDEzMCAxOTAsMTEwIDE3MCwxMDAiIGZpbGw9IiMzYjgyZjYiIGZpbGwtb3BhY2l0eT0iMC42Ii8+CiAgPHBhdGggZD0iTTIzMCwxMDAgQzIxMCwxMTAgMjIwLDEzMCAyMzAsMTQwIEMyNDAsMTMwIDI1MCwxMTAgMjMwLDEwMCIgZmlsbD0iIzNiODJmNiIgZmlsbC1vcGFjaXR5PSIwLjYiLz4KICA8cGF0aCBkPSJNMjAwLDkwIEMxODAsMTAwIDE5MCwxMjAgMjAwLDEzMCBDMjEwLDEyMCAyMjAsMTAwIDIwMCw5MCIgZmlsbD0iIzNiODJmNiIgZmlsbC1vcGFjaXR5PSIwLjYiLz4KICA8cGF0aCBkPSJNMTUwLDExMCBDMTMwLDEyMCAxNDAsMTQwIDE1MCwxNTAgQzE2MCwxNDAgMTcwLDEyMCAxNTAsMTEwIiBmaWxsPSIjM2I4MmY2IiBmaWxsLW9wYWNpdHk9IjAuNiIvPgogIDxwYXRoIGQ9Ik0yNTAsMTEwIEMyMzAsMTIwIDI0MCwxNDAgMjUwLDE1MCBDMjYwLDE0MCAyNzAsMTIwIDI1MCwxMTAiIGZpbGw9IiMzYjgyZjYiIGZpbGwtb3BhY2l0eT0iMC42Ii8+CiAgPCEtLSBSb290IGhhaXJzIC0tPgogIDxwYXRoIGQ9Ik0xNjAsODAgQzE1MCw4MCAxNDAsODUgMTMwLDkwIiBzdHJva2U9IiM5MjRjMDAiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Ik0xODAsOTAgQzE3
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>RadSpot - Medical Imaging Analysis</title> <style> :root { --primary: #1e5f74; --secondary: #3a9ab7; --accent: #48cae4; --light: #d6f6ff; --dark: #0a2e38; --success: #00b894; --warning: #fdcb6e; --danger: #e17055; --neutral: #f3f4f6; --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; } body { display: flex; flex-direction: column; height: 100vh; width: 100%; background-color: #f8fafc; color: var(--dark); overflow: hidden; } .app-container { display: flex; flex-direction: column; max-width: 700px; width: 100%; height: 700px; margin: 0 auto; background-color: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); overflow: hidden; } header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background-color: white; border-bottom: 1px solid rgba(0, 0, 0, 0.05); } .logo { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 18px; color: var(--primary); } .logo svg { width: 22px; height: 22px; } .controls { display: flex; gap: 12px; } .btn { background: none; border: none; cursor: pointer; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 500; transition: var(--transition); display: flex; align-items: center; gap: 6px; } .btn-primary { background-color: var(--primary); color: white; } .btn-primary:hover { background-color: var(--dark); } .btn-outline { border: 1px solid var(--primary); color: var(--primary); } .btn-outline:hover { background-color: var(--light); } .btn svg { width: 16px; height: 16px; } main { display: flex; flex: 1; overflow: hidden; position: relative; } .sidebar { width: 240px; background-color: #f8fafc; border-right: 1px solid rgba(0, 0, 0, 0.05); padding: 15px; overflow-y: auto; transition: transform 0.3s ease; } .sidebar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .sidebar-title { font-size: 14px; font-weight: 600; color: var(--dark); } .patient-info { background-color: white; border-radius: 8px; padding: 12px; margin-bottom: 15px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); } .patient-name { font-weight: 600; font-size: 15px; margin-bottom: 2px; } .patient-details { font-size: 12px; color: #64748b; margin-bottom: 8px; } .tag { display: inline-block; font-size: 11px; padding: 2px 6px; border-radius: 4px; background-color: var(--light); color: var(--primary); font-weight: 500; } .findings-list { margin-top: 15px; } .finding-item { background-color: white; border-radius: 8px; padding: 12px; margin-bottom: 10px; cursor: pointer; transition: var(--transition); border-left: 3px solid transparent; } .finding-item:hover { transform: translateX(2px); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); } .finding-item.active { border-left-color: var(--accent); background-color: var(--light); } .finding-title { font-weight: 600; font-size: 13px; margin-bottom: 4px; } .finding-desc { font-size: 12px; color: #64748b; } .finding-severity { margin-top: 6px; font-size: 11px; font-weight: 500; } .severity-high { color: var(--danger); } .severity-medium { color: var(--warning); } .severity-low { color: var(--success); } .image-viewer { flex: 1; background-color: #0a2e38; position: relative; overflow: hidden; display: flex; justify-content: center; align-items: center; } .medical-image { max-width: 90%; max-height: 90%; object-fit: contain; transition: transform 0.3s ease; } .hotspot { position: absolute; width: 24px; height: 24px; border-radius: 50%; background-color: rgba(72, 202, 228, 0.2); border: 2px solid var(--accent); cursor: pointer; transform: translate(-50%, -50%); transition: all 0.3s ease; z-index: 10; } .hotspot::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 8px; height: 8px; background-color: var(--accent); border-radius: 50%; } .hotspot.active { width: 32px; height: 32px; background-color: rgba(72, 202, 228, 0.4); } .hotspot.pulse::before { content: ''; position: absolute; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(72, 202, 228, 0.3); border-radius: 50%; animation: pulse 2s infinite; z-index: -1; } @keyframes pulse { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(2.5); opacity: 0; } } .tooltip { position: absolute; background-color: white; border-radius: 8px; padding: 12px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); width: 220px; pointer-events: none; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; transform: translateY(10px); z-index: 100; } .tooltip.show { opacity: 1; transform: translateY(0); pointer-events: auto; } .tooltip-title { font-weight: 600; font-size: 14px; margin-bottom: 6px; color: var(--dark); } .tooltip-desc { font-size: 12px; color: #475569; margin-bottom: 8px; } .tooltip-metrics { background-color: var(--neutral); border-radius: 6px; padding: 8px; font-size: 11px; color: #475569; margin-bottom: 8px; } .tooltip-metrics p { margin-bottom: 4px; display: flex; justify-content: space-between; } .tooltip-metrics strong { color: var(--dark); } .tooltip-footer { display: flex; justify-content: space-between; border-top: 1px solid rgba(0, 0, 0, 0.05); padding-top: 8px; font-size: 11px; } .zoom-controls { position: absolute; bottom: 20px; right: 20px; display: flex; gap: 10px; background-color: rgba(10, 46, 56, 0.7); padding: 8px; border-radius: 8px; } .zoom-btn { width: 30px; height: 30px; border-radius: 4px; background-color: rgba(255, 255, 255, 0.1); display: flex; align-items: center; justify-content: center; color: white; cursor: pointer; transition: var(--transition); } .zoom-btn:hover { background-color: rgba(255, 255, 255, 0.2); } .zoom-btn svg { width: 18px; height: 18px; } .toolbar { position: absolute; left: 20px; bottom: 20px; display: flex; gap: 10px; background-color: rgba(10, 46, 56, 0.7); padding: 8px; border-radius: 8px; } .tool-btn { width: 30px; height: 30px; border-radius: 4px; background-color: rgba(255, 255, 255, 0.1); display: flex; align-items: center; justify-content: center; color: white; cursor: pointer; transition: var(--transition); } .tool-btn:hover, .tool-btn.active { background-color: rgba(72, 202, 228, 0.3); } .annotation-input { position: absolute; background-color: white; border-radius: 8px; padding: 12px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); width: 250px; z-index: 100; display: none; } .annotation-input.show { display: block; } .annotation-input input, .annotation-input textarea { width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 10px; font-size: 13px; } .annotation-input textarea { resize: none; height: 80px; } .btn-group { display: flex; gap: 6px; justify-content: flex-end; } .loader-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255, 255, 255, 0.7); display: flex; justify-content: center; align-items: center; z-index: 1000; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .loader-overlay.show { opacity: 1; pointer-events: auto; } .loader { width: 40px; height: 40px; border-radius: 50%; border: 3px solid transparent; border-top-color: var(--accent); border-right-color: var(--accent); animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @media (max-width: 600px) { .sidebar { position: absolute; height: 100%; top: 0; left: 0; transform: translateX(-100%); z-index: 20; width: 85%; max-width: 280px; } .sidebar.show { transform: translateX(0); } .mobile-toggle { display: block; position: absolute; top: 15px; left: 15px; z-index: 30; background-color: rgba(255, 255, 255, 0.8); border-radius: 6px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; } .tooltip { width: 180px; } } </style> </head> <body> <div class="app-container"> <header> <div class="logo"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <path d="M8 12h8"></path> <path d="M12 8v8"></path> </svg> RadSpot </div> <div class="controls"> <button class="btn btn-outline" id="resetView"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M3 9h18v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9Z"></path> <path d="M3 9V5a2 2 0 0 1 2-2h2"></path> <path d="M15 3h2a2 2 0 0 1 2 2v4"></path> </svg> Reset View </button> <button class="btn btn-primary" id="generateReport"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line> <line x1="16" y1="17" x2="8" y2="17"></line> <polyline points="10 9 9 9 8 9"></polyline> </svg> Generate Report </button> </div> </header> <main> <div class="sidebar"> <div class="sidebar-header"> <div class="sidebar-title">Patient Information</div> </div> <div class="patient-info"> <div class="patient-name">Sarah Thompson</div> <div class="patient-details">42y, Female • ID: PT-24891</div> <div class="patient-details">CT Chest w/ Contrast • 05/21/2023</div> <div class="tag">URGENT</div> </div> <div class="sidebar-header"> <div class="sidebar-title">Findings</div> </div> <div class="findings-list"> <div class="finding-item active" data-id="1"> <div class="finding-title">Pulmonary Nodule</div> <div class="finding-desc">RUL 8mm solid nodule</div> <div class="finding-severity severity-high">High Significance</div> </div> <div class="finding-item" data-id="2"> <div class="finding-title">Lymphadenopathy</div> <div class="finding-desc">Subcarinal LN 14mm</div> <div class="finding-severity severity-medium">Medium Significance</div> </div> <div class="finding-item" data-id="3"> <div class="finding-title">Pulmonary Embolism</div> <div class="finding-desc">Right lower lobe PE</div> <div class="finding-severity severity-high">High Significance</div> </div> <div class="finding-item" data-id="4"> <div class="finding-title">Ground Glass Opacity</div> <div class="finding-desc">LLL peripheral GGO</div> <div class="finding-severity severity-medium">Medium Significance</div> </div> <div class="finding-item" data-id="5"> <div class="finding-title">Pleural Effusion</div> <div class="finding-desc">Small right pleural effusion</div> <div class="finding-severity severity-low">Low Significance</div> </div> </div> </div> <div class="mobile-toggle"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="3" y1="12" x2="21" y2="12"></line> <line x1="3" y1="6" x2="21" y2="6"></line> <line x1="3" y1="18" x2="21" y2="18"></line> </svg> </div> <div class="image-viewer"> <img src="https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3" alt="CT Scan Image" class="medical-image" id="medicalImage"> <div class="hotspot pulse active" style="top: 30%; left: 35%;" data-id="1"></div> <div class="hotspot" style="top: 50%; left: 60%;" data-id="2"></div> <div class="hotspot" style="top: 65%; left: 42%;" data-id="3"></div> <div class="hotspot" style="top: 55%; left: 30%;" data-id="4"></div> <div class="hotspot" style="top: 70%; left: 65%;" data-id="5"></div> <div class="tooltip"> <div class="tooltip-title">Pulmonary Nodule</div> <div class="tooltip-desc">Right upper lobe solid nodule with spiculated margins, suspicious for primary lung malignancy.</div> <div class="tooltip-metrics"> <p><span>Size:</span> <strong>8mm x 7mm</strong></p> <p><span>HU Value:</span> <strong>+42 HU</strong></p> <p><span>Location:</span> <strong>RUL, Segment 2</strong></p> </div> <div class="tooltip-footer"> <span>AI Probability: <strong>87% Malignant</strong></span> </div> </div> <div class="zoom-controls"> <div class="zoom-btn" id="zoomIn"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="11" y1="8" x2="11" y2="14"></line> <line x1="8" y1="11" x2="14" y2="11"></line> </svg> </div> <div class="zoom-btn" id="zoomOut"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="8" y1="11" x2="14" y2="11"></line> </svg> </div> </div> <div class="toolbar"> <div class="tool-btn" id="hotspotTool" title="Add Hotspot"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="16"></line> <line x1="8" y1="12" x2="16" y2="12"></line> </svg> </div> <div class="tool-btn" id="measureTool" title="Measure"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M2 12h20"></path> <path d="M6 8v8"></path> <path d="M18 8v8"></path> <path d="M14 8v8"></path> <path d="M10 8v8"></path> </svg> </div> <div class="tool-btn" id="windowTool" title="Window/Level"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="3"></circle> <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path> </svg> </div> </div> <div class="annotation-input"> <input type="text" placeholder="Title" id="annotationTitle"> <textarea placeholder="Description" id="annotationDesc"></textarea> <div class="btn-group"> <button class="btn btn-outline" id="cancelAnnotation">Cancel</button> <button class="btn btn-primary" id="saveAnnotation">Save</button> </div> </div> </div> <div class="loader-overlay"> <div class="loader"></div> </div> </main> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Elements const medicalImage = document.getElementById('medicalImage'); const hotspots = document.querySelectorAll('.hotspot'); const tooltip = document.querySelector('.tooltip'); const zoomIn = document.getElementById('zoomIn'); const zoomOut = document.getElementById('zoomOut'); const resetView = document.getElementById('resetView'); const generateReport = document.getElementById('generateReport'); const hotspotTool = document.getElementById('hotspotTool'); const measureTool = document.getElementById('measureTool'); const windowTool = document.getElementById('windowTool'); const findingItems = document.querySelectorAll('.finding-item'); const mobileToggle = document.querySelector('.mobile-toggle'); const sidebar = document.querySelector('.sidebar'); const loaderOverlay = document.querySelector('.loader-overlay'); const annotationInput = document.querySelector('.annotation-input'); const annotationTitle = document.getElementById('annotationTitle'); const annotationDesc = document.getElementById('annotationDesc'); const saveAnnotation = document.getElementById('saveAnnotation'); const cancelAnnotation = document.getElementById('cancelAnnotation'); // Variables let scale = 1; let isDragging = false; let startX, startY, translateX = 0, translateY = 0; let activeHotspot = null; let activeToolBtn = null; let addingHotspot = false; let newHotspotCoords = { x: 0, y: 0 }; // Tooltip content data const tooltipData = { 1: { title: "Pulmonary Nodule", desc: "Right upper lobe solid nodule with spiculated margins, suspicious for primary lung malignancy.", metrics: { "Size": "8mm x 7mm", "HU Value": "+42 HU", "Location": "RUL, Segment 2" }, probability: "87% Malignant" }, 2: { title: "Lymphadenopathy", desc: "Subcarinal lymph node enlargement with homogeneous enhancement, concerning for metastatic disease.", metrics: { "Size": "14mm x 12mm", "HU Value": "+78 HU", "Location": "Subcarinal" }, probability: "62% Metastatic" }, 3: { title: "Pulmonary Embolism", desc: "Filling defect in the right lower lobe pulmonary artery branch, consistent with acute pulmonary embolism.", metrics: { "Size": "22mm length", "HU Value": "+28 HU", "Location": "RLL Segmental" }, probability: "93% Acute PE" }, 4: { title: "Ground Glass Opacity", desc: "Left lower lobe peripheral ground glass opacity with rounded morphology, possibly infectious or inflammatory.", metrics: { "Size": "26mm x 18mm", "HU Value": "-450 HU", "Location": "LLL, Segment 8" }, probability: "54% Infectious" }, 5: { title: "Pleural Effusion", desc: "Small right-sided pleural effusion with dependent layering, likely reactive to adjacent pathology.", metrics: { "Size": "12mm depth", "HU Value": "+10 HU", "Location": "Right hemithorax" }, probability: "78% Exudative" } }; // Initialize updateTooltipContent(1); // Event listeners hotspots.forEach(hotspot => { hotspot.addEventListener('click', (e) => { e.stopPropagation(); activateHotspot(hotspot); const id = hotspot.getAttribute('data-id'); updateTooltipContent(id); positionTooltip(hotspot); tooltip.classList.add('show'); // Activate corresponding finding in sidebar findingItems.forEach(item => { item.classList.remove('active'); if (item.getAttribute('data-id') === id) { item.classList.add('active'); item.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }); }); hotspot.addEventListener('mouseenter', () => { if (!tooltip.classList.contains('show')) { const id = hotspot.getAttribute('data-id'); updateTooltipContent(id); positionTooltip(hotspot); tooltip.classList.add('show'); } }); }); tooltip.addEventListener('mouseleave', () => { if (!activeHotspot) { tooltip.classList.remove('show'); } }); // Finding items click findingItems.forEach(item => { item.addEventListener('click', () => { const id = item.getAttribute('data-id'); findingItems.forEach(i => i.classList.remove('active')); item.classList.add('active'); // Find and activate corresponding hotspot const hotspot = document.querySelector(`.hotspot[data-id="${id}"]`); if (hotspot) { activateHotspot(hotspot); updateTooltipContent(id); positionTooltip(hotspot); tooltip.classList.add('show'); // Center view on the hotspot const rect = hotspot.getBoundingClientRect(); const imageRect = medicalImage.getBoundingClientRect(); const imageViewerRect = document.querySelector('.image-viewer').getBoundingClientRect(); const centerX = (rect.left + rect.width/2) - (imageViewerRect.left + imageViewerRect.width/2); const centerY = (rect.top + rect.height/2) - (imageViewerRect.top + imageViewerRect.height/2); translateX -= centerX / scale; translateY -= centerY / scale; updateImageTransform(); } // On mobile, hide sidebar after selection if (window.innerWidth < 600) { sidebar.classList.remove('show'); } }); }); // Image zoom and pan zoomIn.addEventListener('click', () => { scale = Math.min(scale * 1.2, 3); updateImageTransform(); }); zoomOut.addEventListener('click', () => { scale = Math.max(scale / 1.2, 0.5); updateImageTransform(); }); resetView.addEventListener('click', () => { scale = 1; translateX = 0; translateY = 0; updateImageTransform(); tooltip.classList.remove('show'); hotspots.forEach(h => h.classList.remove('active')); activeHotspot = null; }); // Image dragging medicalImage.addEventListener('mousedown', startDrag); medicalImage.addEventListener('touchstart', handleTouchStart); document.addEventListener('mousemove', drag); document.addEventListener('touchmove', handleTouchMove); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); // Image viewer click to hide tooltip document.querySelector('.image-viewer').addEventListener('click', (e) => { if (e.target.classList.contains('image-viewer') || e.target === medicalImage) { tooltip.classList.remove('show'); hotspots.forEach(h => h.classList.remove('active')); activeHotspot = null; if (addingHotspot) { // Get coordinates relative to image viewer const imageViewer = document.querySelector('.image-viewer'); const rect = imageViewer.getBoundingClientRect(); newHotspotCoords.x = ((e.clientX - rect.left) / rect.width) * 100; newHotspotCoords.y = ((e.clientY - rect.top) / rect.height) * 100; // Show annotation form annotationInput.style.left = `${e.clientX - rect.left}px`; annotationInput.style.top = `${e.clientY - rect.top}px`; annotationInput.classList.add('show'); annotationTitle.focus(); } } }); // Generate report button generateReport.addEventListener('click', () => { loaderOverlay.classList.add('show'); setTimeout(() => { loaderOverlay.classList.remove('show'); // Show success message or perform other actions alert("Report generated and sent to PACS successfully!"); }, 2000); }); // Tools hotspotTool.addEventListener('click', () => { toggleTool(
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Event Venue Interactive Map</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f8f9fa; width: 100%; height: 100%; overflow: hidden; } .container { width: 700px; height: 700px; padding: 20px; position: relative; background-color: #ffffff; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); overflow: hidden; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .header h1 { font-size: 1.8rem; color: #2d3748; font-weight: 700; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .controls { display: flex; align-items: center; gap: 12px; } .controls button { padding: 8px 12px; border: none; background-color: #4a5568; color: white; border-radius: 6px; font-weight: 500; font-size: 0.9rem; cursor: pointer; transition: all 0.3s ease; } .controls button:hover { background-color: #2d3748; transform: translateY(-2px); } .zoom-controls { position: absolute; right: 30px; bottom: 30px; display: flex; flex-direction: column; gap: 10px; z-index: 100; } .zoom-btn { width: 40px; height: 40px; border-radius: 50%; background-color: white; border: none; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; font-size: 1.2rem; color: #4a5568; } .zoom-btn:hover { transform: scale(1.1); background-color: #f7fafc; } .map-container { position: relative; overflow: hidden; width: 100%; height: 540px; border-radius: 12px; background-color: #f7fafc; transition: transform 0.3s ease; } .map { width: 150%; height: 150%; position: relative; transform-origin: 0 0; background-color: #edf2f7; transition: transform 0.3s ease; } .venue { position: absolute; width: 500px; height: 300px; background-color: #fff; left: 50%; top: 40%; transform: translate(-50%, -50%); border-radius: 8px; border: 2px solid #a0aec0; overflow: hidden; } .stage { position: absolute; width: 200px; height: 60px; background-color: #b794f4; left: 50%; top: 0; transform: translateX(-50%); border-radius: 0 0 30px 30px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: white; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; } .stage:hover { background-color: #9f7aea; box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } .seating-area { position: absolute; width: 100%; height: calc(100% - 60px); top: 60px; display: flex; flex-direction: column; padding: 20px; } .section { display: flex; justify-content: center; margin-bottom: 15px; gap: 6px; } .seat { width: 20px; height: 20px; background-color: #cbd5e0; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; } .seat:hover { transform: scale(1.2); background-color: #4299e1; } .seat.premium { background-color: #f6ad55; } .seat.premium:hover { background-color: #ed8936; } .seat.vip { background-color: #fc8181; } .seat.vip:hover { background-color: #f56565; } .seat.reserved { background-color: #9f7aea; } .seat.selected { background-color: #48bb78; transform: scale(1.2); } .amenity { position: absolute; width: 40px; height: 40px; border-radius: 50%; background-color: white; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); display: flex; align-items: center; justify-content: center; font-size: 1.2rem; color: #4a5568; cursor: pointer; transition: all 0.3s ease; } .amenity:hover { transform: scale(1.2); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .tooltip { position: absolute; background-color: rgba(45, 55, 72, 0.9); color: white; padding: 8px 12px; border-radius: 6px; font-size: 0.8rem; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; z-index: 200; max-width: 200px; } .info-panel { position: absolute; bottom: 0; left: 0; width: 100%; background-color: white; padding: 15px 20px; border-top: 1px solid #e2e8f0; transform: translateY(100%); transition: transform 0.3s ease; display: flex; justify-content: space-between; align-items: center; z-index: 150; } .info-panel.active { transform: translateY(0); } .info-panel .close-btn { background-color: transparent; border: none; cursor: pointer; font-size: 1.2rem; color: #a0aec0; transition: color 0.2s ease; } .info-panel .close-btn:hover { color: #2d3748; } .legend { display: flex; align-items: center; gap: 15px; margin-top: 10px; padding: 10px 0; flex-wrap: wrap; justify-content: center; } .legend-item { display: flex; align-items: center; gap: 5px; font-size: 0.8rem; color: #4a5568; } .legend-color { width: 12px; height: 12px; border-radius: 2px; } .pulse { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(66, 153, 225, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(66, 153, 225, 0); } 100% { box-shadow: 0 0 0 0 rgba(66, 153, 225, 0); } } @media screen and (max-width: 700px) { .header h1 { font-size: 1.5rem; } .controls button { padding: 6px 10px; font-size: 0.8rem; } .venue { width: 400px; height: 250px; } .stage { width: 180px; height: 50px; } .seating-area { top: 50px; height: calc(100% - 50px); } .seat { width: 18px; height: 18px; } } @media screen and (max-width: 550px) { .venue { width: 300px; height: 200px; } .stage { width: 140px; height: 40px; font-size: 0.8rem; } .seating-area { top: 40px; height: calc(100% - 40px); padding: 15px; } .seat { width: 15px; height: 15px; } .info-panel { padding: 10px 15px; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>SonicVenue Map</h1> <div class="controls"> <button id="amenities-btn">View Amenities</button> <button id="seats-btn">Seating Plan</button> </div> </div> <div class="map-container"> <div class="map" id="map"> <div class="venue"> <div class="stage">Main Stage</div> <div class="seating-area" id="seating-area"> <!-- Seats will be generated by JS --> </div> </div> <!-- Amenities --> <div class="amenity" id="bar" style="top: 25%; left: 25%;">🍹</div> <div class="amenity" id="food" style="top: 60%; left: 20%;">🍔</div> <div class="amenity" id="restroom" style="top: 35%; left: 75%;">🚻</div> <div class="amenity" id="merch" style="top: 65%; left: 80%;">👕</div> <div class="amenity" id="first-aid" style="top: 75%; left: 50%;">⛑️</div> </div> <div class="info-panel" id="info-panel"> <div class="info-content" id="info-content"> <!-- Content will be added by JS --> </div> <button class="close-btn" id="close-panel">×</button> </div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #cbd5e0;"></div> <span>Standard</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #f6ad55;"></div> <span>Premium</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #fc8181;"></div> <span>VIP</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #9f7aea;"></div> <span>Reserved</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #48bb78;"></div> <span>Selected</span> </div> </div> <div class="zoom-controls"> <button class="zoom-btn" id="zoom-in">+</button> <button class="zoom-btn" id="zoom-out">−</button> </div> <div class="tooltip" id="tooltip"></div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Variables let scale = 1; let isDragging = false; let startPosition = { x: 0, y: 0 }; let currentTranslate = { x: 0, y: 0 }; let selectedSeats = []; let currentView = 'seats'; // seats or amenities // DOM Elements const map = document.getElementById('map'); const seatingArea = document.getElementById('seating-area'); const infoPanel = document.getElementById('info-panel'); const infoPanelContent = document.getElementById('info-content'); const closePanel = document.getElementById('close-panel'); const tooltip = document.getElementById('tooltip'); const amenitiesBtn = document.getElementById('amenities-btn'); const seatsBtn = document.getElementById('seats-btn'); const zoomIn = document.getElementById('zoom-in'); const zoomOut = document.getElementById('zoom-out'); const amenities = document.querySelectorAll('.amenity'); // Create seating arrangement function createSeatingLayout() { seatingArea.innerHTML = ''; // Define sections (rows and number of seats) const sections = [ { row: 'A', seats: 12, type: 'vip' }, { row: 'B', seats: 14, type: 'premium' }, { row: 'C', seats: 16, type: 'premium' }, { row: 'D', seats: 18, type: 'standard' }, { row: 'E', seats: 18, type: 'standard' }, { row: 'F', seats: 16, type: 'standard' } ]; // Generate random reserved seats const reservedSeats = []; const maxReserved = 20; for (let i = 0; i < maxReserved; i++) { const randomSection = Math.floor(Math.random() * sections.length); const randomSeat = Math.floor(Math.random() * sections[randomSection].seats) + 1; const seatId = `${sections[randomSection].row}-${randomSeat}`; if (!reservedSeats.includes(seatId)) { reservedSeats.push(seatId); } } // Create each section sections.forEach(section => { const sectionDiv = document.createElement('div'); sectionDiv.className = 'section'; for (let i = 1; i <= section.seats; i++) { const seat = document.createElement('div'); const seatId = `${section.row}-${i}`; seat.className = `seat ${section.type}`; seat.dataset.row = section.row; seat.dataset.number = i; seat.dataset.id = seatId; // Check if seat is reserved if (reservedSeats.includes(seatId)) { seat.classList.add('reserved'); } // Add seat interaction seat.addEventListener('click', handleSeatClick); seat.addEventListener('mouseenter', handleSeatHover); seat.addEventListener('mouseleave', () => { tooltip.style.opacity = 0; }); sectionDiv.appendChild(seat); } seatingArea.appendChild(sectionDiv); }); } // Handle seat click function handleSeatClick(e) { const seat = e.target; const seatId = seat.dataset.id; // Check if seat is reserved if (seat.classList.contains('reserved')) { showTooltip(e, 'This seat is already reserved'); return; } // Toggle selected state if (seat.classList.contains('selected')) { seat.classList.remove('selected'); selectedSeats = selectedSeats.filter(id => id !== seatId); } else { seat.classList.add('selected'); selectedSeats.push(seatId); } // Show info panel if seats are selected if (selectedSeats.length > 0) { showInfoPanel(`${selectedSeats.length} seat(s) selected. Proceed to reserve these seats for the event.`); } else { hideInfoPanel(); } } // Handle seat hover function handleSeatHover(e) { const seat = e.target; const row = seat.dataset.row; const number = seat.dataset.number; const type = seat.classList.contains('vip') ? 'VIP' : seat.classList.contains('premium') ? 'Premium' : 'Standard'; let status = 'Available'; if (seat.classList.contains('reserved')) { status = 'Reserved'; } else if (seat.classList.contains('selected')) { status = 'Selected'; } showTooltip(e, `Seat ${row}-${number}<br>Type: ${type}<br>Status: ${status}`); } // Handle amenity click function handleAmenityClick(e) { const amenity = e.target; const amenityId = amenity.id; let content = ''; switch (amenityId) { case 'bar': content = `<h3>Refreshment Bar</h3> <p>Open from 1 hour before event until 30 minutes after.</p> <p>Serving craft cocktails, local beers, and non-alcoholic options.</p> <p>Mobile ordering available via the event app!</p>`; break; case 'food': content = `<h3>Food Court</h3> <p>Multiple vendors offering diverse cuisine options.</p> <p>Special dietary menus available (vegan, gluten-free).</p> <p>Cashless payments only.</p>`; break; case 'restroom': content = `<h3>Restrooms</h3> <p>Located at the east wing, wheelchair accessible.</p> <p>Baby changing stations available.</p> <p>Attendants on duty throughout the event.</p>`; break; case 'merch': content = `<h3>Merchandise Shop</h3> <p>Official event merchandise and limited edition items.</p> <p>Artist-signed memorabilia available in limited quantities.</p> <p>Pre-order collection point for online purchases.</p>`; break; case 'first-aid': content = `<h3>First Aid Station</h3> <p>Medical staff on duty for the duration of the event.</p> <p>Located near the south exit for easy access.</p> <p>Lost & found services also available here.</p>`; break; } showInfoPanel(content); // Add pulse effect to highlight selected amenity amenities.forEach(a => a.classList.remove('pulse')); amenity.classList.add('pulse'); } // Handle amenity hover function handleAmenityHover(e) { const amenity = e.target; const amenityId = amenity.id; let tooltipText = ''; switch (amenityId) { case 'bar': tooltipText = 'Refreshment Bar'; break; case 'food': tooltipText = 'Food Court'; break; case 'restroom': tooltipText = 'Restrooms'; break; case 'merch': tooltipText = 'Merchandise Shop'; break; case 'first-aid': tooltipText = 'First Aid Station'; break; } showTooltip(e, tooltipText); } // Show tooltip function showTooltip(event, text) { tooltip.innerHTML = text; tooltip.style.left = `${event.pageX - 100}px`; tooltip.style.top = `${event.pageY - 70}px`; tooltip.style.opacity = 1; } // Show info panel function showInfoPanel(content) { infoPanelContent.innerHTML = content; infoPanel.classList.add('active'); } // Hide info panel function hideInfoPanel() { infoPanel.classList.remove('active'); } // Toggle between seats and amenities view function toggleView(view) { currentView = view; if (view === 'amenities') { amenities.forEach(amenity => { amenity.style.opacity = 1; amenity.style.visibility = 'visible'; }); amenitiesBtn.style.backgroundColor = '#4299e1'; seatsBtn.style.backgroundColor = '#4a5568'; } else { amenities.forEach(amenity => { amenity.style.opacity = 0.2; amenity.style.visibility = 'visible'; }); seatsBtn.style.backgroundColor = '#4299e1'; amenitiesBtn.style.backgroundColor = '#4a5568'; } } // Update map transform function updateMapTransform() { map.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${scale})`; } // Initialize map dragging function initDragging() { map.addEventListener('mousedown', onDragStart); map.addEventListener('touchstart', onDragStart); document.addEventListener('mousemove', onDragMove); document.addEventListener('touchmove', onDragMove); document.addEventListener('mouseup', onDragEnd); document.addEventListener('touchend', onDragEnd); } function onDragStart(e) { isDragging = true; if (e.type === 'touchstart') { startPosition.x = e.touches[0].clientX - currentTranslate.x; startPosition.y = e.touches[0].clientY - currentTranslate.y; } else { startPosition.x = e.clientX - currentTranslate.x; startPosition.y = e.clientY - currentTranslate.y; map.style.cursor = 'grabbing'; } } function onDragMove(e) { if (!isDragging) return; e.preventDefault(); let clientX, clientY; if (e.type === 'touchmove') { clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; } else { clientX = e.clientX; clientY = e.clientY; } currentTranslate.x = clientX - startPosition.x; currentTranslate.y = clientY - startPosition.y; // Limit dragging const mapWidth = map.offsetWidth * scale; const mapHeight = map.offsetHeight * scale; const containerWidth = map.parentElement.offsetWidth; const containerHeight = map.parentElement.offsetHeight; const minX = containerWidth - mapWidth; const minY = containerHeight - mapHeight; currentTranslate.x = Math.min(0, Math.max(minX, currentTranslate.x)); currentTranslate.y = Math.min(0, Math.max(minY, currentTranslate.y)); updateMapTransform(); } function onDragEnd() { isDragging = false; map.style.cursor = 'grab'; } // Initialize the map function initMap() { createSeatingLayout(); toggleView('seats'); initDragging(); // Add event listeners closePanel.addEventListener('click', hideInfoPanel); amenitiesBtn.addEventListener('click', () => toggleView('amenities')); seatsBtn.addEventListener('click', () => toggleView('seats')); zoomIn.addEventListener('click', () => { if (scale < 2) { scale += 0.1; updateMapTransform(); } }); zoomOut.addEventListener('click', () => { if (scale > 0.5) { scale -= 0.1; updateMapTransform(); } }); // Add amenity interactions amenities.forEach(amenity => { amenity.addEventListener('click', handleAmenityClick); amenity.addEventListener('mouseenter', handleAmenityHover); amenity.addEventListener('mouseleave', () => { tooltip.style.opacity = 0; }); }); // Simulate interaction setTimeout(() => { const amenity = document.getElementById('merch'); amenity.classList.add('pulse'); setTimeout(() => { amenity.classList.remove('pulse'); }, 5000); }, 1000); } // Initialize the app initMap(); }); </script> </body> </html>