Liveweave: Generative AI Web Editor & HTML/CSS/JS Playground
Initializing
Liveweave
Web
expand_more
home
Home
Palette
Color Explorer
arrow_outward
Polyline
Graphics Editor
arrow_outward
frame_source
Python Playground
arrow_outward
build
Tools
expand_more
restart_alt
Load "Hello Weaver!"
post_add
Generate Lorem ipsum...
code
Format HTML
code_blocks
Format CSS
data_object
Format JavaScript
library_add
Library
expand_more
A
Algolia JS
Animate CSS
Apex Charts JS
B
Bulma CSS
Bootstrap
C
Chart JS
Chartist
Create JS
D
D3
Dojo
F
Foundation
Fullpage JS
G
Granim JS
Google Charts
H
Halfmoon
J
jQuery
M
Materialize
Moment JS
Masonry JS
Milligram CSS
P
Pure CSS
Primer CSS
Popper JS
Pattern CSS
Picnic CSS
R
React JS
Raphael JS
Raisin CSS
S
Semantic UI
Skeleton CSS
Spectre CSS
Tachyons CSS
T
Tailwind
Three JS
U
UI Kit
Vis JS
W
Water CSS
download
Download
expand_more
developer_mode
Download as HTML
folder_zip
Download as .ZIP
cloud_upload
Fork
account_circle
Login
settings
Settings
expand_more
Editor font
14
px
A
A
Night mode
Line number
Mini map
Word wrap
sync_alt
Reset Settings
smart_display
Run
left_panel_close
AI Assistant
dashboard
All
HTML
CSS
JS
visibility
Preview
bolt
Live Preview
terminal
JS Console
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <div class="map-app"> <div id="map"></div> <div class="map-fallback" id="mapFallback"> <div class="fallback-card"> <div class="fallback-icon"> ⌖ </div> <span class="fallback-eyebrow"> MAP PREVIEW </span> <h2> Interactive map unavailable </h2> <p> This map provider may block map tiles when viewed inside an embedded iframe. </p> <p class="fallback-sub"> Try opening this page directly in a new browser tab. </p> </div> </div> <!-- SEARCH --> <div class="topbar"> <div class="search-panel"> <div class="brand"> <div class="brand-icon"> ◉ </div> <div> <strong>Atlas</strong> <span>Explore the world</span> </div> </div> <div class="search-box"> <span class="search-icon"> ⌕ </span> <input id="searchInput" type="text" placeholder="Search city, address or place..." autocomplete="off" /> <button id="clearButton" aria-label="Clear search" > × </button> </div> <div id="searchResults" class="search-results" ></div> </div> </div> <!-- MAP CONTROLS --> <div class="map-controls"> <button id="locateButton" title="Use my location" > ◎ </button> <button id="zoomInButton" title="Zoom in" > + </button> <button id="zoomOutButton" title="Zoom out" > − </button> </div> <!-- LOCATION INFO --> <div class="map-info"> <span class="map-info-label"> CURRENT VIEW </span> <strong id="locationTitle"> New York City </strong> <span id="locationCoords"> 40.7128, -74.0060 </span> </div> <div class="attribution-note"> Map data © OpenStreetMap contributors </div> </div> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" ></script>
* { box-sizing: border-box; } html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #dce3e8; } /* APP */ .map-app { position: relative; width: 100vw; height: 100vh; overflow: hidden; background: #dce3e8; } /* MAP */ #map { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; background: #dce3e8; } /* HIDE LEAFLET ZOOM */ .leaflet-control-zoom { display: none; } /* ---------------------------- FALLBACK ----------------------------- */ .map-fallback { position: absolute; inset: 0; z-index: 500; display: flex; align-items: center; justify-content: center; padding: 30px; background: radial-gradient( circle at 50% 32%, #eef2f5, #dfe6eb 48%, #d3dce2 ); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .35s ease, visibility .35s ease; } .map-fallback.visible { opacity: 1; visibility: visible; } .fallback-card { width: min(430px, 92vw); padding: 38px 34px; border: 1px solid rgba(255,255,255,.85); border-radius: 24px; background: rgba(255,255,255,.86); backdrop-filter: blur(20px); box-shadow: 0 30px 80px rgba(35,50,65,.15); text-align: center; } .fallback-icon { width: 62px; height: 62px; display: grid; place-items: center; margin: 0 auto 20px; border-radius: 18px; background: #17202a; color: white; font-size: 29px; box-shadow: 0 14px 32px rgba(20,30,40,.22); } .fallback-eyebrow { display: block; margin-bottom: 8px; color: #959ba2; font-size: 8px; font-weight: 700; letter-spacing: 2px; } .fallback-card h2 { margin: 0; color: #1c2127; font-size: 25px; letter-spacing: -.8px; } .fallback-card p { max-width: 330px; margin: 12px auto 0; color: #747b83; font-size: 11px; line-height: 1.65; } .fallback-card .fallback-sub { margin-top: 7px; color: #9ca2a8; font-size: 10px; } /* ---------------------------- SEARCH PANEL ----------------------------- */ .topbar { position: absolute; top: 20px; left: 20px; z-index: 1000; } .search-panel { position: relative; width: min(420px, calc(100vw - 40px)); padding: 10px; border: 1px solid rgba(255,255,255,.75); border-radius: 20px; background: rgba(255,255,255,.92); backdrop-filter: blur(22px) saturate(140%); box-shadow: 0 18px 55px rgba(19,31,45,.18); } /* BRAND */ .brand { display: flex; align-items: center; gap: 10px; padding: 3px 5px 11px; } .brand-icon { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 11px; background: #111827; color: white; font-size: 16px; } .brand strong, .brand span { display: block; } .brand strong { color: #15181d; font-size: 13px; } .brand span { margin-top: 2px; color: #8a9098; font-size: 9px; } /* SEARCH BOX */ .search-box { height: 46px; display: flex; align-items: center; gap: 10px; padding: 0 11px; border: 1px solid #e2e5e9; border-radius: 12px; background: #f9fafb; transition: border-color .15s, box-shadow .15s, background .15s; } .search-box:focus-within { background: white; border-color: #aeb7c2; box-shadow: 0 0 0 4px rgba(43,76,126,.08); } .search-icon { color: #8c9299; font-size: 19px; } .search-box input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: #171a1f; font-size: 12px; } .search-box input::placeholder { color: #9ca1a8; } #clearButton { width: 27px; height: 27px; border: 0; border-radius: 8px; background: transparent; color: #8b9198; font-size: 18px; cursor: pointer; opacity: 0; transition: opacity .15s, background .15s; } #clearButton.visible { opacity: 1; } #clearButton:hover { background: #eceff2; } /* ---------------------------- SEARCH RESULTS ----------------------------- */ .search-results { display: none; max-height: 310px; margin-top: 8px; overflow-y: auto; border: 1px solid #e6e8eb; border-radius: 12px; background: white; } .search-results.visible { display: block; } .result-item { width: 100%; display: flex; align-items: flex-start; gap: 10px; padding: 12px; border: 0; border-bottom: 1px solid #f0f1f3; background: white; text-align: left; cursor: pointer; transition: background .15s; } .result-item:last-child { border-bottom: 0; } .result-item:hover { background: #f6f8fa; } .result-marker { width: 30px; height: 30px; display: grid; place-items: center; flex-shrink: 0; border-radius: 9px; background: #eef2f7; color: #35445a; font-size: 13px; } .result-content { min-width: 0; } .result-content strong { display: block; overflow: hidden; color: #20242a; font-size: 11px; font-weight: 600; white-space: nowrap; text-overflow: ellipsis; } .result-content span { display: block; margin-top: 4px; color: #90969e; font-size: 9px; line-height: 1.4; } .loading-result { padding: 15px; color: #8b9299; font-size: 10px; text-align: center; } /* ---------------------------- MAP CONTROLS ----------------------------- */ .map-controls { position: absolute; right: 20px; top: 20px; z-index: 1000; display: flex; flex-direction: column; gap: 7px; } .map-controls button { width: 42px; height: 42px; display: grid; place-items: center; border: 1px solid rgba(255,255,255,.78); border-radius: 12px; background: rgba(255,255,255,.92); color: #232831; box-shadow: 0 8px 30px rgba(20,30,40,.13); backdrop-filter: blur(16px); font-size: 18px; font-weight: 500; cursor: pointer; transition: transform .15s, background .15s; } .map-controls button:hover { transform: translateY(-1px); background: white; } /* ---------------------------- INFO CARD ----------------------------- */ .map-info { position: absolute; left: 20px; bottom: 23px; z-index: 1000; min-width: 220px; padding: 13px 16px; border: 1px solid rgba(255,255,255,.7); border-radius: 14px; background: rgba(255,255,255,.9); backdrop-filter: blur(18px); box-shadow: 0 15px 40px rgba(20,30,40,.15); } .map-info-label { display: block; margin-bottom: 4px; color: #9b9fa6; font-size: 7px; font-weight: 700; letter-spacing: 1.5px; } .map-info strong { display: block; color: #1e2228; font-size: 13px; } .map-info > span:last-child { display: block; margin-top: 4px; color: #878c93; font-size: 9px; font-variant-numeric: tabular-nums; } /* ATTRIBUTION */ .attribution-note { position: absolute; right: 20px; bottom: 18px; z-index: 1000; padding: 6px 9px; border-radius: 7px; background: rgba(255,255,255,.84); color: #777d84; font-size: 8px; backdrop-filter: blur(10px); } /* ---------------------------- LEAFLET POPUP ----------------------------- */ .leaflet-popup-content-wrapper { border-radius: 12px !important; box-shadow: 0 12px 35px rgba(0,0,0,.18) !important; } .leaflet-popup-content { margin: 12px 14px !important; font-family: inherit; font-size: 11px; } /* ---------------------------- MARKER ----------------------------- */ .custom-marker { width: 30px; height: 30px; display: grid; place-items: center; border: 3px solid white; border-radius: 50% 50% 50% 0; background: #1e293b; box-shadow: 0 6px 15px rgba(15,23,42,.3); transform: rotate(-45deg); } .custom-marker::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: white; transform: rotate(45deg); } /* ---------------------------- MOBILE ----------------------------- */ @media ( max-width: 600px ) { .topbar { top: 12px; left: 12px; } .search-panel { width: calc(100vw - 76px); border-radius: 16px; } .brand { display: none; } .map-controls { right: 12px; top: 12px; } .map-controls button { width: 40px; height: 40px; } .map-info { left: 12px; bottom: 12px; min-width: 185px; } .attribution-note { display: none; } }
/* ------------------------- MAP -------------------------- */ const map = L.map( "map", { zoomControl: false } ) .setView( [40.7128, -74.0060], 11 ); /* ------------------------- FALLBACK -------------------------- */ const mapFallback = document.getElementById( "mapFallback" ); let tileErrors = 0; let tileSuccesses = 0; /* OpenStreetMap canonical endpoint */ const tileLayer = L.tileLayer( "https://tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap contributors" } ); tileLayer.addTo( map ); /* TILE SUCCESS */ tileLayer.on( "tileload", function() { tileSuccesses++; if ( tileSuccesses >= 2 ) { hideMapFallback(); } } ); /* TILE ERROR */ tileLayer.on( "tileerror", function() { tileErrors++; if ( tileErrors >= 4 && tileSuccesses === 0 ) { showMapFallback(); } } ); function showMapFallback() { mapFallback .classList .add( "visible" ); } function hideMapFallback() { mapFallback .classList .remove( "visible" ); } /* EXTRA TIMEOUT CHECK */ setTimeout( function() { if ( tileSuccesses === 0 && tileErrors > 0 ) { showMapFallback(); } }, 4000 ); /* CHANGE MESSAGE IF EMBEDDED */ try { const isEmbedded = window.self !== window.top; if (isEmbedded) { const message = document.querySelector( ".fallback-sub" ); if (message) { message.textContent = "Open this weave in full view to explore the interactive map."; } } } catch (error) { /* Cross-origin iframe access can throw */ } /* ------------------------- CUSTOM MARKER -------------------------- */ const markerIcon = L.divIcon( { className: "", html: '<div class="custom-marker"></div>', iconSize: [30, 30], iconAnchor: [15, 30] } ); let activeMarker = L.marker( [40.7128, -74.0060], { icon: markerIcon } ) .addTo(map); /* ------------------------- ELEMENTS -------------------------- */ const searchInput = document.getElementById( "searchInput" ); const resultsBox = document.getElementById( "searchResults" ); const clearButton = document.getElementById( "clearButton" ); const locationTitle = document.getElementById( "locationTitle" ); const locationCoords = document.getElementById( "locationCoords" ); let searchTimeout; /* ------------------------- SEARCH INPUT -------------------------- */ searchInput.addEventListener( "input", function() { const query = searchInput .value .trim(); clearButton .classList .toggle( "visible", query.length > 0 ); clearTimeout( searchTimeout ); if ( query.length < 3 ) { resultsBox.classList.remove( "visible" ); resultsBox.innerHTML = ""; return; } searchTimeout = setTimeout( function() { searchLocation( query ); }, 500 ); } ); /* ENTER KEY */ searchInput.addEventListener( "keydown", function(event) { if ( event.key === "Enter" ) { event.preventDefault(); const query = searchInput .value .trim(); if ( query.length >= 3 ) { searchLocation( query ); } } } ); /* ------------------------- NOMINATIM SEARCH -------------------------- */ async function searchLocation( query ) { resultsBox .classList .add( "visible" ); resultsBox.innerHTML = ` <div class="loading-result"> Searching... </div> `; try { const url = "https://nominatim.openstreetmap.org/search" + "?format=json" + "&addressdetails=1" + "&limit=6" + "&q=" + encodeURIComponent( query ); const response = await fetch( url, { headers: { Accept: "application/json" } } ); if ( !response.ok ) { throw new Error( "Search request failed" ); } const results = await response.json(); displayResults( results ); } catch (error) { resultsBox.innerHTML = ` <div class="loading-result"> Search is unavailable in this embedded view. </div> `; } } /* ------------------------- RESULTS -------------------------- */ function displayResults( results ) { resultsBox.innerHTML = ""; if ( results.length === 0 ) { resultsBox.innerHTML = ` <div class="loading-result"> No locations found. </div> `; return; } results.forEach( function(result) { const button = document.createElement( "button" ); button.className = "result-item"; const title = getResultTitle( result ); button.innerHTML = ` <div class="result-marker"> ◉ </div> <div class="result-content"> <strong> ${escapeHTML(title)} </strong> <span> ${escapeHTML( result.display_name )} </span> </div> `; button.addEventListener( "click", function() { selectLocation( result ); } ); resultsBox.appendChild( button ); } ); } /* ------------------------- TITLE -------------------------- */ function getResultTitle( result ) { const address = result.address || {}; return ( address.city || address.town || address.village || address.municipality || address.suburb || address.county || result.name || result.display_name .split(",")[0] ); } /* ------------------------- SELECT LOCATION -------------------------- */ function selectLocation( result ) { const lat = Number( result.lat ); const lon = Number( result.lon ); const title = getResultTitle( result ); map.flyTo( [lat, lon], getZoomForResult( result ), { duration: 1.4 } ); if ( activeMarker ) { map.removeLayer( activeMarker ); } activeMarker = L.marker( [lat, lon], { icon: markerIcon } ) .addTo(map); activeMarker .bindPopup( ` <strong> ${escapeHTML(title)} </strong> <br> ${escapeHTML( result.display_name )} ` ) .openPopup(); locationTitle.textContent = title; locationCoords.textContent = lat.toFixed(4) + ", " + lon.toFixed(4); searchInput.value = title; clearButton.classList.add( "visible" ); resultsBox.classList.remove( "visible" ); } /* ------------------------- ZOOM BY TYPE -------------------------- */ function getZoomForResult( result ) { const type = result.type; if ( type === "country" ) { return 5; } if ( type === "state" || type === "province" ) { return 7; } if ( type === "city" || type === "town" || type === "municipality" ) { return 12; } return 15; } /* ------------------------- CLEAR SEARCH -------------------------- */ clearButton.addEventListener( "click", function() { searchInput.value = ""; resultsBox.innerHTML = ""; resultsBox.classList.remove( "visible" ); clearButton.classList.remove( "visible" ); searchInput.focus(); } ); /* ------------------------- ZOOM BUTTONS -------------------------- */ document .getElementById( "zoomInButton" ) .addEventListener( "click", function() { map.zoomIn(); } ); document .getElementById( "zoomOutButton" ) .addEventListener( "click", function() { map.zoomOut(); } ); /* ------------------------- GEOLOCATION -------------------------- */ document .getElementById( "locateButton" ) .addEventListener( "click", function() { if ( !navigator.geolocation ) { locationTitle.textContent = "Geolocation unavailable"; return; } navigator .geolocation .getCurrentPosition( function( position ) { const lat = position .coords .latitude; const lon = position .coords .longitude; map.flyTo( [lat, lon], 14, { duration: 1.3 } ); if ( activeMarker ) { map.removeLayer( activeMarker ); } activeMarker = L.marker( [lat, lon], { icon: markerIcon } ) .addTo(map); locationTitle.textContent = "Your location"; locationCoords.textContent = lat.toFixed(4) + ", " + lon.toFixed(4); }, function() { locationTitle.textContent = "Location permission denied"; } ); } ); /* ------------------------- MAP MOVE -------------------------- */ map.on( "moveend", function() { const center = map.getCenter(); locationCoords.textContent = center.lat.toFixed(4) + ", " + center.lng.toFixed(4); } ); /* ------------------------- CLOSE SEARCH RESULTS -------------------------- */ document.addEventListener( "click", function(event) { if ( !event.target.closest( ".search-panel" ) ) { resultsBox.classList.remove( "visible" ); } } ); /* ------------------------- ESCAPE HTML -------------------------- */ function escapeHTML( value ) { return String( value ) .replace( /&/g, "&" ) .replace( /</g, "<" ) .replace( />/g, ">" ) .replace( /"/g, """ ) .replace( /'/g, "'" ); }
terminal
JavaScript Console
Preview
Clear
close
›
Run