{"id":50777,"date":"2024-11-27T16:40:03","date_gmt":"2024-11-27T20:40:03","guid":{"rendered":"https:\/\/monicavarela.com\/?page_id=50777"},"modified":"2026-09-10T18:08:24","modified_gmt":"2026-09-10T22:08:24","slug":"store-locator","status":"publish","type":"page","link":"https:\/\/mvbabes.com\/es\/store-locator\/","title":{"rendered":"Localizador de tiendas"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"50777\" class=\"elementor elementor-50777\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-950ae2e e-con-full wd-section-stretch-content e-flex e-con e-parent\" data-id=\"950ae2e\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3be0dca elementor-widget elementor-widget-html\" data-id=\"3be0dca\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n<title>Monica Varela \u2014 Store Locator<\/title>\n<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/maplibre-gl@4.7.1\/dist\/maplibre-gl.css\" \/>\n<\/head>\n<body class=\"store-locator\">\n\n<!--\n  ============================================================\n  MONICA VARELA STORE LOCATOR PROTOTYPE\n  ============================================================\n  - Custom map palette (your colors):\n      Land \/ background : #ddd7d0\n      Water \/ fill       : #fcf9f5\n      Roads \/ lines       : #af9f8d\n  - Pins: #131D44 (navy)\n  - Free vector tiles from OpenFreeMap (no API key, no billing)\n  - Uses MapLibre GL's flyTo() \u2014 the same eased pan+zoom \"swoop\"\n    camera motion Mapbox\/Tiffany use, actually the same engine\n    family (MapLibre is the open-source fork of Mapbox GL JS)\n  - Real data for your 2 Monica Varela locations (Santo Domingo\n    & Punta Cana), pulled from your Google Business Profiles.\n    Double check phone\/hours before publishing.\n  ============================================================\n-->\n\n<div id=\"locator\">\n  <div id=\"panel\">\n    <h1>Find a Store<\/h1>\n    <div id=\"store-list\"><\/div>\n  <\/div>\n  <div id=\"map\"><\/div>\n<\/div>\n\n<style>\n  :root {\n    --pin-navy: #131D44;\n    --land: #ddd7d0;\n    --water: #fcf9f5;\n    --lines: #af9f8d;\n    --ink: #131D44;\n  }\n  * { box-sizing: border-box; }\n  body.store-locator {\n    margin: 0;\n    font-family:\"Salinas Regular\", Sans-serif;\n    color: var(--ink);\n  }\n  \/* ===== Desktop: map fills the screen, panel floats as a card on top ===== *\/\n  #locator {\n    position: relative;\n    height: 110vh;\n    width: 100%;\n  }\n  #panel {\n    position: absolute;\n    top: 28px;\n    left: 28px;\n    bottom: 28px;\n    width: 350px;\n    background: #fff;\n    border-radius: 14px;\n    box-shadow: 0 12px 40px rgba(19,29,68,0.18);\n    padding: 32px 28px;\n    overflow-y: auto;\n    z-index: 2;\n  }\n  #panel h1 {\n    font-weight: 400;\n    font-size: 28px;\n    margin: 0 0 12px;\n  }\n  .store-card {\n    font-family: \"Avenir Next Regular\", Arial, Helvetica, sans-serif;\n    letter-spacing: 0.5px;\n    border-top: 1px solid #eee;\n    padding: 16px 0;\n  }\n  .store-card:first-child { border-top: none; }\n  .store-card h3 {\n    font-family:\"Salinas Regular\", Sans-serif;\n    font-weight: 400;\n    font-size: 22px;\n    margin: 0 0 4px;\n  }\n  .store-card h3.store-link {\n    cursor: pointer;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n  .store-card h3.store-link:hover {\n    color: var(--pin-navy);\n    text-decoration: underline;\n  }\n  .store-card h3.store-link .chevron {\n    font-size: 22px;\n    font-family:\"Salinas Regular\", Sans-serif;\n    color: #aaa;\n  }\n  .store-card .distance {\n    font-size: 14px;\n    color: #000;\n    margin-bottom: 6px;\n  }\n  .store-card .nearest-badge {\n    display: block;\n    width: fit-content;\n    background: var(--pin-navy);\n    color: #fff;\n    font-size: 11px;\n    font-weight: bold;\n    letter-spacing: 0.06em;\n    text-transform: uppercase;\n    padding: 4px 10px;\n    border-radius: 3px;\n    margin: 0 0 10px;\n  }\n  .store-card a {\n    font-size: 14px;\n    color: var(--ink);\n    text-decoration: underline;\n    margin-right: 16px;\n  }\n  #map {\n    position: absolute;\n    inset: 0;\n    width: 100%;\n    height: 100%;\n    background: var(--land);\n    z-index: 1;\n  }\n\n  \/* ===== Tablet & mobile: back to stacked \u2014 map on top (full width),\n     store list below as a regular panel, not a floating card ===== *\/\n   \n     @media (min-width: 1025px) {  \n     #panel {\n         height: 680px;;\n         top:70px;\n     }\n     }\n  @media (max-width: 1024px) {\n    #locator {\n      display: flex;\n      flex-direction: column;\n      height: 100dvh; \/* dvh accounts for mobile browser chrome better than vh *\/\n    }\n    #map {\n      position: static;\n      order: -1;\n      width: 100%;\n      height: auto;\n      flex: 0 0 65vh;\n      flex: 0 0 65dvh;\n    }\n    #panel {\n      position: static;\n      inset: auto;\n      width: 100%;\n      flex: 1;\n      min-height: 570px;\n      border-radius: 0;\n      box-shadow: none;\n      border-top: 1px solid #eee;\n      padding: 20px 20px 32px;\n    }\n    #panel h1 {\n      font-size: 24px;\n    }\n  }\n\n  @media (max-width: 480px) {\n    #map {\n      flex: 0 0 60vh;\n      flex: 0 0 60dvh;\n    }\n    #panel {\n      padding: 16px 16px 28px;\n    }\n  }\n  \/* MV store location pin \u2014 custom brand icon *\/\n  .mv-store-pin {\n    width: 42px;\n    height: auto;\n    display: block;\n    cursor: pointer;\n    filter: drop-shadow(0 3px 6px rgba(0,0,0,0.35));\n  }\n  \/* User's current location dot (unchanged) *\/\n  .mv-pin.user-pin {\n    width: 16px;\n    height: 16px;\n    border-radius: 50%;\n    background: var(--pin-navy);\n    box-shadow: 0 0 0 4px rgba(19,29,68,0.25), 0 0 0 8px rgba(19,29,68,0.12);\n  }\n  .maplibregl-ctrl-group {\n    border-radius: 20px !important;\n    overflow: hidden;\n    box-shadow: 0 2px 6px rgba(0,0,0,0.15) !important;\n  }\n<\/style>\n\n<script src=\"https:\/\/unpkg.com\/maplibre-gl@4.7.1\/dist\/maplibre-gl.js\"><\/script>\n<script>\n\/* ============ 1. YOUR STORE DATA (edit if anything changes) ============ *\/\nconst stores = [\n  {\n    name: \"MVSphere\",\n    address: \"<strong>Monica Varela \u2014 Santo Domingo<\/strong><br \/>Edificio Progressus, Av. Abraham Lincoln, Santo Domingo<br \/>\",\n    phone: \"(809) 350-5622\",\n    hours: \"Mon - Fri 9AM \u2013 6PM,<br \/>Sat 10AM - 2PM (Sun closed)\",\n    lat: 18.4788837,\n    lng: -69.9373931\n  },\n  {\n    name: \"MVPalm\",\n    address: \"<strong>Monica Varela  \u2014 Punta Cana<\/strong><br \/>Carr. Aeropuerto, Punta Cana 23000\",\n    phone: \"(849) 472-5541\",\n    hours: \"Mon - Sat 10AM \u2013 7PM<br \/>(Sun 10AM \u2013 4PM)\",\n    lat: 18.5593867,\n    lng: -68.3703358\n  }\n];\n\/* ========================================================================= *\/\n\n\/* ============ 2. CUSTOM MAP STYLE (your palette, extended with a few in-family tones) ============ *\/\n\/*\n  Base palette (yours):     fcf9f5 (water)  ddd7d0 (land)  af9f8d (lines)\n  Added, same warm-neutral family, for legibility of streets & buildings:\n    ece7e0  urban tint (residential\/commercial area fill \u2014 lighter than land)\n    f5f1ea  building fill (light, so footprints lift off the land tone)\n    c6bbae  building outline + minor-road casing (halfway between land and lines)\n*\/\nconst customStyle = {\n  version: 8,\n  sources: {\n    ofm: {\n      type: \"vector\",\n      url: \"https:\/\/tiles.openfreemap.org\/planet\"\n    }\n  },\n  glyphs: \"https:\/\/tiles.openfreemap.org\/fonts\/{fontstack}\/{range}.pbf\",\n  layers: [\n    { id: \"background\", type: \"background\", paint: { \"background-color\": \"#ddd7d0\" } },\n    { id: \"water\", type: \"fill\", source: \"ofm\", \"source-layer\": \"water\",\n      paint: { \"fill-color\": \"#fcf9f5\" } },\n    { id: \"landuse-park\", type: \"fill\", source: \"ofm\", \"source-layer\": \"landuse\",\n      filter: [\"in\", \"class\", \"park\", \"wood\", \"forest\", \"grass\"],\n      paint: { \"fill-color\": \"#fcf9f5\", \"fill-opacity\": 0.5 } },\n    { id: \"landuse-urban\", type: \"fill\", source: \"ofm\", \"source-layer\": \"landuse\",\n      filter: [\"in\", \"class\", \"residential\", \"commercial\", \"industrial\"],\n      minzoom: 9,\n      paint: { \"fill-color\": \"#ece7e0\", \"fill-opacity\": 0.7 } },\n\n    \/* buildings: fill + outline so footprints read clearly once zoomed in *\/\n    { id: \"buildings\", type: \"fill\", source: \"ofm\", \"source-layer\": \"building\",\n      minzoom: 13,\n      paint: { \"fill-color\": \"#f5f1ea\", \"fill-opacity\": 0.95 } },\n    { id: \"buildings-outline\", type: \"line\", source: \"ofm\", \"source-layer\": \"building\",\n      minzoom: 13.5,\n      paint: { \"line-color\": \"#c6bbae\", \"line-width\": 0.6, \"line-opacity\": 0.8 } },\n\n    \/* minor streets: casing underneath + white centerline on top, so they\n       show up against the land tone instead of disappearing into it *\/\n    { id: \"roads-minor-casing\", type: \"line\", source: \"ofm\", \"source-layer\": \"transportation\",\n      filter: [\"!in\", \"class\", \"motorway\", \"trunk\", \"primary\", \"secondary\"],\n      minzoom: 12,\n      paint: { \"line-color\": \"#ece7e1\", \"line-width\": [\"interpolate\", [\"linear\"], [\"zoom\"], 12, 3, 16, 7, 18, 10.5], \"line-opacity\": 0.9 } },\n    { id: \"roads-minor\", type: \"line\", source: \"ofm\", \"source-layer\": \"transportation\",\n      filter: [\"!in\", \"class\", \"motorway\", \"trunk\", \"primary\", \"secondary\"],\n      minzoom: 12,\n      paint: { \"line-color\": \"#ffffff\", \"line-width\": [\"interpolate\", [\"linear\"], [\"zoom\"], 12, 1.6, 16, 5, 18, 7.8] } },\n\n    \/* secondary streets: visible a bit earlier than minor, slightly heavier *\/\n    { id: \"roads-secondary-casing\", type: \"line\", source: \"ofm\", \"source-layer\": \"transportation\",\n      filter: [\"==\", \"class\", \"secondary\"],\n      paint: { \"line-color\": \"#ece7e1\", \"line-width\": [\"interpolate\", [\"linear\"], [\"zoom\"], 12, 3.6, 16, 8, 18, 12], \"line-opacity\": 0.9 } },\n    { id: \"roads-secondary\", type: \"line\", source: \"ofm\", \"source-layer\": \"transportation\",\n      filter: [\"==\", \"class\", \"secondary\"],\n      paint: { \"line-color\": \"#ffffff\", \"line-width\": [\"interpolate\", [\"linear\"], [\"zoom\"], 12, 1.9, 16, 5.4, 18, 8.5] } },\n\n    \/* major roads: darker line, with a light casing so they read at low zoom too *\/\n    { id: \"roads-major-casing\", type: \"line\", source: \"ofm\", \"source-layer\": \"transportation\",\n      filter: [\"in\", \"class\", \"motorway\", \"trunk\", \"primary\"],\n      paint: { \"line-color\": \"#fcf9f5\", \"line-width\": [\"interpolate\", [\"linear\"], [\"zoom\"], 10, 3, 16, 9, 18, 13], \"line-opacity\": 0.9 } },\n    { id: \"roads-major\", type: \"line\", source: \"ofm\", \"source-layer\": \"transportation\",\n      filter: [\"in\", \"class\", \"motorway\", \"trunk\", \"primary\"],\n      paint: { \"line-color\": \"#af9f8d\", \"line-width\": [\"interpolate\", [\"linear\"], [\"zoom\"], 10, 1.5, 16, 5, 18, 7.8], \"line-opacity\": 0.9 } },\n\n    { id: \"boundaries\", type: \"line\", source: \"ofm\", \"source-layer\": \"boundary\",\n      filter: [\"<=\", \"admin_level\", 2],\n      paint: { \"line-color\": \"#af9f8d\", \"line-width\": 1, \"line-dasharray\": [2, 2] } },\n    { id: \"place-labels\", type: \"symbol\", source: \"ofm\", \"source-layer\": \"place\",\n      filter: [\"in\", \"class\", \"city\", \"town\"],\n      layout: { \"text-field\": [\"get\", \"name\"], \"text-size\": 12, \"text-font\": [\"Noto Sans Regular\"] },\n      paint: { \"text-color\": \"#af9f8d\" } },\n      {\n  id: 'road-labels',\n  type: 'symbol',\n  source: 'ofm',\n  'source-layer': 'transportation_name',\n  filter: ['!in', 'class', 'path', 'track'],\n  layout: {\n    'symbol-placement': 'line',\n    'text-field': ['get', 'name'],\n    'text-font': ['Noto Sans Regular'],\n    'text-size': ['interpolate', ['linear'], ['zoom'], 12, 13, 16, 15.5, 18, 17],\n    'text-letter-spacing': 0.06,\n    'symbol-spacing': 300\n  },\n  paint: {\n    'text-color': '#7a6e62',\n    'text-halo-color': '#fcf9f5',\n    'text-halo-width': 2.4,\n    'text-halo-blur': 0.3\n  }\n},\n{\n  id: 'poi-labels',\n  type: 'symbol',\n  source: 'ofm',\n  'source-layer': 'poi',\n  minzoom: 15,\n  filter: ['<=', ['get', 'rank'], 8],\n  layout: {\n    'text-field': ['get', 'name'],\n    'text-font': ['Noto Sans Regular'],\n    'text-size': 10,\n    'text-anchor': 'top',\n    'text-offset': [0, 0.6],\n    'text-max-width': 8\n  },\n  paint: {\n    'text-color': '#6b5f54',\n    'text-halo-color': '#fcf9f5',\n    'text-halo-width': 1\n  }\n}\n  ]\n};\n\/* ========================================================================= *\/\n\n\/\/ Initial view: zoomed out enough to show the wider Caribbean and Florida,\n\/\/ not just the 2 store markers \u2014 the \"swoop in\" on geolocate does the rest.\nconst INITIAL_CENTER = [-74, 22.5];\nconst INITIAL_ZOOM = 4.3;\n\nconst map = new maplibregl.Map({\n  container: 'map',\n  style: customStyle,\n  center: INITIAL_CENTER,\n  zoom: INITIAL_ZOOM,\n  \/\/ Require Ctrl\/Cmd + scroll to zoom the map, and two fingers to pan on\n  \/\/ touch \u2014 so a plain mouse-wheel or one-finger swipe just scrolls the\n  \/\/ page instead of getting caught by the map.\n  cooperativeGestures: true\n});\nmap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'bottom-right');\n\n\/\/ MV store pin \u2014 custom brand icon (image, replaces the old CSS diamond)\nconst STORE_PIN_IMAGE = 'https:\/\/mvbabes.com\/wp-content\/uploads\/WEB-ELEMENT-VECTORS-58.png';\nfunction makeStorePinEl() {\n  const el = document.createElement('img');\n  el.src = STORE_PIN_IMAGE;\n  el.alt = 'Monica Varela store';\n  el.className = 'mv-store-pin';\n  return el;\n}\n\n\/\/ User's current-location dot \u2014 unchanged CSS pin\nfunction makeUserPinEl() {\n  const el = document.createElement('div');\n  el.className = 'mv-pin user-pin';\n  return el;\n}\n\nconst storeMarkers = {};\nstores.forEach(s => {\n  const marker = new maplibregl.Marker({ element: makeStorePinEl(), anchor: 'bottom' })\n    .setLngLat([s.lng, s.lat])\n    .setPopup(new maplibregl.Popup({ offset: 24 }).setHTML(`<strong>${s.name}<\/strong><br>${s.address}`))\n    .addTo(map);\n  storeMarkers[s.name] = marker;\n});\n\n\/\/ Clicking a store in the list flies the map to its pin and opens its popup\nfunction focusStore(name) {\n  const s = stores.find(st => st.name === name);\n  const marker = storeMarkers[name];\n  if (!s || !marker) return;\n  map.flyTo({ center: [s.lng, s.lat], zoom: 16, speed: 1, curve: 1.4, essential: true });\n  if (!marker.getPopup().isOpen()) marker.togglePopup();\n}\n\nfunction haversineMiles(lat1, lon1, lat2, lon2) {\n  const R = 3958.8;\n  const dLat = (lat2 - lat1) * Math.PI \/ 180;\n  const dLon = (lon2 - lon1) * Math.PI \/ 180;\n  const a = Math.sin(dLat \/ 2) ** 2 +\n            Math.cos(lat1 * Math.PI \/ 180) * Math.cos(lat2 * Math.PI \/ 180) *\n            Math.sin(dLon \/ 2) ** 2;\n  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n}\n\nfunction renderList(userLoc) {\n  const withDist = stores.map(s => ({\n    ...s,\n    distance: userLoc ? haversineMiles(userLoc.lat, userLoc.lng, s.lat, s.lng) : null\n  }));\n  if (userLoc) withDist.sort((a, b) => a.distance - b.distance);\n\n  document.getElementById('store-list').innerHTML = withDist.map((s, i) => `\n    <div class=\"store-card\">\n      ${i === 0 && userLoc ? '<div class=\"nearest-badge\">Nearest Store<\/div>' : ''}\n      <h3 class=\"store-link\" data-store=\"${s.name}\">${s.name} <span class=\"chevron\">&rsaquo;<\/span><\/h3>\n      ${s.distance !== null ? `<div class=\"distance\">${s.distance.toFixed(1)} miles away<\/div>` : ''}\n      <div class=\"distance\">${s.address}<\/div>\n      <div class=\"distance\">Open: ${s.hours}<br \/>${s.phone}<\/div>\n      <a href=\"https:\/\/www.google.com\/maps\/dir\/?api=1&destination=${s.lat},${s.lng}\" target=\"_blank\">Get Directions<\/a>\n    <\/div>\n  `).join('');\n\n  \/\/ wire up \"click a store, fly to its pin\" behavior\n  document.querySelectorAll('.store-link').forEach(el => {\n    el.addEventListener('click', () => focusStore(el.dataset.store));\n  });\n}\n\nrenderList(null);\n\n\/* ============ 3. GEOLOCATE + FLY TO NEAREST STORE ============ *\/\nmap.on('load', () => {\n  if (navigator.geolocation) {\n    navigator.geolocation.getCurrentPosition(\n      (pos) => {\n        const userLoc = { lat: pos.coords.latitude, lng: pos.coords.longitude };\n\n        new maplibregl.Marker({ element: makeUserPinEl(), anchor: 'center' })\n          .setLngLat([userLoc.lng, userLoc.lat])\n          .setPopup(new maplibregl.Popup({ offset: 12 }).setText('You are here'))\n          .addTo(map);\n\n        let nearest = stores[0];\n        let nearestDist = Infinity;\n        stores.forEach(s => {\n          const d = haversineMiles(userLoc.lat, userLoc.lng, s.lat, s.lng);\n          if (d < nearestDist) { nearestDist = d; nearest = s; }\n        });\n\n        renderList(userLoc);\n\n        \/\/ THE \"SWOOP\" ANIMATION \u2014 flyTo eases pan + zoom together.\n        \/\/ duration is fixed (ms) rather than speed-derived, so it's a\n        \/\/ predictable, slow, deliberate swoop every time. Custom\n        \/\/ ease-in-out on top of that smooths the start\/end further so\n        \/\/ it never feels like it \"snaps\" at either end.\n        const easeInOutCubic = t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) \/ 2;\n        setTimeout(() => {\n          map.flyTo({\n            center: [nearest.lng, nearest.lat],\n            zoom: 16,\n            duration: 10000,  \/\/ very slow, cinematic swoop into the nearest store\n            curve: 1.2,       \/\/ gentler arc so the middle doesn't feel rushed\n            easing: easeInOutCubic,\n            essential: true\n          });\n        }, 500);\n      },\n      () => {\n        const bounds = new maplibregl.LngLatBounds();\n        stores.forEach(s => bounds.extend([s.lng, s.lat]));\n        map.fitBounds(bounds, { padding: 80, duration: 2000 });\n      },\n      { timeout: 8000, maximumAge: 60000 } \/\/ fail fast instead of hanging silently\n    );\n  }\n});\n<\/script>\n\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Monica Varela \u2014 Store Locator Find a Store<\/p>","protected":false},"author":1,"featured_media":10,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"class_list":["post-50777","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/pages\/50777","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/comments?post=50777"}],"version-history":[{"count":5,"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/pages\/50777\/revisions"}],"predecessor-version":[{"id":75184,"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/pages\/50777\/revisions\/75184"}],"wp:attachment":[{"href":"https:\/\/mvbabes.com\/es\/wp-json\/wp\/v2\/media?parent=50777"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}