<!DOCTYPE html>
<html lang="{% if languagecode is defined %}{{ languagecode.locale|default((app.request.getLocale()|default)) }}{% else %}{{ app.request.getLocale()|default }}{% endif %}">
<head>
{% block head -%}
{%- include 'partials\\head.html.twig' -%}
{%- endblock %}
</head>
<body{% block body_class %}{%- endblock %}>
{{ get_config('scripts_body',app.request.getLocale())|default|raw }}
{{ scriptsbody|default|raw }}
{% if is_authenticated_in_studio() %}
{% include 'admin\\adminbar.html.twig' %}
{% endif %}
{% block body %}
<div class="wrap">
{% block header %}
{% include 'partials\\variants\\' ~ get_config('header_style','') ~ '.html.twig' %}
{% endblock %}
{% if preview|default %}
<div class="mt-3 mx-4 alert alert-danger">
{{ 'is_preview'|trans({},'pages') }}
</div>
{% endif %}
{% block breadcrumb %}
{% include 'partials\\breadcrumb.html.twig' %}
{% endblock %}
<main>
{% block content %}
{{ blockscontent|default|raw }}
{% endblock %}
</main>
{% block footer %}
{% block pub_standard %}
{% if hide_pub is not defined or hide_pub == false %}
{% if is_ad_zone_active('standard') %}
{% include 'partials/ads-banners/standard.html.twig' %}
{% endif %}
{% endif %}
{% endblock %}
{% block pubs %}
{% if is_ad_zone_active('layer') %}
{% include 'partials/ads-banners/layer.html.twig' %}
{% endif %}
{% if is_ad_zone_active('xtrad') %}
{% include 'partials/ads-banners/xtrad.html.twig' %}
{% endif %}
{% if is_mobile() and is_ad_zone_active('splash') %}
{% include 'partials/ads-banners/splash.html.twig' %}
{% endif %}
{% endblock %}
{% include 'partials\\variants\\' ~ get_config('footer_style','') ~ '.html.twig' %}
{% if get_config('mobilebottombar',locale)|default %}
{% include 'partials\\elements\\mobile-bar.twig' %}
{% endif %}
{% endblock %}
</div>
{% endblock %}
{% block cookies %}
{% if get_config('cookies_pages', '')|default
and (
(get_env('COOKIE_DATABASE_ENABLED') and studio.getCookie() is null)
or
(not get_env('COOKIE_DATABASE_ENABLED') and app.request.cookies.get(get_env('COOKIE_STD_NAME'),'') == '')
) %}
{% set cookiepage=get_page_info(app.request.locale,get_config('cookies_pages','')) %}
<div id="cookies-modal" data-url="{{ cookiepage.url|default }}"
data-accept="{{ path('std_cookies_accept') }}"></div>
{% endif %}
{% endblock %}
{% block modalFlashes %}
{% set flashessuccess=app.flashes('success') %}
{% set flasheserror=app.flashes('error') %}
<div class="modal fade" id="modalMessages" tabindex="-1">
<div class="modal-dialog modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body">
<button type="button" class="close-modal ml-auto" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<div class="js-modal-messages-content">
{% for message in flashessuccess %}
{{ message|html_entity_decode|raw }}
{% endfor %}
{% for message in flasheserror %}
{{ message|html_entity_decode|raw }}
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{# ============================
Publicidade – Provider Loader
============================ #}
{% set adsProvider = get_config('ads_provider', '')|default('none') %}
{% set adsScripts = get_config('ads_scripts', '')|default('') %}
{% if adsProvider == 'studio' %}
<script src="/build/assets/js/ads-banners.js"></script>
{%elseif adsScripts is defined and adsScripts != '' and adsProvider!='none' %}
{{ adsScripts|raw }}
{% endif %}
{% include 'partials\\scripts.html.twig' %}
{% set webpackEntries=app.request.attributes.get('webpackEntries')|default([]) %}
{% if get_config('notificationsbar_page', '')|default %}
{% set webpackEntries=webpackEntries|merge(['B31_notificationsbar']) %}
{% endif %}
{% set webpackEntries=webpackEntries|merge(['slick-carousel']) %}
{% if get_config('newsletterblock_page', '')|default %}
{% set webpackEntries=webpackEntries|merge(['validation_messages_' ~ locale]) %}
{% set webpackEntries=webpackEntries|merge(['B32_newsletter']) %}
{% endif %}
{% if webpackEntries is defined %}
{% for entry in webpackEntries %}
{{ encore_entry_script_tags(entry) }}
{% endfor %}
{% endif %}
{% endblock %}
<script type="text/javascript">
{% block javascripts %}
{{ blockscontentjs|raw }}
{% if scripts_tracking_precise is defined and scripts_tracking_precise is same as(true) %}
document.addEventListener("DOMContentLoaded", function () {
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Calculate distance between two coordinates in km (Haversine formula)
function haversineDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // Earth's radius in km
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
const LOCATION_VERIFY_INTERVAL = 10 * 60 * 1000;
let locationVerifyTimer = null;
function verifyLocationWithBackend() {
if (!("geolocation" in navigator)) return;
const lastLat = localStorage.getItem('lastLocationLat');
const lastLon = localStorage.getItem('lastLocationLon');
if (!lastLat || !lastLon) return;
navigator.geolocation.getCurrentPosition(
function(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
{% if use_geo_map_tracking is defined and use_geo_map_tracking is same as(true) %}
fetch(`/geo-location/verify?lon=${longitude}&lat=${latitude}`)
.then(res => res.json())
.then(data => {
if (data.needs_update) {
fetch(`/geo-location/accept?lon=${longitude}&lat=${latitude}&distance=500`)
.then(res => res.json())
.then(updateData => {
if (!updateData.error) {
localStorage.setItem('lastLocationLat', latitude.toString());
localStorage.setItem('lastLocationLon', longitude.toString());
localStorage.setItem('lastLocationVerify', Date.now().toString());
location.reload();
}
})
.catch(err => console.error('[Location Verify] Update error:', err));
} else {
localStorage.setItem('lastLocationVerify', Date.now().toString());
}
})
.catch(() => {});
{% endif %}
},
function() {},
{ enableHighAccuracy: false, timeout: 15000, maximumAge: 60000 }
);
}
function startLocationVerification() {
if (locationVerifyTimer) clearInterval(locationVerifyTimer);
locationVerifyTimer = setInterval(verifyLocationWithBackend, LOCATION_VERIFY_INTERVAL);
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
const lastVerify = parseInt(localStorage.getItem('lastLocationVerify') || '0');
if (Date.now() - lastVerify > 5 * 60 * 1000) verifyLocationWithBackend();
}
});
}
if (localStorage.getItem('lastLocationLat') && localStorage.getItem('lastLocationLon')) {
startLocationVerification();
}
function updateLocationSilently() {
if (!("geolocation" in navigator)) return;
navigator.geolocation.getCurrentPosition(
function (position) {
const longitude = position.coords.longitude;
const latitude = position.coords.latitude;
const distance = 500;
const lastLat = parseFloat(localStorage.getItem('lastLocationLat'));
const lastLon = parseFloat(localStorage.getItem('lastLocationLon'));
if (!isNaN(lastLat) && !isNaN(lastLon)) {
const distanceMoved = haversineDistance(lastLat, lastLon, latitude, longitude);
if (distanceMoved <= 1) return; // Haven't moved much, skip update
}
{% if use_geo_map_tracking is defined and use_geo_map_tracking is same as(true) %}
fetch(`/geo-location/accept?lon=${longitude}&lat=${latitude}&distance=${distance}`)
.then(res => res.json())
.then(data => {
if (!data.error) {
localStorage.setItem('lastLocationLat', latitude.toString());
localStorage.setItem('lastLocationLon', longitude.toString());
localStorage.setItem('lastLocationVerify', Date.now().toString());
localStorage.removeItem('locationReloaded');
}
})
.catch(() => {});
{% endif %}
},
function () {},
{enableHighAccuracy: true, timeout: 10000, maximumAge: 0}
);
}
// Removed duplicate visibilitychange listener - now handled by startLocationVerification()
let cookieConsentGiven = false;
let locationAsked = false;
function askLocationOnce() {
if (locationAsked) return;
if (localStorage.getItem('locationReloaded')) return;
locationAsked = true;
cookieConsentGiven = true;
clearInterval(intervalId);
tryAskLocation();
}
function checkForExistingConsent() {
// Check localStorage for AdOpt consent (most reliable method)
const adoptConsent = localStorage.getItem('adopt-consent');
if (adoptConsent) {
askLocationOnce();
return true;
}
// Check if banner is not present (consent already given on previous visit)
const banner = document.getElementById('cookie-banner');
if (!banner) {
// Wait a bit for banner to potentially load
setTimeout(function() {
const bannerCheck = document.getElementById('cookie-banner');
if (!bannerCheck) {
askLocationOnce();
}
}, 2000);
}
return false;
}
function setupCookieBannerListeners() {
// Listen for AdOpt custom events
window.addEventListener('adopt:consent', function() {
askLocationOnce();
});
// Listen for localStorage changes (AdOpt stores consent there)
window.addEventListener('storage', function(e) {
if (e.key === 'adopt-consent' && e.newValue) {
askLocationOnce();
}
});
document.addEventListener('click', function(e) {
const target = e.target;
if (target.id === 'adopt-accept-all-button' ||
target.id === 'adopt-reject-all-button' ||
target.closest('#adopt-accept-all-button') ||
target.closest('#adopt-reject-all-button')) {
// Small delay to let AdOpt process
setTimeout(askLocationOnce, 500);
}
});
const observeBody = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.removedNodes.forEach(function(node) {
if (node.id === 'cookie-banner' || (node.querySelector && node.querySelector('#cookie-banner'))) {
setTimeout(askLocationOnce, 300);
observeBody.disconnect();
}
});
});
});
observeBody.observe(document.body, { childList: true, subtree: true });
}
function tryAskLocation() {
// if (localStorage.getItem('locationReloaded')) {
// return;
// }
//{#const analyticsCookie = getCookie('{{ get_env("COOKIE_STD_ANALYTICS_NAME") }}');#}
// if (analyticsCookie === 'accepted') {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
function (position) {
const longitude = position.coords.longitude;
const latitude = position.coords.latitude;
const distance = 500;
// Check if user has moved significantly since last location update
const lastLat = parseFloat(localStorage.getItem('lastLocationLat'));
const lastLon = parseFloat(localStorage.getItem('lastLocationLon'));
const locationReloaded = localStorage.getItem('locationReloaded');
let shouldUpdate = !locationReloaded;
// If we have previous coordinates, check if user moved > 1km
if (locationReloaded && !isNaN(lastLat) && !isNaN(lastLon)) {
const distanceMoved = haversineDistance(lastLat, lastLon, latitude, longitude);
if (distanceMoved > 1) { // More than 1km
shouldUpdate = true;
localStorage.removeItem('locationReloaded');
}
}
{% if use_api_tracking is defined and use_api_tracking is same as(true) %}
fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=pt-PT`)
.then(res => res.json())
.then(data => {
const country = data.countryName;
const principalSubdivision = data.principalSubdivision;
const city = data.city;
const locality = data.locality;
localStorage.setItem('locations', country || '');
localStorage.setItem('municipality', principalSubdivision || '');
localStorage.setItem('city', city || '');
localStorage.setItem('locality', locality || '');
fetch('/ip-geo-location/accept', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({country, city})
});
});
{% endif %}
{% if use_geo_map_tracking is defined and use_geo_map_tracking is same as(true) %}
fetch(`/geo-location/accept?lon=${longitude}&lat=${latitude}&distance=${distance}`)
.then(res => res.json())
.then(data => {
if (!data.error) {
// Store current coordinates for future comparison
localStorage.setItem('lastLocationLat', latitude.toString());
localStorage.setItem('lastLocationLon', longitude.toString());
localStorage.setItem('lastLocationVerify', Date.now().toString());
// Start periodic location verification
startLocationVerification();
if (shouldUpdate) {
localStorage.setItem('locationReloaded', 'true');
location.reload();
}
} else {
console.error('Erro:', data.error);
}
})
.catch(err => {
console.error('Erro na requisição:', err);
});
{% endif %}
},
function (error) {
if (error.code === error.PERMISSION_DENIED) {
localStorage.removeItem('locationReloaded');
localStorage.removeItem('lastLocationLat');
localStorage.removeItem('lastLocationLon');
}
console.error("Error getting location:", error.message);
},
{enableHighAccuracy: true, timeout: 120000, maximumAge: 0}
);
} else {
console.log("Geolocation is not supported by this browser.");
}
// } else if (analyticsCookie !== undefined && analyticsCookie !== '' && analyticsCookie !== 'accepted') {
// clearInterval(intervalId);
// }
}
// Setup listeners for cookie consent
setupCookieBannerListeners();
const intervalId = setInterval(function() {
if (checkForExistingConsent()) {
clearInterval(intervalId);
}
}, 1000);
// Initial check
checkForExistingConsent();
});
{% endif %}
{% endblock %}
</script>
{{ get_config('scripts_footer',app.request.getLocale())|default|raw }}
{{ scriptsfooter|default|raw }}
</body>
</html>