{"id":16890,"date":"2025-11-17T08:46:38","date_gmt":"2025-11-17T07:46:38","guid":{"rendered":"https:\/\/lespouilles.blog\/apulia-favorites\/"},"modified":"2026-07-02T11:23:06","modified_gmt":"2026-07-02T09:23:06","slug":"apulia-favorites","status":"publish","type":"page","link":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/","title":{"rendered":"APULIA &#8211; Favorites"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"16890\" class=\"elementor elementor-16890 elementor-6671\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8dc424f e-flex e-con-boxed e-con e-parent\" data-id=\"8dc424f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7debdb8 elementor-widget elementor-widget-html\" data-id=\"7debdb8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!-- =================================================== -->\r\n<!--  APULIA IA \u2014 FAVORIS (ONGLETS MULTI + LISTE)        -->\r\n<!--  localStorage key = apulia_favs_v11                 -->\r\n<!-- =================================================== -->\r\n\r\n<style>\r\n  .apx-page-header{\r\n    display:flex;align-items:center;justify-content:space-between;\r\n    padding:10px 8px 6px;\r\n    font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif;\r\n    color:#FFFFFF;\r\n  }\r\n  .apx-header-back{width:40px;display:inline-flex;align-items:center;justify-content:flex-start;text-decoration:none;}\r\n  .apx-header-back span{font-size:30px;line-height:1;color:#F6A623;}\r\n  .apx-header-title{flex:1;text-align:center;font-size:24px;font-weight:600;}\r\n  .apx-header-spacer{width:40px;}\r\n\r\n  .apulia-favs-shell{\r\n    max-width:960px;margin:12px auto 40px;padding:0 16px 24px;\r\n    font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif;\r\n    color:#fff;\r\n  }\r\n\r\n  .apulia-favs-tabs-wrapper{display:flex;justify-content:center;margin-bottom:14px;}\r\n  .apulia-favs-tabs{\r\n    display:flex;gap:2px;padding:4px 3px;border-radius:999px;\r\n    background:rgba(0,0,0,0.55);border:1px solid rgba(255,255,255,0.10);\r\n    overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;\r\n  }\r\n  .apulia-favs-tabs::-webkit-scrollbar{display:none;}\r\n  .apulia-fav-tab{\r\n    flex:1 1 0;border:none;background:transparent;padding:6px 4px;\r\n    font-size:12px;line-height:1.2;font-weight:600;color:rgba(255,255,255,0.75);\r\n    cursor:pointer;border-radius:999px;text-align:center;white-space:nowrap;\r\n    transition:background-color 0.18s ease,color 0.18s ease;\r\n  }\r\n  .apulia-fav-tab:hover{background:rgba(255,255,255,0.06);}\r\n  .apulia-fav-tab.is-active{background:#F6A623;color:#001D26;}\r\n\r\n  @media (max-width: 768px){\r\n    .apulia-fav-tab{flex:0 0 calc(33.333% - 4px);font-size:12px;padding:7px 4px;}\r\n  }\r\n\r\n  .apulia-favs-panels{position:relative;overflow:hidden;}\r\n  .apulia-fav-panel{display:none;}\r\n  .apulia-fav-panel.is-active{display:block;}\r\n\r\n  .apulia-favs-empty{\r\n    font-size:15px;opacity:0.85;padding:14px 14px;border-radius:18px;background:rgba(0,0,0,0.32);\r\n  }\r\n\r\n  .apulia-favs-grid{\r\n    display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));\r\n    gap:16px;margin-top:10px;\r\n  }\r\n  .apulia-fav-card{\r\n    display:flex;flex-direction:column;border-radius:24px;overflow:hidden;\r\n    background:linear-gradient(135deg,rgba(0,31,45,0.96),rgba(0,94,117,0.95));\r\n    box-shadow:0 14px 40px rgba(0,0,0,0.45);\r\n    text-decoration:none;color:#fff;min-height:240px;\r\n  }\r\n  .apulia-fav-thumb{position:relative;padding-top:62%;overflow:hidden;}\r\n  .apulia-fav-thumb img{\r\n    position:absolute;inset:0;width:100%;height:100%;object-fit:cover;\r\n    transform:scale(1.03);transition:transform 0.2s ease;\r\n  }\r\n  .apulia-fav-card:hover .apulia-fav-thumb img{transform:scale(1.06);}\r\n  .apulia-fav-body{padding:12px 14px 14px;display:flex;flex-direction:column;gap:4px;}\r\n  .apulia-fav-name{font-size:16px;font-weight:600;}\r\n<\/style>\r\n\r\n<div class=\"apx-page-header\">\r\n  <a class=\"apx-header-back\" href=\"https:\/\/lespouilles.blog\/en\/apulia-explorer\/\" aria-label=\"Retour \u00e0 Explorer\"><span>\u2039<\/span><\/a>\r\n  <div class=\"apx-header-title\">Favoris<\/div>\r\n  <div class=\"apx-header-spacer\"><\/div>\r\n<\/div>\r\n\r\n<div class=\"apulia-favs-shell\" id=\"apulia-favs-shell\">\r\n\r\n  <div class=\"apulia-favs-tabs-wrapper\">\r\n    <div class=\"apulia-favs-tabs\" role=\"tablist\">\r\n      <button class=\"apulia-fav-tab is-active\" data-tab=\"ville_village\" aria-selected=\"true\" aria-controls=\"apulia-tab-ville_village\">Villes<\/button>\r\n      <button class=\"apulia-fav-tab\" data-tab=\"plage\" aria-controls=\"apulia-tab-plage\">Plages<\/button>\r\n      <button class=\"apulia-fav-tab\" data-tab=\"hebergement\" aria-controls=\"apulia-tab-hebergement\">H\u00e9bergements<\/button>\r\n      <button class=\"apulia-fav-tab\" data-tab=\"restaurant\" aria-controls=\"apulia-tab-restaurant\">Restaurants<\/button>\r\n      <button class=\"apulia-fav-tab\" data-tab=\"activite\" aria-controls=\"apulia-tab-activite\">Activit\u00e9s<\/button>\r\n      <button class=\"apulia-fav-tab\" data-tab=\"location\" aria-controls=\"apulia-tab-location\">Locations<\/button>\r\n      <button class=\"apulia-fav-tab\" data-tab=\"lido\" aria-controls=\"apulia-tab-lido\">Lido<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div class=\"apulia-favs-panels\">\r\n    <section id=\"apulia-tab-ville_village\" class=\"apulia-fav-panel is-active\"><div id=\"apulia-favs-ville_village-content\"><\/div><\/section>\r\n    <section id=\"apulia-tab-plage\" class=\"apulia-fav-panel\"><div id=\"apulia-favs-plage-content\"><\/div><\/section>\r\n    <section id=\"apulia-tab-hebergement\" class=\"apulia-fav-panel\"><div id=\"apulia-favs-hebergement-content\"><\/div><\/section>\r\n    <section id=\"apulia-tab-restaurant\" class=\"apulia-fav-panel\"><div id=\"apulia-favs-restaurant-content\"><\/div><\/section>\r\n    <section id=\"apulia-tab-activite\" class=\"apulia-fav-panel\"><div id=\"apulia-favs-activite-content\"><\/div><\/section>\r\n    <section id=\"apulia-tab-location\" class=\"apulia-fav-panel\"><div id=\"apulia-favs-location-content\"><\/div><\/section>\r\n    <section id=\"apulia-tab-lido\" class=\"apulia-fav-panel\"><div id=\"apulia-favs-lido-content\"><\/div><\/section>\r\n  <\/div>\r\n<\/div>\r\n\r\n<script>\r\n(function(){\r\n  \"use strict\";\r\n\r\n  const STORAGE_KEY = \"apulia_favs_v11\";\r\n\r\n  \/\/ WP REST (public)\r\n  const ORIGIN   = window.location.origin.replace(\/\\\/$\/, \"\");\r\n  const WP_V2    = ORIGIN + \"\/wp-json\/wp\/v2\";\r\n  const CPT      = \"explorer_item\";\r\n  const TAX      = \"explorer_type\";\r\n\r\n  \/\/ --- utils ---\r\n  function loadFavs(){\r\n    try{\r\n      const raw = localStorage.getItem(STORAGE_KEY);\r\n      if(!raw) return [];\r\n      const parsed = JSON.parse(raw);\r\n      return Array.isArray(parsed) ? parsed : [];\r\n    }catch(e){ return []; }\r\n  }\r\n  function saveFavs(list){\r\n    try{ localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); }catch(e){}\r\n  }\r\n  function uniqById(list){\r\n    const seen = new Set();\r\n    const out = [];\r\n    list.forEach(x=>{\r\n      const id = x && x.id != null ? parseInt(String(x.id),10) : NaN;\r\n      if(Number.isNaN(id) || id <= 0) return;\r\n      if(seen.has(id)) return;\r\n      seen.add(id);\r\n      out.push({ id: String(id) });\r\n    });\r\n    return out;\r\n  }\r\n  function stripTags(html){\r\n    return String(html||\"\").replace(\/<[^>]*>\/g,\"\").trim();\r\n  }\r\n  function escapeHtml(s){\r\n    return String(s || \"\")\r\n      .replace(\/&\/g,\"&amp;\")\r\n      .replace(\/<\/g,\"&lt;\")\r\n      .replace(\/>\/g,\"&gt;\")\r\n      .replace(\/\"\/g,\"&quot;\")\r\n      .replace(\/'\/g,\"&#039;\");\r\n  }\r\n  function renderEmpty(c, m){\r\n    if(!c) return;\r\n    c.innerHTML = '<div class=\"apulia-favs-empty\">'+escapeHtml(m)+'<\/div>';\r\n  }\r\n  function renderCards(c, list){\r\n    if(!c) return;\r\n    if(!list.length){\r\n      renderEmpty(c, \"Aucun favori pour le moment.\");\r\n      return;\r\n    }\r\n    const items = list.map(function(x){\r\n      const href  = x.href || \"#\";\r\n      const title = x.title || \"Sans titre\";\r\n      const img   = x.img || \"\";\r\n\r\n      const thumb = img\r\n        ? '<div class=\"apulia-fav-thumb\"><img decoding=\"async\" loading=\"lazy\" src=\"'+escapeHtml(img)+'\" alt=\"'+escapeHtml(title)+'\"><\/div>'\r\n        : \"\";\r\n\r\n      return (\r\n        '<a class=\"apulia-fav-card\" href=\"'+escapeHtml(href)+'\">' +\r\n          thumb +\r\n          '<div class=\"apulia-fav-body\">' +\r\n            '<div class=\"apulia-fav-name\">'+escapeHtml(title)+'<\/div>' +\r\n          '<\/div>' +\r\n        '<\/a>'\r\n      );\r\n    });\r\n\r\n    c.innerHTML = '<div class=\"apulia-favs-grid\">'+items.join(\"\")+'<\/div>';\r\n  }\r\n\r\n  \/\/ --- cache session ---\r\n  const CACHE_KEY = \"apulia_favs_wp_cache_v5\"; \/\/ bump (for items)\r\n  const MEDIA_CACHE_KEY = \"apulia_favs_media_cache_v1\"; \/\/ for media URLs\r\n  const HTMLIMG_CACHE_KEY = \"apulia_favs_htmlimg_cache_v1\"; \/\/ PATCH ville\/plage -> extract img from permalink HTML\r\n\r\n  function loadCache(key){\r\n    try{\r\n      const raw = sessionStorage.getItem(key);\r\n      if(!raw) return {};\r\n      const o = JSON.parse(raw);\r\n      return (o && typeof o === \"object\") ? o : {};\r\n    }catch(e){ return {}; }\r\n  }\r\n  function saveCache(key, o){\r\n    try{ sessionStorage.setItem(key, JSON.stringify(o||{})); }catch(e){}\r\n  }\r\n\r\n  \/\/ --- taxonomy map (termId -> slug) ---\r\n  function fetchTaxonomyMap(){\r\n    return fetch(WP_V2 + \"\/\" + TAX + \"?per_page=100&_fields=id,slug\", { credentials:\"include\" })\r\n      .then(r => r.ok ? r.json() : [])\r\n      .then(arr => {\r\n        const map = {};\r\n        (Array.isArray(arr)?arr:[]).forEach(t=>{\r\n          if(t && t.id != null && t.slug) map[String(t.id)] = String(t.slug).toLowerCase();\r\n        });\r\n        return map;\r\n      })\r\n      .catch(() => ({}));\r\n  }\r\n\r\n  \/\/ --- fetch bulk posts ---\r\n  function fetchItemsBulk(ids){\r\n    if(!ids || !ids.length) return Promise.resolve([]);\r\n    const per = Math.min(ids.length, 100);\r\n\r\n    \/\/ IMPORTANT: on inclut featured_media (ID) pour fallback media direct\r\n    const url = WP_V2 + \"\/\" + CPT\r\n      + \"?include=\" + ids.map(encodeURIComponent).join(\",\")\r\n      + \"&per_page=\" + per\r\n      + \"&_embed\"\r\n      + \"&_fields=id,link,title,\" + TAX + \",acf,featured_media,_embedded\";\r\n\r\n    return fetch(url, { credentials:\"include\" })\r\n      .then(r => r.ok ? r.json() : [])\r\n      .then(arr => Array.isArray(arr) ? arr : [])\r\n      .catch(() => []);\r\n  }\r\n\r\n  \/\/ --- fetch media by ID (fallback ville\/plage) ---\r\n  function fetchMediaUrl(mediaId){\r\n    const mid = parseInt(String(mediaId||\"\"),10);\r\n    if(!mid || mid <= 0) return Promise.resolve(\"\");\r\n\r\n    const mediaCache = loadCache(MEDIA_CACHE_KEY);\r\n    if(mediaCache[String(mid)]) return Promise.resolve(mediaCache[String(mid)]);\r\n\r\n    const url = WP_V2 + \"\/media\/\" + encodeURIComponent(mid) + \"?_fields=id,source_url,media_details,guid\";\r\n    return fetch(url, { credentials:\"include\" })\r\n      .then(r => r.ok ? r.json() : null)\r\n      .then(m => {\r\n        let u = \"\";\r\n        try{\r\n          if(m && m.source_url) u = m.source_url;\r\n          if(!u && m && m.media_details && m.media_details.sizes){\r\n            const s = m.media_details.sizes;\r\n            if(s.large && s.large.source_url) u = s.large.source_url;\r\n            else if(s.medium_large && s.medium_large.source_url) u = s.medium_large.source_url;\r\n            else if(s.medium && s.medium.source_url) u = s.medium.source_url;\r\n            else if(s.full && s.full.source_url) u = s.full.source_url;\r\n          }\r\n          if(!u && m && m.guid && m.guid.rendered) u = m.guid.rendered;\r\n        }catch(e){}\r\n\r\n        if(u){\r\n          mediaCache[String(mid)] = u;\r\n          saveCache(MEDIA_CACHE_KEY, mediaCache);\r\n        }\r\n        return u || \"\";\r\n      })\r\n      .catch(() => \"\");\r\n  }\r\n\r\n  \/\/ --- PATCH: ville\/plage -> extract image from permalink HTML (bypass REST \/media 401) ---\r\n  function fetchImageFromPermalink(permalink){\r\n    const url = String(permalink||\"\");\r\n    if(!url) return Promise.resolve(\"\");\r\n\r\n    const htmlCache = loadCache(HTMLIMG_CACHE_KEY);\r\n    if(htmlCache[url]) return Promise.resolve(htmlCache[url]);\r\n\r\n    return fetch(url, { credentials:\"include\" })\r\n      .then(r => r.ok ? r.text() : \"\")\r\n      .then(html => {\r\n        let img = \"\";\r\n        try{\r\n          const doc = new DOMParser().parseFromString(html, \"text\/html\");\r\n\r\n          \/\/ 1) og:image\r\n          const og = doc.querySelector('meta[property=\"og:image\"], meta[name=\"og:image\"]');\r\n          if(og && og.getAttribute(\"content\")) img = String(og.getAttribute(\"content\")||\"\").trim();\r\n\r\n          \/\/ 2) twitter:image\r\n          if(!img){\r\n            const tw = doc.querySelector('meta[name=\"twitter:image\"], meta[property=\"twitter:image\"]');\r\n            if(tw && tw.getAttribute(\"content\")) img = String(tw.getAttribute(\"content\")||\"\").trim();\r\n          }\r\n\r\n          \/\/ 3) common featured img classes\r\n          if(!img){\r\n            const im = doc.querySelector(\"img.wp-post-image, img.attachment-post-thumbnail, img.attachment-large, img.attachment-full\");\r\n            if(im && im.getAttribute(\"src\")) img = String(im.getAttribute(\"src\")||\"\").trim();\r\n          }\r\n\r\n          \/\/ 4) any first large-ish image (fallback very last)\r\n          if(!img){\r\n            const any = doc.querySelector(\"main img[src], article img[src], .site-main img[src]\");\r\n            if(any && any.getAttribute(\"src\")) img = String(any.getAttribute(\"src\")||\"\").trim();\r\n          }\r\n        }catch(e){}\r\n\r\n        if(img){\r\n          htmlCache[url] = img;\r\n          saveCache(HTMLIMG_CACHE_KEY, htmlCache);\r\n        }\r\n        return img || \"\";\r\n      })\r\n      .catch(() => \"\");\r\n  }\r\n\r\n  \/\/ --- map slug -> onglet ---\r\n  function slugToTab(slug){\r\n    const s = String(slug||\"\").toLowerCase();\r\n    if (s === \"plage\") return \"plage\";\r\n    if (s === \"hebergement\") return \"hebergement\";\r\n    if (s === \"restaurant\") return \"restaurant\";\r\n    if (s === \"activite\" || s === \"activit\u00e9\") return \"activite\";\r\n    if (s === \"location\") return \"location\";\r\n    if (s === \"lido\") return \"lido\";\r\n    if (s === \"ville\" || s === \"village\") return \"ville_village\";\r\n    return \"ville_village\";\r\n  }\r\n\r\n  function isProTab(tab){\r\n    return tab === \"hebergement\" || tab === \"restaurant\" || tab === \"activite\" || tab === \"location\" || tab === \"lido\";\r\n  }\r\n\r\n  function resolveTitle(p, tab){\r\n    const wpTitle = stripTags(p && p.title && p.title.rendered ? p.title.rendered : \"\");\r\n    if(isProTab(tab)){\r\n      const acf = p && p.acf ? p.acf : null;\r\n      const nc = acf && typeof acf.nom_commercial === \"string\" ? acf.nom_commercial.trim() : \"\";\r\n      if(nc) return nc;\r\n    }\r\n    return wpTitle || \"Sans titre\";\r\n  }\r\n\r\n  function resolveImageFromEmbedded(p){\r\n    let img = \"\";\r\n    try{\r\n      const fm = p && p._embedded && p._embedded[\"wp:featuredmedia\"] && p._embedded[\"wp:featuredmedia\"][0];\r\n      if(fm){\r\n        if(fm.source_url) img = fm.source_url;\r\n        if(!img && fm.media_details && fm.media_details.sizes){\r\n          const s = fm.media_details.sizes;\r\n          if(s.large && s.large.source_url) img = s.large.source_url;\r\n          else if(s.medium_large && s.medium_large.source_url) img = s.medium_large.source_url;\r\n          else if(s.medium && s.medium.source_url) img = s.medium.source_url;\r\n          else if(s.full && s.full.source_url) img = s.full.source_url;\r\n        }\r\n        if(!img && fm.guid && fm.guid.rendered) img = fm.guid.rendered;\r\n      }\r\n    }catch(e){}\r\n    return img;\r\n  }\r\n\r\n  function resolveProFallbackImage(p){\r\n    let img = \"\";\r\n    const acf = p && p.acf ? p.acf : null;\r\n    const pm = acf && typeof acf.pro_main_url === \"string\" ? acf.pro_main_url.trim() : \"\";\r\n    if(pm) img = pm;\r\n\r\n    if(!img && acf && Array.isArray(acf.galerie_photos) && acf.galerie_photos[0]){\r\n      const u = acf.galerie_photos[0].url;\r\n      const uu = (typeof u === \"string\") ? u.trim() : \"\";\r\n      if(uu) img = uu;\r\n    }\r\n    return img;\r\n  }\r\n\r\n  function initTabsAndSwipe(){\r\n    const shell=document.getElementById(\"apulia-favs-shell\");\r\n    if(!shell) return;\r\n\r\n    const tabs=shell.querySelectorAll(\".apulia-fav-tab\");\r\n    const panels=shell.querySelectorAll(\".apulia-fav-panel\");\r\n    const order=[\"ville_village\",\"plage\",\"hebergement\",\"restaurant\",\"activite\",\"location\",\"lido\"];\r\n\r\n    function activate(n){\r\n      tabs.forEach(b=>{\r\n        const a=b.getAttribute(\"data-tab\")===n;\r\n        b.classList.toggle(\"is-active\",a);\r\n        b.setAttribute(\"aria-selected\",a?\"true\":\"false\");\r\n      });\r\n      panels.forEach(p=>{\r\n        p.classList.toggle(\"is-active\",p.id===\"apulia-tab-\"+n);\r\n      });\r\n      const activeBtn = shell.querySelector(\".apulia-fav-tab.is-active\");\r\n      if(activeBtn && activeBtn.scrollIntoView){\r\n        activeBtn.scrollIntoView({ behavior:\"smooth\", block:\"nearest\", inline:\"center\" });\r\n      }\r\n    }\r\n\r\n    tabs.forEach(b=> b.addEventListener(\"click\", ()=>activate(b.getAttribute(\"data-tab\"))) );\r\n\r\n    let sx=null,sy=null;\r\n    document.addEventListener(\"touchstart\",e=>{\r\n      if(!e.touches.length)return;\r\n      sx=e.touches[0].clientX; sy=e.touches[0].clientY;\r\n    },{passive:true});\r\n\r\n    document.addEventListener(\"touchend\",e=>{\r\n      if(sx===null||sy===null)return;\r\n      const t=e.changedTouches[0];\r\n      const dx=t.clientX-sx,dy=t.clientY-sy;\r\n      sx=sy=null;\r\n      if(Math.abs(dx)<40||Math.abs(dx)<Math.abs(dy))return;\r\n      const cur=shell.querySelector(\".apulia-fav-tab.is-active\");\r\n      if(!cur)return;\r\n      const n=cur.getAttribute(\"data-tab\");\r\n      const idx=order.indexOf(n);\r\n      let next=idx;\r\n      if(dx<0) next=Math.min(order.length-1,idx+1);\r\n      else     next=Math.max(0,idx-1);\r\n      if(next!==idx) activate(order[next]);\r\n    },{passive:true});\r\n  }\r\n\r\n  function refreshPanels(){\r\n    \/\/ normalise storage\r\n    const cleaned = uniqById(loadFavs());\r\n    saveFavs(cleaned);\r\n\r\n    const vv=[], pl=[], hb=[], rs=[], ac=[], lo=[], li=[];\r\n    const cache = loadCache(CACHE_KEY);\r\n\r\n    \/\/ loading\r\n    renderEmpty(document.getElementById(\"apulia-favs-ville_village-content\"), \"Chargement...\");\r\n    renderEmpty(document.getElementById(\"apulia-favs-plage-content\"), \"Chargement...\");\r\n    renderEmpty(document.getElementById(\"apulia-favs-hebergement-content\"), \"Chargement...\");\r\n    renderEmpty(document.getElementById(\"apulia-favs-restaurant-content\"), \"Chargement...\");\r\n    renderEmpty(document.getElementById(\"apulia-favs-activite-content\"), \"Chargement...\");\r\n    renderEmpty(document.getElementById(\"apulia-favs-location-content\"), \"Chargement...\");\r\n    renderEmpty(document.getElementById(\"apulia-favs-lido-content\"), \"Chargement...\");\r\n\r\n    const ids = cleaned.map(x=>x.id);\r\n\r\n    fetchTaxonomyMap().then(function(taxMap){\r\n      const missing = ids.filter(id => !cache[id]);\r\n\r\n      fetchItemsBulk(missing).then(function(posts){\r\n        const postById = {};\r\n        (posts||[]).forEach(p=>{\r\n          if(p && p.id != null) postById[String(p.id)] = p;\r\n        });\r\n\r\n        \/\/ Build items + jobs (media or html extraction)\r\n        const jobs = [];\r\n\r\n        Object.keys(postById).forEach(function(id){\r\n          const p = postById[id];\r\n\r\n          const termIds = Array.isArray(p[TAX]) ? p[TAX] : [];\r\n          const firstSlug = termIds.length ? (taxMap[String(termIds[0])] || \"\") : \"\";\r\n          const tab = slugToTab(firstSlug);\r\n\r\n          const title = resolveTitle(p, tab);\r\n          const href  = p.link || \"\";\r\n\r\n          let img = \"\";\r\n\r\n          \/\/ 1) PRO: ACF first (unchanged)\r\n          if(isProTab(tab)){\r\n            img = resolveProFallbackImage(p) || \"\";\r\n          }\r\n\r\n          \/\/ 2) Others: try embedded featured image\r\n          if(!img){\r\n            img = resolveImageFromEmbedded(p) || \"\";\r\n          }\r\n\r\n          \/\/ 3) VILLE\/PLAGE: try direct media endpoint if available (may 401)\r\n          if(!img && (tab === \"ville_village\" || tab === \"plage\")){\r\n            const fmId = p.featured_media;\r\n            if(fmId){\r\n              jobs.push(\r\n                fetchMediaUrl(fmId).then(function(u){\r\n                  if(u && cache[id]) cache[id].img = u;\r\n                  return u;\r\n                }).catch(()=>\"\")\r\n              );\r\n            }\r\n          }\r\n\r\n          \/\/ write cache now (img may still be empty, will be patched later)\r\n          cache[id] = { id, title: title || \"Sans titre\", href, img, type: tab };\r\n\r\n          \/\/ 4) PATCH (PRODUCTION): if still no img for VILLE\/PLAGE -> extract from permalink HTML (bypass REST \/media)\r\n          if(!cache[id].img && (tab === \"ville_village\" || tab === \"plage\") && href){\r\n            jobs.push(\r\n              fetchImageFromPermalink(href).then(function(u){\r\n                if(u) cache[id].img = u;\r\n                return u;\r\n              }).catch(()=>\"\")\r\n            );\r\n          }\r\n        });\r\n\r\n        Promise.all(jobs).then(function(){\r\n          \/\/ Apr\u00e8s jobs, on peut aussi relire MEDIA_CACHE (au cas o\u00f9)\r\n          Object.keys(postById).forEach(function(id){\r\n            const p = postById[id];\r\n            const termIds = Array.isArray(p[TAX]) ? p[TAX] : [];\r\n            const firstSlug = termIds.length ? (taxMap[String(termIds[0])] || \"\") : \"\";\r\n            const tab = slugToTab(firstSlug);\r\n\r\n            if(cache[id] && (!cache[id].img) && (tab === \"ville_village\" || tab === \"plage\") && p.featured_media){\r\n              const mc = loadCache(MEDIA_CACHE_KEY);\r\n              const u = mc[String(parseInt(String(p.featured_media),10))] || \"\";\r\n              if(u) cache[id].img = u;\r\n            }\r\n          });\r\n\r\n          saveCache(CACHE_KEY, cache);\r\n\r\n          const okItems = ids.map(id => cache[id]).filter(Boolean);\r\n\r\n          okItems.forEach(function(x){\r\n            switch(x.type){\r\n              case \"plage\":       pl.push(x); break;\r\n              case \"hebergement\": hb.push(x); break;\r\n              case \"restaurant\":  rs.push(x); break;\r\n              case \"activite\":    ac.push(x); break;\r\n              case \"location\":    lo.push(x); break;\r\n              case \"lido\":        li.push(x); break;\r\n              default:            vv.push(x);\r\n            }\r\n          });\r\n\r\n          renderCards(document.getElementById(\"apulia-favs-ville_village-content\"), vv);\r\n          renderCards(document.getElementById(\"apulia-favs-plage-content\"),         pl);\r\n          renderCards(document.getElementById(\"apulia-favs-hebergement-content\"),   hb);\r\n          renderCards(document.getElementById(\"apulia-favs-restaurant-content\"),    rs);\r\n          renderCards(document.getElementById(\"apulia-favs-activite-content\"),      ac);\r\n          renderCards(document.getElementById(\"apulia-favs-location-content\"),      lo);\r\n          renderCards(document.getElementById(\"apulia-favs-lido-content\"),          li);\r\n        });\r\n      });\r\n    });\r\n  }\r\n\r\n  function resolveBackLink(){\r\n    const back = document.querySelector(\".apx-header-back\");\r\n    if(!back) return;\r\n    try{\r\n      const from = new URLSearchParams(window.location.search).get(\"from\");\r\n      if(from === \"sejour\"){\r\n        back.href = \"\/apulia-sejour\/?panel=carnet\";\r\n        back.setAttribute(\"aria-label\", \"Retour au Carnet de voyage\");\r\n      }else{\r\n        back.href = \"\/apulia-explorer\/\";\r\n        back.setAttribute(\"aria-label\", \"Retour \u00e0 Explorer\");\r\n      }\r\n    }catch(e){}\r\n  }\r\n\r\n  function init(){\r\n    resolveBackLink();\r\n    initTabsAndSwipe();\r\n    refreshPanels();\r\n    window.addEventListener(\"pageshow\", refreshPanels);\r\n  }\r\n\r\n  if(document.readyState===\"loading\") document.addEventListener(\"DOMContentLoaded\", init);\r\n  else init();\r\n\r\n})();\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\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>\u2039 Favoris Villes Plages H\u00e9bergements Restaurants Activit\u00e9s Locations Lido<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-16890","page","type-page","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>APULIA - Favorites - APULIA \u2013 Application et guide des Pouilles<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lespouilles.blog\/en\/apulia-favorites\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"APULIA - Favorites - APULIA \u2013 Application et guide des Pouilles\" \/>\n<meta property=\"og:description\" content=\"\u2039 Favoris Villes Plages H\u00e9bergements Restaurants Activit\u00e9s Locations Lido\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lespouilles.blog\/en\/apulia-favorites\/\" \/>\n<meta property=\"og:site_name\" content=\"APULIA \u2013 Application et guide des Pouilles\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/share\/1CMLv8ojyW\/\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-02T09:23:06+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/apulia-favorites\\\/\",\"url\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/apulia-favorites\\\/\",\"name\":\"APULIA - Favorites - APULIA \u2013 Application et guide des Pouilles\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/#website\"},\"datePublished\":\"2025-11-17T07:46:38+00:00\",\"dateModified\":\"2026-07-02T09:23:06+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/apulia-favorites\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lespouilles.blog\\\/en\\\/apulia-favorites\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/apulia-favorites\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"APULIA &#8211; Favorites\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/\",\"name\":\"Les Pouilles \u2013 Guide voyage. L'application Apulia pour organiser votre s\u00e9jour\",\"description\":\"APULIA est l\u2019application qui facilite votre voyage dans les Pouilles et vous accompagne avant et pendant votre s\u00e9jour. Plages, h\u00e9bergements, activit\u00e9s, m\u00e9t\u00e9o des plages, conseils locaux et s\u00e9jours personnalis\u00e9s.\",\"publisher\":{\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/#organization\",\"name\":\"APULIA\",\"url\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lespouilles.blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/logo-Apulia-transparent.png\",\"contentUrl\":\"https:\\\/\\\/lespouilles.blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/logo-Apulia-transparent.png\",\"width\":1024,\"height\":576,\"caption\":\"APULIA\"},\"image\":{\"@id\":\"https:\\\/\\\/lespouilles.blog\\\/en\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/share\\\/1CMLv8ojyW\\\/\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"APULIA - Favorites - APULIA \u2013 Application et guide des Pouilles","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/","og_locale":"en_US","og_type":"article","og_title":"APULIA - Favorites - APULIA \u2013 Application et guide des Pouilles","og_description":"\u2039 Favoris Villes Plages H\u00e9bergements Restaurants Activit\u00e9s Locations Lido","og_url":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/","og_site_name":"APULIA \u2013 Application et guide des Pouilles","article_publisher":"https:\/\/www.facebook.com\/share\/1CMLv8ojyW\/","article_modified_time":"2026-07-02T09:23:06+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/","url":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/","name":"APULIA - Favorites - APULIA \u2013 Application et guide des Pouilles","isPartOf":{"@id":"https:\/\/lespouilles.blog\/en\/#website"},"datePublished":"2025-11-17T07:46:38+00:00","dateModified":"2026-07-02T09:23:06+00:00","breadcrumb":{"@id":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lespouilles.blog\/en\/apulia-favorites\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/lespouilles.blog\/en\/apulia-favorites\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/lespouilles.blog\/en\/"},{"@type":"ListItem","position":2,"name":"APULIA &#8211; Favorites"}]},{"@type":"WebSite","@id":"https:\/\/lespouilles.blog\/en\/#website","url":"https:\/\/lespouilles.blog\/en\/","name":"Les Pouilles \u2013 Guide voyage. L'application Apulia pour organiser votre s\u00e9jour","description":"APULIA est l\u2019application qui facilite votre voyage dans les Pouilles et vous accompagne avant et pendant votre s\u00e9jour. Plages, h\u00e9bergements, activit\u00e9s, m\u00e9t\u00e9o des plages, conseils locaux et s\u00e9jours personnalis\u00e9s.","publisher":{"@id":"https:\/\/lespouilles.blog\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lespouilles.blog\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lespouilles.blog\/en\/#organization","name":"APULIA","url":"https:\/\/lespouilles.blog\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lespouilles.blog\/en\/#\/schema\/logo\/image\/","url":"https:\/\/lespouilles.blog\/wp-content\/uploads\/2026\/06\/logo-Apulia-transparent.png","contentUrl":"https:\/\/lespouilles.blog\/wp-content\/uploads\/2026\/06\/logo-Apulia-transparent.png","width":1024,"height":576,"caption":"APULIA"},"image":{"@id":"https:\/\/lespouilles.blog\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/share\/1CMLv8ojyW\/"]}]}},"_links":{"self":[{"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/pages\/16890","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/comments?post=16890"}],"version-history":[{"count":4,"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/pages\/16890\/revisions"}],"predecessor-version":[{"id":18413,"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/pages\/16890\/revisions\/18413"}],"wp:attachment":[{"href":"https:\/\/lespouilles.blog\/en\/wp-json\/wp\/v2\/media?parent=16890"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}