{"id":1327,"title":"The Villa Group Beach Resorts \u0026 Spas nombra a John Paul Kirwan vicepresidente de operaciones","title_en":"The Villa Group Beach Resorts \u0026 Spas Appoints John Paul Kirwan as Vice President of Operations","description":"\u003cp\u003eNo hay dos líderes iguales, por lo tanto, en The Villa Group Resorts siempre buscamos los mejores talentos para ocupar los puestos donde se requiere un gran liderazgo. Por esta razón, nos complace anunciar que hemos nombrado a\u003cb\u003e John Paul Kirwan como vicepresidente de operaciones.\u003c/b\u003e El primer anuncio de esta noticia fue emitido por \u003ca href=\"https://www.prnewswire.com/news-releases/the-villa-group-beach-resorts--spas-appoints-john-paul-kirwan-as-vice-president-of-operations-301732627.html\" target=\"_blank\"\u003ePR Newswire el 30 de enero de 2023.\u003c/a\u003e\u003c/p\u003e\r\n\u003cp\u003ePR Newswire, una empresa de Cision, ha sido pionera en la industria de distribución de noticias comerciales desde hace más de 60 años y actualmente ofrece soluciones integrales para producir, optimizar y dirigir contenido, así como distribuir y medir resultados. \u003cb\u003ePR Newswire cubre las historias de organizaciones de todo el mundo a través de la distribución de contenido multicanal y multicultural más grande del mundo.\u003c/b\u003e\u003c/p\u003e\r\n\u003cp\u003eAlgunas personas nacen líderes, mientras que algunos líderes pueden nacer a través del trabajo duro y la capacitación, la dedicación y la determinación. \u003cb\u003eLos grandes líderes ayudan a las personas que los rodean a crecer mientras brindan orientación, motivación y un claro ejemplo a seguir para los demás.\u003c/b\u003e Otro aspecto importante del liderazgo, especialmente en la industria de la hospitalidad, es tener habilidades estelares con las personas, lo cual es imprescindible para crear experiencias vacacionales de lujo.\u003c/p\u003e\u003cp\u003e\u003cimg src=\"https://bucketrediseno.s3.amazonaws.com/uploads/photo/image/9839/hospitality_executive.jpg\" id=\"9839\" style=\"width: 100%;\"\u003e\u003cbr\u003e\u003c/p\u003e\r\n\u003cp\u003eComo consecuencia, estamos comprometidos a\u003cb\u003e reclutar personas apasionadas, orientadas a los resultados y al servicio, ingeniosas y que tengan la tenacidad para lograr nuestro objetivo de convertirnos en el grupo número uno de hoteles todo incluido en México mientras impulsamos nuestro plan de crecimiento a alturas aún más altas. \u003c/b\u003ePor esta razón, estamos ansiosos de ver el fruto de la visión de Kirwan aplicada en The Villa Group Resorts y apoyarlo para que siga cosechando éxitos en su carrera hotelera.\u003c/p\u003e\r\n\u003cp\u003eSer el Vicepresidente de Operaciones no es tarea fácil. Se debe \u003cb\u003emantener la imagen de toda la organización, mejorar el servicio al cliente, aumentar la productividad, desarrollar e implementar operaciones eficientes y sistemas rentables\u003c/b\u003e para satisfacer las necesidades actuales y futuras de la empresa, al tiempo que se proporciona un entorno de trabajo seguro y agradable, que es esencial para el desarrollo general de nuestra actividad.\u003c/p\u003e\r\n\u003cp\u003eComo vicepresidente de operaciones, el objetivo de John Paul Kirwan es continuar haciendo crecer la marca The Villa Group utilizando su amplia experiencia y los conocimientos acumulados a lo largo de su larga y fructífera carrera en la industria del turismo. \u003cb\u003eCon más de 20 años de experiencia en la gestión de hoteles en múltiples destinos vacacionales,\u003c/b\u003e Kirwan debe comenzar a trabajar para crear un entorno en el que tanto él como su equipo puedan prosperar en armonía.\u003c/p\u003e\u003cp\u003e\u003cimg src=\"https://bucketrediseno.s3.amazonaws.com/uploads/photo/image/9810/the_villa_group_ceo.jpg\" id=\"9810\" style=\"width: 100%;\"\u003e\u003cbr\u003e\u003c/p\u003e\r\n\u003cp\u003eA lo largo de su carrera hotelera, Kirwan ha expandido su experiencia gerencial y sus conocimientos en el desarrollo de estrategias financieras, marketing, gestión de crisis, así como la creación y ejecución de manuales de operaciones para brindar experiencias vacacionales de lujo. También es\u003cb\u003e miembro de la Junta Directiva de la Asociación de Hoteles de Los Cabos\u003c/b\u003e, una organización sin fines de lucro que representa el desarrollo y los intereses de la industria hotelera en Los Cabos, México.\u003c/p\u003e\r\n\u003cp\u003eKirwan estará a cargo de un gran equipo, ya que son \u003ca href=\"https://villagroupresorts.com.mx/resorts\"\u003evarios los hoteles todo incluido en México que forman parte de la renombrada cadena\u003c/a\u003e The Villa Group Beach Resorts \u0026amp; Spas. Estos hoteles en México están ubicados en algunos de los mejores destinos vacacionales y cada uno tiene su propia esencia que no encontrarás en ningún otro lugar. John Paul ha tomado el relevo para seguir elevando las operaciones y los servicios de The Villa Group a los más altos estándares de hospitalidad.\u003c/p\u003e\r\n\u003cp\u003e\u003cbr\u003e\u003cb\u003eNuestros 35 años de trayectoria como The Villa Group Resorts son garantía de experiencias vacacionales de lujo para toda la familia \u003c/b\u003een suites de alta gama, instalaciones bien equipadas y servicios hospitalarios de primer nivel en nuestras diversas propiedades en Puerto Vallarta, Riviera Nayarit y Los Cabos. Ahora, con John Paul Kirwan, seguiremos creciendo por esta senda. ¡Descubre algo nuevo y emocionante en cualquiera de nuestros hoteles en México!\u003c/p\u003e","description_en":"\u003cp\u003eNo two leaders are the same, therefore, we’re quite keen on finding the best talents that are suited to fill positions from entry level all the way to the top. For this reason, it brings us great pleasure to announce that\u003cb\u003e \u003c/b\u003eThe Villa Group Beach Resorts \u0026amp; Spas appoints\u003cb\u003e John Paul Kirwan as Vice President of Operations.\u003c/b\u003e The news announcement was first issued by \u003ca href=\"https://www.prnewswire.com/news-releases/the-villa-group-beach-resorts--spas-appoints-john-paul-kirwan-as-vice-president-of-operations-301732627.html\" target=\"_blank\"\u003ePR Newswire on January 30th, 2023.\u0026nbsp;\u003c/a\u003e\u003c/p\u003e\r\n\u003cp\u003ePR Newswire, a Cision company, pioneered the commercial news distribution industry over 60 years ago and currently provides end-to-end solutions to produce, optimize and target content, as well as distribute and measure results. \u003cb\u003ePR Newswire powers the stories of organizations around the world combining the world's largest multi-channel and multicultural content distribution.\u0026nbsp;\u003c/b\u003e\u003c/p\u003e\r\n\u003cp\u003eSome people are born leaders while some leaders can be birthed through hard work and training, dedication, and determination. \u003cb\u003eGreat leaders help people around them to grow while providing guidance, motivation, and a clear example for others to follow.\u003c/b\u003e Another important aspect of leadership, especially in the hospitality industry, is having stellar people skills which is a must for any successful leader.\u003c/p\u003e\u003cp\u003e\u003cimg src=\"https://bucketrediseno.s3.amazonaws.com/uploads/photo/image/9838/hospitality_executive.jpg\" id=\"9838\" style=\"width: 100%;\"\u003e\u003cbr\u003e\u003c/p\u003e\r\n\u003cp\u003eAs a consequence, \u003cb\u003ewe are committed to recruiting people who are passionate, results and service-oriented, resourceful, and have the tenacity to achieve our objective of becoming the number one group of Mexico resorts while boosting our growth plan to even higher heights. \u003c/b\u003eFor this reason, we are excited to see the fruition of Kirwan’s vision during his tenure at The Villa Group Resorts as he continues to advance his hospitality career.\u003c/p\u003e\r\n\u003cp\u003eBeing the Vice President of Operations is no easy task. One \u003cb\u003ehas to uphold the image of the entire organization, improve revenue-generating capacity, increase productivity, and develop and implement efficient operations and cost-effective systems\u003c/b\u003e to meet the current and future needs of the company while providing a safe and efficient working environment that is essential to the overall performance of the business.\u003c/p\u003e\r\n\u003cp\u003eAs Vice President of Operations, John Paul Kirwan’s goal is to continue to grow The Villa Group’s brand by using his extensive experience and expertise accumulated throughout his long and fruitfulcareer in the tourism industry. \u003cb\u003eWith more than 20 years of experience managing hotels in multiple vacation destinations and 35 years in total creating unforgettable experiences for guests\u003c/b\u003e, Kirwan is to hit the ground running to create an environment that both he and his team can thrive harmoniously.\u003c/p\u003e\u003cp\u003e\u003cimg src=\"https://bucketrediseno.s3.amazonaws.com/uploads/photo/image/9808/the_villa_group_ceo.jpg\" id=\"9808\" style=\"width: 100%;\"\u003e\u003cspan style=\"font-size: 1rem;\"\u003e\u003cbr\u003e\u003c/span\u003e\u003c/p\u003e\u003cp\u003e\u003cspan style=\"font-size: 1rem;\"\u003eThroughout his hospitality career, Kirwan has deepened his management experience and expertise in the development of financial strategies, providing luxury vacation experiences, marketing, crisis management, the creation and execution of operation manuals, and more.\u003c/span\u003e\u003cb style=\"font-size: 1rem;\"\u003e Kirwan is also a member of the Board of Directors of the Los Cabos Hotel Association \u003c/b\u003e\u003cspan style=\"font-size: 1rem;\"\u003ewhich is a non-profit organization representing the development and interests of the hotel industry in Los Cabos, Mexico.\u003c/span\u003e\u003cbr\u003e\u003c/p\u003e\r\n\u003cp\u003eHe will be in charge of a large team as there are several \u003ca href=\"https://villagroupresorts.com/resorts\" style=\"\"\u003eall inclusive resorts Mexico under one umbrella\u003c/a\u003e\u003cb\u003e \u003c/b\u003erepresenting \u003cb\u003eThe\u0026nbsp; Group Beach Resorts and Spa's award-winning brand. \u003c/b\u003eIt should come as no surprise that our all inclusive resorts are located in some of the best vacation destinations in Mexico and each has its own unique luxury vacation experiences you won’t find anywhere else. And Kirwan is keen on taking up the baton to elevate The Villa Group's operations and services to the highest hospitality standards.\u003c/p\u003e\r\n\u003cp\u003e\u003cbr\u003e\u003cb\u003eOur 35 years of experience as The Villa Group Resorts has allowed us to provide more luxury vacation experiences via high-end and family-oriented\u003c/b\u003e, all-suite accommodations, well-equipped facilities, and hospitable services at our diverse properties in Puerto Vallarta, Cabo San Lucas, Riviera Nayarit, and Los Cabos. Discover something new and exciting at any one of our all inclusive Mexico resorts!\u003c/p\u003e\r\n\u003c!--\r\n  Copyright 2023 Google LLC\r\n\r\n  Licensed under the Apache License, Version 2.0 (the \"License\");\r\n  you may not use this file except in compliance with the License.\r\n  You may obtain a copy of the License at\r\n\r\n      https://www.apache.org/licenses/LICENSE-2.0\r\n\r\n  Unless required by applicable law or agreed to in writing, software\r\n  distributed under the License is distributed on an \"AS IS\" BASIS,\r\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n  See the License for the specific language governing permissions and\r\n  limitations under the License.\r\n--\u003e\r\n\r\n\r\n  \r\n    \u003ctitle\u003eNeighborhood Discovery\u003c/title\u003e\r\n    \u003cmeta charset=\"UTF-8\"\u003e\r\n    \u003cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1\"\u003e\r\n    \u003cscript src=\"https://ajax.googleapis.com/ajax/libs/handlebars/4.7.7/handlebars.min.js\"\u003e\u003c/script\u003e\r\n    \u003clink href=\"https://fonts.googleapis.com/css?family=Roboto\" rel=\"stylesheet\"\u003e\r\n    \u003clink href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\"\u003e\r\n    \u003cstyle\u003e\r\n      html, body {\r\n        height: 100%;\r\n        margin: 0;\r\n        padding: 0;\r\n      }\r\n\r\n      .neighborhood-discovery {\r\n        box-sizing: border-box;\r\n        font-family: \"Roboto\", sans-serif;\r\n        height: 100%;\r\n        position: relative;\r\n        width: 100%;\r\n      }\r\n\r\n      .neighborhood-discovery a {\r\n        color: #4285f4;\r\n        text-decoration: none;\r\n      }\r\n\r\n      .neighborhood-discovery button {\r\n        background: none;\r\n        border: none;\r\n        color: inherit;\r\n        cursor: pointer;\r\n        font: inherit;\r\n        font-size: inherit;\r\n        padding: 0;\r\n      }\r\n\r\n      .neighborhood-discovery .info {\r\n        color: #555;\r\n        font-size: 0.9em;\r\n        margin-top: 0.3em;\r\n      }\r\n\r\n      .neighborhood-discovery .panel {\r\n        background: white;\r\n        bottom: 0;\r\n        box-sizing: border-box;\r\n        left: 0;\r\n        overflow-y: auto;\r\n        position: absolute;\r\n        top: 0;\r\n        width: 20em;\r\n      }\r\n\r\n      .neighborhood-discovery .panel.no-scroll {\r\n        overflow-y: hidden;\r\n      }\r\n\r\n      .neighborhood-discovery .photo {\r\n        background-color: #dadce0;\r\n        background-position: center;\r\n        background-size: cover;\r\n        border-radius: 0.3em;\r\n        cursor: pointer;\r\n      }\r\n\r\n      .neighborhood-discovery .navbar {\r\n        background: white;\r\n        position: sticky;\r\n        top: 0;\r\n        z-index: 2;\r\n      }\r\n\r\n      .neighborhood-discovery .right {\r\n        float: right;\r\n      }\r\n\r\n      .neighborhood-discovery .star-icon {\r\n        filter: invert(88%) sepia(60%) saturate(2073%) hue-rotate(318deg) brightness(93%) contrast(104%);\r\n        height: 1.2em;\r\n        margin-right: -0.3em;\r\n        margin-top: -0.08em;\r\n        vertical-align: top;\r\n        width: 1.2em;\r\n      }\r\n\r\n      .neighborhood-discovery .star-icon:last-child {\r\n        margin-right: 0.2em;\r\n      }\r\n\r\n      .neighborhood-discovery .travel-icon {\r\n        height: 1.2em;\r\n        margin-top: -0.08em;\r\n        vertical-align: top;\r\n      }\r\n\r\n      .neighborhood-discovery .map {\r\n        bottom: 0;\r\n        left: 20em;\r\n        position: absolute;\r\n        right: 0;\r\n        top: 0;\r\n      }\r\n\r\n      @media only screen and (max-width: 640px) {\r\n        .neighborhood-discovery .panel {\r\n          right: 0;\r\n          top: 50%;\r\n          width: unset;\r\n        }\r\n\r\n        .neighborhood-discovery .map {\r\n          bottom: 50%;\r\n          left: 0;\r\n        }\r\n      }\r\n\r\n      /* --------------------------- PLACES PANEL --------------------------- */\r\n\r\n      .neighborhood-discovery .places-panel {\r\n        box-shadow: 0 0 10px rgb(60 64 67 / 28%);\r\n        z-index: 1;\r\n      }\r\n\r\n      .neighborhood-discovery .places-panel header {\r\n        padding: 0.5em;\r\n      }\r\n\r\n      .neighborhood-discovery .search-input input {\r\n        border: 1px solid rgba(0, 0, 0, 0.2);\r\n        border-radius: 0.3em;\r\n        box-sizing: border-box;\r\n        font-size: 1em;\r\n        height: 2.2em;\r\n        padding: 0 2.5em 0 1em;\r\n        width: 100%;\r\n      }\r\n\r\n      .neighborhood-discovery .search-input button {\r\n        position: absolute;\r\n        right: 0.8em;\r\n        top: 0.8em;\r\n      }\r\n\r\n      .neighborhood-discovery .show-more-button {\r\n        bottom: 0.5em;\r\n        display: none;\r\n        left: 28%;\r\n        line-height: 1.5em;\r\n        padding: 0.6em;\r\n        position: relative;\r\n        width: 44%;\r\n      }\r\n\r\n      .neighborhood-discovery .show-more-button.sticky {\r\n        background: white;\r\n        border-radius: 1.5em;\r\n        box-shadow: 0 4px 10px rgb(60 64 67 / 28%);\r\n        position: sticky;\r\n        z-index: 2;\r\n      }\r\n\r\n      .neighborhood-discovery .show-more-button:disabled {\r\n        opacity: 0.5;\r\n      }\r\n\r\n      .neighborhood-discovery .place-results-list {\r\n        list-style-type: none;\r\n        margin: 0;\r\n        padding: 0;\r\n      }\r\n\r\n      .neighborhood-discovery .place-result {\r\n        border-top: 1px solid rgba(0, 0, 0, 0.12);\r\n        cursor: pointer;\r\n        display: flex;\r\n        padding: 0.8em;\r\n      }\r\n\r\n      .neighborhood-discovery .place-result .text {\r\n        flex-grow: 1;\r\n      }\r\n\r\n      .neighborhood-discovery .place-result .name {\r\n        font-size: 1em;\r\n        font-weight: 500;\r\n        text-align: left;\r\n      }\r\n\r\n      .neighborhood-discovery .place-result .photo {\r\n        flex: 0 0 4em;\r\n        height: 4em;\r\n        margin-left: 0.8em;\r\n      }\r\n\r\n      /* -------------------------- DETAILS PANEL --------------------------- */\r\n\r\n      .neighborhood-discovery .details-panel {\r\n        display: none;\r\n        z-index: 20;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .back-button {\r\n        color: #4285f4;\r\n        padding: 0.9em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .back-button .icon {\r\n        /* Match link color #4285f4 */\r\n        filter: invert(47%) sepia(71%) saturate(2372%) hue-rotate(200deg) brightness(97%) contrast(98%);\r\n        height: 1.2em;\r\n        width: 1.2em;\r\n        vertical-align: bottom;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel header {\r\n        padding: 0.9em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel h2 {\r\n        font-size: 1.4em;\r\n        font-weight: 400;\r\n        margin: 0;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .section {\r\n        border-top: 1px solid rgba(0, 0, 0, 0.12);\r\n        padding: 0.9em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .contact {\r\n        align-items: center;\r\n        display: flex;\r\n        font-size: 0.9em;\r\n        margin: 0.8em 0;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .contact .icon {\r\n        width: 1.5em;\r\n        height: 1.5em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .contact .text {\r\n        margin-left: 1em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .contact .weekday {\r\n        display: inline-block;\r\n        width: 5em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .photos {\r\n        text-align: center;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .photo {\r\n        display: inline-block;\r\n        height: 5.5em;\r\n        width: 5.5em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .review {\r\n        margin-top: 1.2em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .review .reviewer-avatar {\r\n        background-repeat: no-repeat;\r\n        background-size: cover;\r\n        float: left;\r\n        height: 1.8em;\r\n        margin-right: 0.8em;\r\n        width: 1.8em;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .review .reviewer-name {\r\n        color: #202124;\r\n        font-weight: 500;\r\n        line-height: 1.8em;\r\n        overflow: hidden;\r\n        text-overflow: ellipsis;\r\n        white-space: nowrap;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .review .rating {\r\n        margin: 0.5em 0;\r\n      }\r\n\r\n      .neighborhood-discovery .details-panel .attribution {\r\n        color: #777;\r\n        margin: 0;\r\n        font-size: 0.8em;\r\n        font-style: italic;\r\n      }\r\n\r\n      /* --------------------------- PHOTO MODAL ---------------------------- */\r\n\r\n      .neighborhood-discovery .photo-modal {\r\n        background: rgba(0, 0, 0, 0.8);\r\n        display: none;\r\n        height: 100%;\r\n        position: fixed;\r\n        width: 100%;\r\n        z-index: 100;\r\n      }\r\n\r\n      .neighborhood-discovery .photo-modal \u003e img {\r\n        bottom: 0;\r\n        left: 0;\r\n        margin: auto;\r\n        max-height: 100%;\r\n        max-width: 100%;\r\n        position: absolute;\r\n        right: 0;\r\n        top: 0;\r\n      }\r\n\r\n      .neighborhood-discovery .photo-modal \u003e div {\r\n        border-radius: 0.4em;\r\n        color: white;\r\n        background: rgba(0, 0, 0, 0.6);\r\n        margin: 1em;\r\n        padding: 0.9em;\r\n        position: absolute;\r\n      }\r\n\r\n      .neighborhood-discovery .photo-modal .back-button .icon {\r\n        filter: brightness(0) invert(1);\r\n        margin: 0.4em 0.6em 0 0;\r\n      }\r\n\r\n      .neighborhood-discovery .photo-modal .photo-text {\r\n        float: right;\r\n      }\r\n\r\n      .neighborhood-discovery .photo-modal .photo-attrs {\r\n        font-size: 0.8em;\r\n        margin-top: 0.3em;\r\n      }\r\n    \u003c/style\u003e\r\n    \u003cscript\u003e\r\n      'use strict';\r\n\r\n      /** Hides a DOM element and optionally focuses on focusEl. */\r\n      function hideElement(el, focusEl) {\r\n        el.style.display = 'none';\r\n        if (focusEl) focusEl.focus();\r\n      }\r\n\r\n      /** Shows a DOM element that has been hidden and optionally focuses on focusEl. */\r\n      function showElement(el, focusEl) {\r\n        el.style.display = 'block';\r\n        if (focusEl) focusEl.focus();\r\n      }\r\n\r\n      /** Determines if a DOM element contains content that cannot be scrolled into view. */\r\n      function hasHiddenContent(el) {\r\n        const noscroll = window.getComputedStyle(el).overflowY.includes('hidden');\r\n        return noscroll \u0026\u0026 el.scrollHeight \u003e el.clientHeight;\r\n      }\r\n\r\n      /** Format a Place Type string by capitalizing and replacing underscores with spaces. */\r\n      function formatPlaceType(str) {\r\n        const capitalized = str.charAt(0).toUpperCase() + str.slice(1);\r\n        return capitalized.replace(/_/g, ' ');\r\n      }\r\n\r\n      /** Initializes an array of zeros with the given size. */\r\n      function initArray(arraySize) {\r\n        const array = [];\r\n        while (array.length \u003c arraySize) {\r\n          array.push(0);\r\n        }\r\n        return array;\r\n      }\r\n\r\n      /** Assigns star icons to an object given its rating (out of 5). */\r\n      function addStarIcons(obj) {\r\n        if (!obj.rating) return;\r\n        const starsOutOfTen = Math.round(2 * obj.rating);\r\n        const fullStars = Math.floor(starsOutOfTen / 2);\r\n        const halfStars = fullStars !== starsOutOfTen / 2 ? 1 : 0;\r\n        const emptyStars = 5 - fullStars - halfStars;\r\n\r\n        // Express stars as arrays to make iterating in Handlebars easy.\r\n        obj.fullStarIcons = initArray(fullStars);\r\n        obj.halfStarIcons = initArray(halfStars);\r\n        obj.emptyStarIcons = initArray(emptyStars);\r\n      }\r\n\r\n      /**\r\n       * Constructs an array of opening hours by day from a PlaceOpeningHours object,\r\n       * where adjacent days of week with the same hours are collapsed into one element.\r\n       */\r\n      function parseDaysHours(openingHours) {\r\n        const daysHours = openingHours.weekday_text.map((e) =\u003e e.split(/\\:\\s+/))\r\n                  .map((e) =\u003e ({'days': e[0].substr(0, 3), 'hours': e[1]}));\r\n\r\n        for (let i = 1; i \u003c daysHours.length; i++) {\r\n          if (daysHours[i - 1].hours === daysHours[i].hours) {\r\n            if (daysHours[i - 1].days.indexOf('-') !== -1) {\r\n              daysHours[i - 1].days =\r\n                  daysHours[i - 1].days.replace(/\\w+$/, daysHours[i].days);\r\n            } else {\r\n              daysHours[i - 1].days += ' - ' + daysHours[i].days;\r\n            }\r\n            daysHours.splice(i--, 1);\r\n          }\r\n        }\r\n        return daysHours;\r\n      }\r\n\r\n      /** Number of POIs to show on widget load. */\r\n      const ND_NUM_PLACES_INITIAL = 5;\r\n\r\n      /** Number of additional POIs to show when 'Show More' button is clicked. */\r\n      const ND_NUM_PLACES_SHOW_MORE = 5;\r\n\r\n      /** Maximum number of place photos to show on the details panel. */\r\n      const ND_NUM_PLACE_PHOTOS_MAX = 6;\r\n\r\n      /** Minimum zoom level at which the default map POI pins will be shown. */\r\n      const ND_DEFAULT_POI_MIN_ZOOM = 18;\r\n\r\n      /** Mapping of Place Types to Material Icons used to render custom map markers. */\r\n      const ND_MARKER_ICONS_BY_TYPE = {\r\n        // Full list of icons can be found at https://fonts.google.com/icons\r\n        '_default': 'circle',\r\n        'restaurant': 'restaurant',\r\n        'cafe': 'local_cafe',\r\n        'night_club': 'nightlife',\r\n        'bar': 'local_bar',\r\n        'movie_theater': 'theaters',\r\n        'art_gallery': 'palette',\r\n        'supermarket': 'local_grocery_store',\r\n        'bakery': 'bakery_dining',\r\n        'department_store': 'local_mall',\r\n        'electronics_store': 'local_mall',\r\n        'shopping_mall': 'local_mall',\r\n        'atm': 'atm',\r\n      };\r\n\r\n      /**\r\n       * Defines an instance of the Neighborhood Discovery widget, to be\r\n       * instantiated when the Maps library is loaded.\r\n       */\r\n      function NeighborhoodDiscovery(configuration) {\r\n        const widget = this;\r\n        const widgetEl = document.querySelector('.neighborhood-discovery');\r\n\r\n        widget.center = configuration.mapOptions.center;\r\n        widget.places = configuration.pois || [];\r\n\r\n        // Initialize core functionalities -------------------------------------\r\n\r\n        initializeMap();\r\n        initializePlaceDetails();\r\n        initializeSidePanel();\r\n\r\n        // Initialize additional capabilities ----------------------------------\r\n\r\n        initializeSearchInput();\r\n        initializeDistanceMatrix();\r\n        initializeDirections();\r\n\r\n        // Initializer function definitions ------------------------------------\r\n\r\n        /** Initializes the interactive map and adds place markers. */\r\n        function initializeMap() {\r\n          const mapOptions = configuration.mapOptions;\r\n          widget.mapBounds = new google.maps.Circle(\r\n            {center: widget.center, radius: configuration.mapRadius}).getBounds();\r\n          mapOptions.restriction = {latLngBounds: widget.mapBounds};\r\n          mapOptions.mapTypeControlOptions = {position: google.maps.ControlPosition.TOP_RIGHT};\r\n          widget.map = new google.maps.Map(widgetEl.querySelector('.map'), mapOptions);\r\n          widget.map.fitBounds(widget.mapBounds, /* padding= */ 0);\r\n          widget.map.addListener('click', (e) =\u003e {\r\n            // Check if user clicks on a POI pin from the base map.\r\n            if (e.placeId) {\r\n              e.stop();\r\n              widget.selectPlaceById(e.placeId);\r\n            }\r\n          });\r\n          widget.map.addListener('zoom_changed', () =\u003e {\r\n            // Customize map styling to show/hide default POI pins or text based on zoom level.\r\n            const hideDefaultPoiPins = widget.map.getZoom() \u003c ND_DEFAULT_POI_MIN_ZOOM;\r\n            widget.map.setOptions({\r\n              styles: [{\r\n                featureType: 'poi',\r\n                elementType: hideDefaultPoiPins ? 'labels' : 'labels.text',\r\n                stylers: [{visibility: 'off'}],\r\n              }],\r\n            });\r\n          });\r\n\r\n          const markerPath = widgetEl.querySelector('.marker-pin path').getAttribute('d');\r\n          const drawMarker = function(title, position, fillColor, strokeColor, labelText) {\r\n            return new google.maps.Marker({\r\n              title: title,\r\n              position: position,\r\n              map: widget.map,\r\n              icon: {\r\n                path: markerPath,\r\n                fillColor: fillColor,\r\n                fillOpacity: 1,\r\n                strokeColor: strokeColor,\r\n                anchor: new google.maps.Point(13, 35),\r\n                labelOrigin: new google.maps.Point(13, 13),\r\n              },\r\n              label: {\r\n                text: labelText,\r\n                color: 'white',\r\n                fontSize: '16px',\r\n                fontFamily: 'Material Icons',\r\n              },\r\n            });\r\n          };\r\n\r\n          // Add marker at the center location (if specified).\r\n          if (configuration.centerMarker \u0026\u0026 configuration.centerMarker.icon) {\r\n            drawMarker('Home', widget.center,\r\n                       '#1A73E8', '#185ABC', configuration.centerMarker.icon);\r\n          }\r\n\r\n          // Add marker for the specified Place object.\r\n          widget.addPlaceMarker = function(place) {\r\n            place.marker = drawMarker(place.name, place.coords, '#EA4335', '#C5221F', place.icon);\r\n            place.marker.addListener('click', () =\u003e void widget.selectPlaceById(place.placeId));\r\n          };\r\n\r\n          // Fit map to bounds that contain all markers of the specified Place objects.\r\n          widget.updateBounds = function(places) {\r\n            const bounds = new google.maps.LatLngBounds();\r\n            bounds.extend(widget.center);\r\n            for (let place of places) {\r\n              bounds.extend(place.marker.getPosition());\r\n            }\r\n            widget.map.fitBounds(bounds, /* padding= */ 100);\r\n          };\r\n\r\n          // Marker used to highlight a place from Autocomplete search.\r\n          widget.selectedPlaceMarker = new google.maps.Marker({title: 'Point of Interest'});\r\n        }\r\n\r\n        /** Initializes Place Details service for the widget. */\r\n        function initializePlaceDetails() {\r\n          const detailsService = new google.maps.places.PlacesService(widget.map);\r\n          const placeIdsToDetails = new Map();  // Create object to hold Place results.\r\n\r\n          for (let place of widget.places) {\r\n            placeIdsToDetails.set(place.placeId, place);\r\n            place.fetchedFields = new Set(['place_id']);\r\n          }\r\n\r\n          widget.fetchPlaceDetails = function(placeId, fields, callback) {\r\n            if (!placeId || !fields) return;\r\n\r\n            // Check for field existence in Place object.\r\n            let place = placeIdsToDetails.get(placeId);\r\n            if (!place) {\r\n              place = {placeId: placeId, fetchedFields: new Set(['place_id'])};\r\n              placeIdsToDetails.set(placeId, place);\r\n            }\r\n            const missingFields = fields.filter((field) =\u003e !place.fetchedFields.has(field));\r\n            if (missingFields.length === 0) {\r\n              callback(place);\r\n              return;\r\n            }\r\n\r\n            const request = {placeId: placeId, fields: missingFields};\r\n            let retryCount = 0;\r\n            const processResult = function(result, status) {\r\n              if (status !== google.maps.places.PlacesServiceStatus.OK) {\r\n                // If query limit has been reached, wait before making another call;\r\n                // Increase wait time of each successive retry with exponential backoff\r\n                // and terminate after five failed attempts.\r\n                if (status === google.maps.places.PlacesServiceStatus.OVER_QUERY_LIMIT \u0026\u0026\r\n                    retryCount \u003c 5) {\r\n                  const delay = (Math.pow(2, retryCount) + Math.random()) * 500;\r\n                  setTimeout(() =\u003e void detailsService.getDetails(request, processResult), delay);\r\n                  retryCount++;\r\n                }\r\n                return;\r\n              }\r\n\r\n              // Basic details.\r\n              if (result.name) place.name = result.name;\r\n              if (result.geometry) place.coords = result.geometry.location;\r\n              if (result.formatted_address) place.address = result.formatted_address;\r\n              if (result.photos) {\r\n                place.photos = result.photos.map((photo) =\u003e ({\r\n                  urlSmall: photo.getUrl({maxWidth: 200, maxHeight: 200}),\r\n                  urlLarge: photo.getUrl({maxWidth: 1200, maxHeight: 1200}),\r\n                  attrs: photo.html_attributions,\r\n                })).slice(0, ND_NUM_PLACE_PHOTOS_MAX);\r\n              }\r\n              if (result.types) {\r\n                place.type = formatPlaceType(result.types[0]);\r\n                place.icon = ND_MARKER_ICONS_BY_TYPE['_default'];\r\n                for (let type of result.types) {\r\n                  if (type in ND_MARKER_ICONS_BY_TYPE) {\r\n                    place.type = formatPlaceType(type);\r\n                    place.icon = ND_MARKER_ICONS_BY_TYPE[type];\r\n                    break;\r\n                  }\r\n                }\r\n              }\r\n              if (result.url) place.url = result.url;\r\n\r\n              // Contact details.\r\n              if (result.website) {\r\n                place.website = result.website;\r\n                const url = new URL(place.website);\r\n                place.websiteDomain = url.hostname;\r\n              }\r\n              if (result.formatted_phone_number) place.phoneNumber = result.formatted_phone_number;\r\n              if (result.opening_hours) place.openingHours = parseDaysHours(result.opening_hours);\r\n\r\n              // Review details.\r\n              if (result.rating) {\r\n                place.rating = result.rating;\r\n                addStarIcons(place);\r\n              }\r\n              if (result.user_ratings_total) place.numReviews = result.user_ratings_total;\r\n              if (result.price_level) {\r\n                place.priceLevel = result.price_level;\r\n                place.dollarSigns = initArray(result.price_level);\r\n              }\r\n              if (result.reviews) {\r\n                place.reviews = result.reviews;\r\n                for (let review of place.reviews) {\r\n                  addStarIcons(review);\r\n                }\r\n              }\r\n\r\n              for (let field of missingFields) {\r\n                place.fetchedFields.add(field);\r\n              }\r\n              callback(place);\r\n            };\r\n\r\n            // Use result from Place Autocomplete if available.\r\n            if (widget.placeIdsToAutocompleteResults) {\r\n              const autoCompleteResult = widget.placeIdsToAutocompleteResults.get(placeId);\r\n              if (autoCompleteResult) {\r\n                processResult(autoCompleteResult, google.maps.places.PlacesServiceStatus.OK);\r\n                return;\r\n              }\r\n            }\r\n            detailsService.getDetails(request, processResult);\r\n          };\r\n        }\r\n\r\n        /** Initializes the side panel that holds curated POI results. */\r\n        function initializeSidePanel() {\r\n          const placesPanelEl = widgetEl.querySelector('.places-panel');\r\n          const detailsPanelEl = widgetEl.querySelector('.details-panel');\r\n          const placeResultsEl = widgetEl.querySelector('.place-results-list');\r\n          const showMoreButtonEl = widgetEl.querySelector('.show-more-button');\r\n          const photoModalEl = widgetEl.querySelector('.photo-modal');\r\n          const detailsTemplate = Handlebars.compile(\r\n              document.getElementById('nd-place-details-tmpl').innerHTML);\r\n          const resultsTemplate = Handlebars.compile(\r\n              document.getElementById('nd-place-results-tmpl').innerHTML);\r\n\r\n          // Show specified POI photo in a modal.\r\n          const showPhotoModal = function(photo, placeName) {\r\n            const prevFocusEl = document.activeElement;\r\n            const imgEl = photoModalEl.querySelector('img');\r\n            imgEl.src = photo.urlLarge;\r\n            const backButtonEl = photoModalEl.querySelector('.back-button');\r\n            backButtonEl.addEventListener('click', () =\u003e {\r\n              hideElement(photoModalEl, prevFocusEl);\r\n              imgEl.src = '';\r\n            });\r\n            photoModalEl.querySelector('.photo-place').innerHTML = placeName;\r\n            photoModalEl.querySelector('.photo-attrs span').innerHTML = photo.attrs;\r\n            const attributionEl = photoModalEl.querySelector('.photo-attrs a');\r\n            if (attributionEl) attributionEl.setAttribute('target', '_blank');\r\n            photoModalEl.addEventListener('click', (e) =\u003e {\r\n              if (e.target === photoModalEl) {\r\n                hideElement(photoModalEl, prevFocusEl);\r\n                imgEl.src = '';\r\n              }\r\n            });\r\n            showElement(photoModalEl, backButtonEl);\r\n          };\r\n\r\n          // Select a place by id and show details.\r\n          let selectedPlaceId;\r\n          widget.selectPlaceById = function(placeId, panToMarker) {\r\n            if (selectedPlaceId === placeId) return;\r\n            selectedPlaceId = placeId;\r\n            const prevFocusEl = document.activeElement;\r\n\r\n            const showDetailsPanel = function(place) {\r\n              detailsPanelEl.innerHTML = detailsTemplate(place);\r\n              const backButtonEl = detailsPanelEl.querySelector('.back-button');\r\n              backButtonEl.addEventListener('click', () =\u003e {\r\n                hideElement(detailsPanelEl, prevFocusEl);\r\n                selectedPlaceId = undefined;\r\n                widget.updateDirections();\r\n                widget.selectedPlaceMarker.setMap(null);\r\n              });\r\n              detailsPanelEl.querySelectorAll('.photo').forEach((photoEl, i) =\u003e {\r\n                photoEl.addEventListener('click', () =\u003e {\r\n                  showPhotoModal(place.photos[i], place.name);\r\n                });\r\n              });\r\n              showElement(detailsPanelEl, backButtonEl);\r\n              detailsPanelEl.scrollTop = 0;\r\n            };\r\n\r\n            const processResult = function(place) {\r\n              if (place.marker) {\r\n                widget.selectedPlaceMarker.setMap(null);\r\n              } else {\r\n                widget.selectedPlaceMarker.setPosition(place.coords);\r\n                widget.selectedPlaceMarker.setMap(widget.map);\r\n              }\r\n              if (panToMarker) {\r\n                widget.map.panTo(place.coords);\r\n              }\r\n              showDetailsPanel(place);\r\n              widget.fetchDuration(place, showDetailsPanel);\r\n              widget.updateDirections(place);\r\n            };\r\n\r\n            widget.fetchPlaceDetails(placeId, [\r\n              'name', 'types', 'geometry.location', 'formatted_address', 'photo', 'url',\r\n              'website', 'formatted_phone_number', 'opening_hours',\r\n              'rating', 'user_ratings_total', 'price_level', 'review',\r\n            ], processResult);\r\n          };\r\n\r\n          // Render the specified place objects and append them to the POI list.\r\n          const renderPlaceResults = function(places, startIndex) {\r\n            placeResultsEl.insertAdjacentHTML('beforeend', resultsTemplate({places: places}));\r\n            placeResultsEl.querySelectorAll('.place-result').forEach((resultEl, i) =\u003e {\r\n              const place = places[i - startIndex];\r\n              if (!place) return;\r\n              // Clicking anywhere on the item selects the place.\r\n              // Additionally, create a button element to make this behavior\r\n              // accessible under tab navigation.\r\n              resultEl.addEventListener('click', () =\u003e {\r\n                widget.selectPlaceById(place.placeId, /* panToMarker= */ true);\r\n              });\r\n              resultEl.querySelector('.name').addEventListener('click', (e) =\u003e {\r\n                widget.selectPlaceById(place.placeId, /* panToMarker= */ true);\r\n                e.stopPropagation();\r\n              });\r\n              resultEl.querySelector('.photo').addEventListener('click', (e) =\u003e {\r\n                showPhotoModal(place.photos[0], place.name);\r\n                e.stopPropagation();\r\n              });\r\n              widget.addPlaceMarker(place);\r\n            });\r\n          };\r\n\r\n          // Index of next Place object to show in the POI list.\r\n          let nextPlaceIndex = 0;\r\n\r\n          // Fetch and show basic info for the next N places.\r\n          const showNextPlaces = function(n) {\r\n            const nextPlaces = widget.places.slice(nextPlaceIndex, nextPlaceIndex + n);\r\n            if (nextPlaces.length \u003c 1) {\r\n              hideElement(showMoreButtonEl);\r\n              return;\r\n            }\r\n            showMoreButtonEl.disabled = true;\r\n            // Keep track of the number of Places calls that have not finished.\r\n            let count = nextPlaces.length;\r\n            for (let place of nextPlaces) {\r\n              const processResult = function(place) {\r\n                count--;\r\n                if (count \u003e 0) return;\r\n                renderPlaceResults(nextPlaces, nextPlaceIndex);\r\n                nextPlaceIndex += n;\r\n                widget.updateBounds(widget.places.slice(0, nextPlaceIndex));\r\n                const hasMorePlacesToShow = nextPlaceIndex \u003c widget.places.length;\r\n                if (hasMorePlacesToShow || hasHiddenContent(placesPanelEl)) {\r\n                  showElement(showMoreButtonEl);\r\n                  showMoreButtonEl.disabled = false;\r\n                } else {\r\n                  hideElement(showMoreButtonEl);\r\n                }\r\n              };\r\n              widget.fetchPlaceDetails(place.placeId, [\r\n                'name', 'types', 'geometry.location',\r\n                'photo',\r\n                'rating', 'user_ratings_total', 'price_level',\r\n              ], processResult);\r\n            }\r\n          };\r\n          showNextPlaces(ND_NUM_PLACES_INITIAL);\r\n\r\n          showMoreButtonEl.addEventListener('click', () =\u003e {\r\n            placesPanelEl.classList.remove('no-scroll');\r\n            showMoreButtonEl.classList.remove('sticky');\r\n            showNextPlaces(ND_NUM_PLACES_SHOW_MORE);\r\n          });\r\n        }\r\n\r\n        /** Initializes Search Input for the widget. */\r\n        function initializeSearchInput() {\r\n          const searchInputEl = widgetEl.querySelector('.place-search-input');\r\n          widget.placeIdsToAutocompleteResults = new Map();\r\n\r\n          // Set up Autocomplete on the search input.\r\n          const autocomplete = new google.maps.places.Autocomplete(searchInputEl, {\r\n            types: ['establishment'],\r\n            fields: [\r\n              'place_id', 'name', 'types', 'geometry.location', 'formatted_address', 'photo', 'url',\r\n              'website', 'formatted_phone_number', 'opening_hours',\r\n              'rating', 'user_ratings_total', 'price_level', 'review',\r\n            ],\r\n            bounds: widget.mapBounds,\r\n            strictBounds: true,\r\n          });\r\n          autocomplete.addListener('place_changed', () =\u003e {\r\n            const place = autocomplete.getPlace();\r\n            widget.placeIdsToAutocompleteResults.set(place.place_id, place);\r\n            widget.selectPlaceById(place.place_id, /* panToMarker= */ true);\r\n            searchInputEl.value = '';\r\n          });\r\n        }\r\n\r\n        /** Initializes Distance Matrix service for the widget. */\r\n        function initializeDistanceMatrix() {\r\n          const distanceMatrixService = new google.maps.DistanceMatrixService();\r\n\r\n          // Annotate travel times from the centered location to the specified place.\r\n          widget.fetchDuration = function(place, callback) {\r\n            if (!widget.center || !place || !place.coords || place.duration) return;\r\n            const request = {\r\n              origins: [widget.center],\r\n              destinations: [place.coords],\r\n              travelMode: google.maps.TravelMode.DRIVING,\r\n            };\r\n            distanceMatrixService.getDistanceMatrix(request, function(result, status) {\r\n              if (status === google.maps.DistanceMatrixStatus.OK) {\r\n                const trip = result.rows[0].elements[0];\r\n                if (trip.status === google.maps.DistanceMatrixElementStatus.OK) {\r\n                  place.duration = trip.duration;\r\n                  callback(place);\r\n                }\r\n              }\r\n            });\r\n          };\r\n        }\r\n\r\n        /** Initializes Directions service for the widget. */\r\n        function initializeDirections() {\r\n          const directionsService = new google.maps.DirectionsService();\r\n          const directionsRenderer = new google.maps.DirectionsRenderer({\r\n            suppressMarkers: true,\r\n            preserveViewport: true,\r\n          });\r\n\r\n          // Update directions from the centered location to specified place.\r\n          widget.updateDirections = function(place) {\r\n            if (!widget.center || !place || !place.coords) {\r\n              directionsRenderer.setMap(null);\r\n              return;\r\n            }\r\n            // Use existing results if available.\r\n            if (place.directions) {\r\n              directionsRenderer.setMap(widget.map);\r\n              directionsRenderer.setDirections(place.directions);\r\n              return;\r\n            }\r\n            const request = {\r\n              origin: widget.center,\r\n              destination: place.coords,\r\n              travelMode: google.maps.TravelMode.DRIVING,\r\n            };\r\n            directionsService.route(request, function(result, status) {\r\n              if (status === google.maps.DirectionsStatus.OK) {\r\n                place.directions = result;\r\n                directionsRenderer.setMap(widget.map);\r\n                directionsRenderer.setDirections(result);\r\n              }\r\n            });\r\n          };\r\n        }\r\n      }\r\n    \u003c/script\u003e\r\n    \u003cscript\u003e\r\n      const CONFIGURATION = {\r\n        \"capabilities\": {\"search\":true,\"distances\":true,\"directions\":true,\"contacts\":true,\"atmospheres\":true,\"thumbnails\":true},\r\n        \"pois\": [\r\n          {\"placeId\": \"ChIJXZGCs-RKr4YRTR77TJ8eJPc\"},\r\n          {\"placeId\": \"ChIJ7YUKFKtLr4YRvIG-TE78BrQ\"},\r\n          {\"placeId\": \"ChIJF5dO2H1Kr4YRPhk6hhMhsJI\"},\r\n          {\"placeId\": \"ChIJh-tOmuVKr4YR-xU38u6Vqrg\"},\r\n          {\"placeId\": \"ChIJuUG-2OJKr4YReYbejsEg0HA\"},\r\n          {\"placeId\": \"ChIJgTdeQvtKr4YRBhZnIKtcyHw\"},\r\n          {\"placeId\": \"ChIJG6JYPPxKr4YRD1hyyiEb9b0\"},\r\n          {\"placeId\": \"ChIJX3J_nvFKr4YRQgO2_7SvtFU\"},\r\n          {\"placeId\": \"ChIJ99wNvORKr4YRaCt4TnsGFbw\"},\r\n          {\"placeId\": \"ChIJhY9VgKpLr4YRNyYcIa5zskE\"},\r\n          {\"placeId\": \"ChIJ-elZMuVKr4YRF6LD2FGgI2o\"},\r\n          {\"placeId\": \"ChIJT1jjqv5Kr4YRva5i4fDrp4k\"},\r\n          {\"placeId\": \"ChIJD2JVIf1Kr4YRTgV_Qq3xRPE\"},\r\n          {\"placeId\": \"ChIJSf6zZ_dKr4YR4XdiV8y7zLk\"},\r\n          {\"placeId\": \"ChIJZ1c1c_5Kr4YR7Uj3SSo3kMY\"},\r\n          {\"placeId\": \"ChIJs7Sdo4VLr4YRozRn5cbTYWo\"},\r\n          {\"placeId\": \"ChIJWT7hsORKr4YRIE7ruFlPcAg\"},\r\n          {\"placeId\": \"ChIJqYiYDvxKr4YRcShuxKj3wxI\"},\r\n          {\"placeId\": \"ChIJd_ATM_tKr4YRha5WKP08IJM\"},\r\n          {\"placeId\": \"ChIJ-8bh-uVKr4YRz6D0Pg8eFFg\"},\r\n          {\"placeId\": \"ChIJ1xm9k1dar4YRit6FrSpcWsI\"}\r\n        ],\r\n        \"centerMarker\": {\"icon\":\"circle\"},\r\n        \"mapRadius\": 2000,\r\n        \"mapOptions\": {\"center\":{\"lat\":22.89387499999999,\"lng\":-109.8988731},\"fullscreenControl\":true,\"mapTypeControl\":true,\"streetViewControl\":false,\"zoom\":16,\"zoomControl\":true,\"maxZoom\":20,\"mapId\":\"\"},\r\n        \"mapsApiKey\": \"YOUR_API_KEY_HERE\"\r\n      };\r\n\r\n      function initMap() {\r\n        new NeighborhoodDiscovery(CONFIGURATION);\r\n      }\r\n    \u003c/script\u003e\r\n    \u003cscript id=\"nd-place-results-tmpl\" type=\"text/x-handlebars-template\"\u003e\r\n      {{#each places}}\r\n        \u003cli class=\"place-result\"\u003e\r\n          \u003cdiv class=\"text\"\u003e\r\n            \u003cbutton class=\"name\"\u003e{{name}}\u003c/button\u003e\r\n            \u003cdiv class=\"info\"\u003e\r\n              {{#if rating}}\r\n                \u003cspan\u003e{{rating}}\u003c/span\u003e\r\n                \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star/v15/24px.svg\" alt=\"rating\" class=\"star-icon\"/\u003e\r\n              {{/if}}\r\n              {{#if numReviews}}\r\n                \u003cspan\u003e\u0026nbsp;({{numReviews}})\u003c/span\u003e\r\n              {{/if}}\r\n              {{#if priceLevel}}\r\n                \u0026#183;\u0026nbsp;\u003cspan\u003e{{#each dollarSigns}}${{/each}}\u0026nbsp;\u003c/span\u003e\r\n              {{/if}}\r\n            \u003c/div\u003e\r\n            \u003cdiv class=\"info\"\u003e{{type}}\u003c/div\u003e\r\n          \u003c/div\u003e\r\n          \u003cbutton class=\"photo\" style=\"background-image:url({{photos.0.urlSmall}})\" aria-label=\"show photo in viewer\"\u003e\u003c/button\u003e\r\n        \u003c/li\u003e\r\n      {{/each}}\r\n    \u003c/script\u003e\r\n    \u003cscript id=\"nd-place-details-tmpl\" type=\"text/x-handlebars-template\"\u003e\r\n      \u003cdiv class=\"navbar\"\u003e\r\n        \u003cbutton class=\"back-button\"\u003e\r\n          \u003cimg class=\"icon\" src=\"https://fonts.gstatic.com/s/i/googlematerialicons/arrow_back/v11/24px.svg\" alt=\"back\"/\u003e\r\n          Back\r\n        \u003c/button\u003e\r\n      \u003c/div\u003e\r\n      \u003cheader\u003e\r\n        \u003ch2\u003e{{name}}\u003c/h2\u003e\r\n        \u003cdiv class=\"info\"\u003e\r\n          {{#if rating}}\r\n            \u003cspan class=\"star-rating-numeric\"\u003e{{rating}}\u003c/span\u003e\r\n            \u003cspan\u003e\r\n              {{#each fullStarIcons}}\r\n                \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star/v15/24px.svg\" alt=\"full star\" class=\"star-icon\"/\u003e\r\n              {{/each}}\r\n              {{#each halfStarIcons}}\r\n                \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star_half/v17/24px.svg\" alt=\"half star\" class=\"star-icon\"/\u003e\r\n              {{/each}}\r\n              {{#each emptyStarIcons}}\r\n                \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star_outline/v9/24px.svg\" alt=\"empty star\" class=\"star-icon\"/\u003e\r\n              {{/each}}\r\n            \u003c/span\u003e\r\n          {{/if}}\r\n          {{#if numReviews}}\r\n            \u003ca href=\"{{url}}\" target=\"_blank\"\u003e{{numReviews}} reviews\u003c/a\u003e\r\n          {{else}}\r\n            \u003ca href=\"{{url}}\" target=\"_blank\"\u003eSee on Google Maps\u003c/a\u003e\r\n          {{/if}}\r\n          {{#if priceLevel}}\r\n            \u0026#183;\r\n            \u003cspan class=\"price-dollars\"\u003e\r\n              {{#each dollarSigns}}${{/each}}\r\n            \u003c/span\u003e\r\n          {{/if}}\r\n        \u003c/div\u003e\r\n        {{#if type}}\r\n          \u003cdiv class=\"info\"\u003e{{type}}\u003c/div\u003e\r\n        {{/if}}\r\n        {{#if duration}}\r\n          \u003cdiv class=\"info\"\u003e\r\n            \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/directions_car/v11/24px.svg\" alt=\"car travel\" class=\"travel-icon\"/\u003e\r\n            \u003cspan\u003e\u0026nbsp;{{duration.text}}\u003c/span\u003e\r\n          \u003c/div\u003e\r\n        {{/if}}\r\n      \u003c/header\u003e\r\n      \u003cdiv class=\"section\"\u003e\r\n        {{#if address}}\r\n          \u003cdiv class=\"contact\"\u003e\r\n            \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/place/v10/24px.svg\" alt=\"Address\" class=\"icon\"/\u003e\r\n            \u003cdiv class=\"text\"\u003e\r\n              {{address}}\r\n            \u003c/div\u003e\r\n          \u003c/div\u003e\r\n        {{/if}}\r\n        {{#if website}}\r\n          \u003cdiv class=\"contact\"\u003e\r\n            \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/public/v10/24px.svg\" alt=\"Website\" class=\"icon\"/\u003e\r\n            \u003cdiv class=\"text\"\u003e\r\n              \u003ca href=\"{{website}}\" target=\"_blank\"\u003e{{websiteDomain}}\u003c/a\u003e\r\n            \u003c/div\u003e\r\n          \u003c/div\u003e\r\n        {{/if}}\r\n        {{#if phoneNumber}}\r\n          \u003cdiv class=\"contact\"\u003e\r\n            \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/phone/v10/24px.svg\" alt=\"Phone number\" class=\"icon\"/\u003e\r\n            \u003cdiv class=\"text\"\u003e\r\n              {{phoneNumber}}\r\n            \u003c/div\u003e\r\n          \u003c/div\u003e\r\n        {{/if}}\r\n        {{#if openingHours}}\r\n          \u003cdiv class=\"contact\"\u003e\r\n            \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/schedule/v12/24px.svg\" alt=\"Opening hours\" class=\"icon\"/\u003e\r\n            \u003cdiv class=\"text\"\u003e\r\n              {{#each openingHours}}\r\n                \u003cdiv\u003e\r\n                  \u003cspan class=\"weekday\"\u003e{{days}}\u003c/span\u003e\r\n                  \u003cspan class=\"hours\"\u003e{{hours}}\u003c/span\u003e\r\n                \u003c/div\u003e\r\n              {{/each}}\r\n            \u003c/div\u003e\r\n          \u003c/div\u003e\r\n        {{/if}}\r\n      \u003c/div\u003e\r\n      {{#if photos}}\r\n        \u003cdiv class=\"photos section\"\u003e\r\n          {{#each photos}}\r\n            \u003cbutton class=\"photo\" style=\"background-image:url({{urlSmall}})\" aria-label=\"show photo in viewer\"\u003e\u003c/button\u003e\r\n          {{/each}}\r\n        \u003c/div\u003e\r\n      {{/if}}\r\n      {{#if reviews}}\r\n        \u003cdiv class=\"reviews section\"\u003e\r\n          \u003cp class=\"attribution\"\u003eReviews by Google users\u003c/p\u003e\r\n          {{#each reviews}}\r\n            \u003cdiv class=\"review\"\u003e\r\n              \u003ca class=\"reviewer-identity\" href=\"{{author_url}}\" target=\"_blank\"\u003e\r\n                \u003cdiv class=\"reviewer-avatar\" style=\"background-image:url({{profile_photo_url}})\"\u003e\u003c/div\u003e\r\n                \u003cdiv class=\"reviewer-name\"\u003e{{author_name}}\u003c/div\u003e\r\n              \u003c/a\u003e\r\n              \u003cdiv class=\"rating info\"\u003e\r\n                \u003cspan\u003e\r\n                  {{#each fullStarIcons}}\r\n                    \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star/v15/24px.svg\" alt=\"full star\" class=\"star-icon\"/\u003e\r\n                  {{/each}}\r\n                  {{#each halfStarIcons}}\r\n                    \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star_half/v17/24px.svg\" alt=\"half star\" class=\"star-icon\"/\u003e\r\n                  {{/each}}\r\n                  {{#each emptyStarIcons}}\r\n                    \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/star_outline/v9/24px.svg\" alt=\"empty star\" class=\"star-icon\"/\u003e\r\n                  {{/each}}\r\n                \u003c/span\u003e\r\n                \u003cspan class=\"review-time\"\u003e{{relative_time_description}}\u003c/span\u003e\r\n              \u003c/div\u003e\r\n              \u003cdiv class=\"info\"\u003e{{text}}\u003c/div\u003e\r\n            \u003c/div\u003e\r\n          {{/each}}\r\n        \u003c/div\u003e\r\n      {{/if}}\r\n      {{#if html_attributions}}\r\n        \u003cdiv class=\"section\"\u003e\r\n          {{#each html_attributions}}\r\n            \u003cp class=\"attribution\"\u003e{{{this}}}\u003c/p\u003e\r\n          {{/each}}\r\n        \u003c/div\u003e\r\n      {{/if}}\r\n    \u003c/script\u003e\r\n  \r\n  \r\n    \u003cdiv class=\"neighborhood-discovery\"\u003e\r\n      \u003cdiv class=\"places-panel panel no-scroll\"\u003e\r\n        \u003cheader class=\"navbar\"\u003e\r\n          \u003cdiv class=\"search-input\"\u003e\r\n            \u003cinput class=\"place-search-input\" placeholder=\"Search nearby places\"\u003e\r\n            \u003cbutton class=\"place-search-button\"\u003e\r\n              \u003cimg src=\"https://fonts.gstatic.com/s/i/googlematerialicons/search/v11/24px.svg\" alt=\"search\"\u003e\r\n            \u003c/button\u003e\r\n          \u003c/div\u003e\r\n        \u003c/header\u003e\r\n        \u003cdiv class=\"results\"\u003e\r\n          \u003cul class=\"place-results-list\"\u003e\u003c/ul\u003e\r\n        \u003c/div\u003e\r\n        \u003cbutton class=\"show-more-button sticky\"\u003e\r\n          \u003cspan\u003eShow More\u003c/span\u003e\r\n          \u003cimg class=\"right\" src=\"https://fonts.gstatic.com/s/i/googlematerialicons/expand_more/v11/24px.svg\" alt=\"expand\"\u003e\r\n        \u003c/button\u003e\r\n      \u003c/div\u003e\r\n      \u003cdiv class=\"details-panel panel\"\u003e\u003c/div\u003e\r\n      \u003cdiv class=\"map\"\u003e\u003c/div\u003e\r\n      \u003cdiv class=\"photo-modal\"\u003e\r\n        \u003cimg alt=\"place photo\"\u003e\r\n        \u003cdiv\u003e\r\n          \u003cbutton class=\"back-button\"\u003e\r\n            \u003cimg class=\"icon\" src=\"https://fonts.gstatic.com/s/i/googlematerialicons/arrow_back/v11/24px.svg\" alt=\"back\"\u003e\r\n          \u003c/button\u003e\r\n          \u003cdiv class=\"photo-text\"\u003e\r\n            \u003cdiv class=\"photo-place\"\u003e\u003c/div\u003e\r\n            \u003cdiv class=\"photo-attrs\"\u003ePhoto by \u003cspan\u003e\u003c/span\u003e\u003c/div\u003e\r\n          \u003c/div\u003e\r\n        \u003c/div\u003e\r\n      \u003c/div\u003e\r\n      \u003csvg class=\"marker-pin\" xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"38\" fill=\"none\"\u003e\r\n        \u003cpath d=\"M13 0C5.817 0 0 5.93 0 13.267c0 7.862 5.59 10.81 9.555 17.624C12.09 35.248 11.342 38 13 38c1.723 0 .975-2.817 3.445-7.043C20.085 24.503 26 21.162 26 13.267 26 5.93 20.183 0 13 0Z\"\u003e\u003c/path\u003e\r\n      \u003c/svg\u003e\r\n    \u003c/div\u003e\r\n    \u003cscript src=\"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY_HERE\u0026amp;callback=initMap\u0026amp;libraries=places,geometry\u0026amp;solution_channel=GMP_QB_neighborhooddiscovery_v3_cABCDEF\" async=\"\" defer=\"\"\u003e\u003c/script\u003e\r\n  \r\n","cover":{"url":null,"webp":{"url":null},"optimizada":{"url":null},"thumbnail":{"url":null}},"cover_en":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/john_paul_kirwan.jpg","webp":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/john_paul_kirwan.webp"},"optimizada":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/optimizada_john_paul_kirwan.jpg"},"thumbnail":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/thumbnail_john_paul_kirwan.jpg"},"rezise_360":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/rezise_360_john_paul_kirwan.jpg"},"optimizada_home":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/optimizada_home_john_paul_kirwan.webp"},"optimizada_360p":{"url":"https://villacdn.villagroupresorts.com/uploads/article/cover_en/1327/optimizada_360p_john_paul_kirwan.jpg"}},"slug":"the-villa-group-beach-resorts-spas-nombra-a-john-paul-kirwan-vicepresidente-de-operaciones","slug_en":"the-villa-group-beach-resorts-spas-appoints-john-paul-kirwan-as-vice-president-of-operations","status":"borrador","hotel_id":10,"user_id":14620,"created_at":"2023-02-01T11:01:53.000-06:00","updated_at":"2025-04-11T11:04:48.000-06:00","url":"https://villagroupresorts.com.mx/villa-core/blog/1327.json"}