Naprawa lokalizacji - odczytu

This commit is contained in:
2026-06-10 20:31:48 +02:00
parent 04aaa6e321
commit 79a83d4d73
16 changed files with 1826 additions and 477 deletions

View File

@@ -71,33 +71,191 @@ body {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
padding: 24px 20px;
text-align: center;
transition: opacity 0.5s ease, visibility 0.5s;
overflow-y: auto;
}
.geo-shell {
width: 100%;
max-width: 400px;
}
.geo-icon {
font-size: 80px;
margin-bottom: 16px;
font-size: 64px;
margin-bottom: 12px;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15px); }
50% { transform: translateY(-10px); }
}
.geo-text h2 {
.geo-title {
font-family: 'Playfair Display', serif;
margin: 0 0 12px;
margin: 0 0 10px;
color: var(--primary);
font-size: 1.65rem;
line-height: 1.2;
}
.geo-lead {
color: var(--text-muted);
font-size: 15px;
line-height: 1.45;
margin: 0;
}
.geo-status {
color: var(--text-muted);
font-size: 14px;
line-height: 1.45;
margin: 14px 0 0;
min-height: 0;
}
.geo-status:empty {
display: none;
}
.geo-status.is-error {
color: #ff8a8a;
font-weight: 600;
}
.geo-status.is-info {
color: var(--primary);
}
.geo-msg {
.geo-actions {
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 10px;
margin-top: 20px;
width: 100%;
}
.geo-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
min-height: 76px;
padding: 12px 10px;
border-radius: 14px;
cursor: pointer;
font-family: inherit;
line-height: 1.25;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.geo-btn:active {
transform: scale(0.98);
}
.geo-btn-main {
font-size: 14px;
font-weight: 700;
}
.geo-btn-sub {
font-size: 11px;
font-weight: 500;
opacity: 0.85;
}
.geo-btn-menu {
background: rgba(255, 255, 255, 0.03);
border: 1.5px solid rgba(226, 176, 126, 0.25);
color: var(--text-muted);
font-size: 15px;
}
.geo-btn-menu .geo-btn-main {
color: var(--text-main);
}
.geo-btn-locate {
border: none;
box-shadow: 0 4px 18px rgba(226, 176, 126, 0.28);
}
.geo-btn-locate .geo-btn-main {
font-size: 13px;
line-height: 1.3;
}
.geo-btn-locate:disabled {
opacity: 0.65;
cursor: wait;
transform: none;
}
.geo-instructions {
margin-top: 16px;
padding: 14px 16px;
background: var(--surface-light);
border: 1px solid rgba(226, 176, 126, 0.2);
border-radius: 12px;
font-size: 13px;
line-height: 1.55;
color: #d1d5db;
text-align: left;
}
.geo-instructions.hidden {
display: none;
}
.geo-wifi-callout {
margin: 20px 0 0;
padding: 16px 18px;
background: rgba(226, 176, 126, 0.1);
border: 1.5px solid rgba(226, 176, 126, 0.35);
border-radius: 14px;
font-size: 14px;
line-height: 1.5;
margin-bottom: 16px;
color: #e8e8ea;
text-align: left;
}
.geo-wifi-callout-title {
margin: 0 0 10px;
font-size: 16px;
font-weight: 700;
color: var(--primary);
}
.geo-wifi-callout p {
margin: 0 0 8px;
}
.geo-wifi-network,
.geo-wifi-password {
font-size: 15px;
color: var(--text-main);
}
.geo-wifi-list {
margin: 8px 0 0;
padding-left: 20px;
color: #d1d5db;
}
.geo-wifi-list li {
margin-bottom: 4px;
}
.geo-wifi-list li:last-child {
margin-bottom: 0;
}
@media (max-width: 360px) {
.geo-actions {
grid-template-columns: 1fr;
}
}
/* --- MAIN LAYOUT --- */
@@ -907,6 +1065,10 @@ header {
line-height: 1.45;
}
.menu-only-banner.is-hidden {
display: none !important;
}
.menu-only-banner-btn {
flex-shrink: 0;
border: 1px solid rgba(226, 176, 126, 0.45);
@@ -944,6 +1106,10 @@ header {
margin-bottom: 10px;
}
#menuOnlyBanner.is-hidden + .menu-search-container {
padding-top: 0;
}
#menuSearchInput {
width: 100%;
background: var(--surface);

View File

@@ -7,6 +7,9 @@ window.kitchenAnimations = [
];
window.selectedAnimationHtml = null;
const MENU_ASSET_VERSION =
window.MENU_ASSET_VERSION || window.APP_ASSET_VERSION || "1";
const params = new URLSearchParams(location.search);
let hashParam = (params.get("h") || "").trim();
const isStaffPreview = params.get("preview") === "staff";
@@ -454,7 +457,10 @@ function updateNavAccessState() {
if (el) el.classList.toggle("nav-locked", locked);
});
const banner = document.getElementById("menuOnlyBanner");
if (banner) banner.classList.toggle("hidden", appAccessLevel !== "menu");
if (banner) {
banner.classList.remove("hidden");
banner.classList.toggle("is-hidden", appAccessLevel !== "menu");
}
}
function showBottomNav() {
@@ -479,46 +485,156 @@ async function resolveTableLabel() {
}
}
function isIOSDevice() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
}
const GEO_GATE_LABELS = {
status: "status zamówienia",
waiter: "wezwanie kelnera",
bill: "prośbę o rachunek",
};
const GEO_DEFAULT_LEAD =
"Przeglądaj menu od razu — albo potwierdź, że jesteś u nas, aby wezwać kelnera, śledzić zamówienie i poprosić o rachunek.";
function setGeoLead(html) {
const el = document.getElementById("geoLead");
if (el) el.innerHTML = html;
}
function setGeoStatus(html, { error = false, info = false } = {}) {
const el = document.getElementById("geoMsg");
if (!el) return;
el.innerHTML = html || "";
el.classList.toggle("is-error", error);
el.classList.toggle("is-info", info);
}
function showGeoInstructions(html) {
const el = document.getElementById("geoInstructions");
if (!el) return;
el.innerHTML = html || "";
el.classList.toggle("hidden", !html);
}
function hideGeoInstructions() {
showGeoInstructions("");
}
function setGeoActionBusy(busy) {
const btn = document.getElementById("geoActionBtn");
if (!btn) return;
btn.disabled = false;
btn.setAttribute("aria-busy", busy ? "true" : "false");
if (busy) {
setGeoActionLabel("Sprawdzanie…");
}
}
function isGeoPermissionDenied(error) {
return Number(error?.code) === 1;
}
function setGeoActionLabel(text) {
const btn = document.getElementById("geoActionBtn");
if (!btn) return;
const main = btn.querySelector(".geo-btn-main");
if (main) main.textContent = text;
else btn.textContent = text;
}
function getGeoPermissionInstructions() {
if (isIOSDevice()) {
return `<b>iPhone (Safari):</b><br>
1. Kliknij <b>aA</b> po lewej stronie paska adresu.<br>
2. Wybierz <b>Ustawienia witryny</b>.<br>
3. Ustaw <b>Położenie</b> na „Zapytaj” lub „Pozwalaj”.<br>
4. Odśwież stronę.<br><br>
<i>Lokalizacja działa tylko przez bezpieczne <b>https://</b>.</i>`;
}
return `<b>Android / Chrome:</b><br>
1. Kliknij ikonę <b>kłódki</b> obok adresu strony.<br>
2. W <b>Uprawnieniach</b> zmień Lokalizację na „Zezwalaj”.<br>
3. Odśwież stronę.`;
}
let geoMenuButtonMode = "menu_only";
window.handleGeoMenuClick = function () {
if (geoMenuButtonMode === "back_to_menu") {
document.getElementById("geoScreen")?.classList.add("hidden");
return;
}
enterMenuOnlyMode();
};
window.retryGeolocation = function () {
if (shouldBypassGeolocationHost()) {
bypassGeolocation("trusted_host", { host: window.location.hostname });
return;
}
initGeolocationAfterBypassChecks({ userInitiated: true }).catch((err) => {
console.error("[GEO] retry failed", err);
showGeoPermissionBlockedState();
});
};
function bindGeoScreenButtons() {
document.getElementById("geoMenuOnlyBtn")?.addEventListener("click", (event) => {
event.preventDefault();
handleGeoMenuClick();
});
document.getElementById("geoActionBtn")?.addEventListener("click", (event) => {
event.preventDefault();
retryGeolocation();
});
}
function configureGeoSecondaryButton(mode) {
const menuOnlyBtn = document.getElementById("geoMenuOnlyBtn");
if (!menuOnlyBtn) return;
geoMenuButtonMode = mode;
const mainEl = menuOnlyBtn.querySelector(".geo-btn-main");
const subEl = menuOnlyBtn.querySelector(".geo-btn-sub");
if (mode === "back_to_menu") {
menuOnlyBtn.style.display = "";
menuOnlyBtn.textContent = "Wróć do menu";
menuOnlyBtn.onclick = () => {
document.getElementById("geoScreen")?.classList.add("hidden");
};
if (mainEl) mainEl.textContent = "Wróć do menu";
if (subEl) {
subEl.textContent = "";
subEl.style.display = "none";
}
return;
}
menuOnlyBtn.style.display = "";
menuOnlyBtn.textContent = "Przeglądaj menu bez lokalizacji";
menuOnlyBtn.onclick = () => enterMenuOnlyMode();
if (mainEl) mainEl.textContent = "Przejdź do menu";
if (subEl) {
subEl.textContent = "bez lokalizacji";
subEl.style.display = "";
}
}
function showGeoGateForAction(action) {
const geoScreen = document.getElementById("geoScreen");
const loadingScreen = document.getElementById("loadingScreen");
const geoMsg = document.getElementById("geoMsg");
const geoActionBtn = document.getElementById("geoActionBtn");
if (loadingScreen) loadingScreen.classList.add("hidden");
if (geoScreen) geoScreen.classList.remove("hidden");
const feature = GEO_GATE_LABELS[action] || "tę funkcję";
if (geoMsg) {
geoMsg.innerHTML = `Aby skorzystać z <b>${feature}</b>, potwierdź, że jesteś w restauracji.<br><br>Prosimy o zgodę na dostęp do lokalizacji.`;
}
setGeoLead(`Menu masz już otwarte. Aby skorzystać z <b>${feature}</b>, potwierdź krótko, że jesteś w restauracji.`);
setGeoStatus("");
hideGeoInstructions();
if (geoActionBtn) {
geoActionBtn.disabled = false;
geoActionBtn.textContent = "Sprawdź lokalizację";
setGeoActionBusy(false);
setGeoActionLabel("Sprawdź lokalizację");
}
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
}
@@ -1322,7 +1438,7 @@ window.fetchGUS = async function () {
// --- DYNAMIC MENU LOADING ---
async function loadMenu() {
try {
const response = await fetch('menu.json');
const response = await fetch(`menu.json?v=${encodeURIComponent(MENU_ASSET_VERSION)}`);
if (!response.ok) throw new Error('Nie udało się załadować menu');
const menuData = await response.json();
window.menuDataRaw = menuData;
@@ -1467,9 +1583,13 @@ window.confirmInvoice = async function () {
};
// --- GEOLOCATION LOGIC ---
const RESTAURANT_LAT = 50.5624963;
const RESTAURANT_LNG = 22.0608059;
const MAX_DISTANCE_METERS = 200;
// Dwa punkty odniesienia: OSM (adres budynku) i pin Google Maps (z nim porównują goście w Maps).
const RESTAURANT_LOCATIONS = [
{ lat: 50.5622609, lng: 22.0606303, source: "osm" },
{ lat: 50.567953, lng: 22.061045, source: "google_maps" },
];
const MAX_DISTANCE_METERS = 300;
const MAX_ACCURACY_BONUS = 150;
function haversineDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3;
@@ -1485,6 +1605,76 @@ function haversineDistance(lat1, lon1, lat2, lon2) {
return R * c;
}
function distanceToRestaurant(lat, lng) {
return Math.min(
...RESTAURANT_LOCATIONS.map(({ lat: rLat, lng: rLng }) =>
haversineDistance(rLat, rLng, lat, lng)
)
);
}
function isInsideRestaurantGeofence(distanceMeters, accuracyMeters) {
const accuracyBonus = Math.min(Math.max(Number(accuracyMeters) || 0, 0), MAX_ACCURACY_BONUS);
return distanceMeters <= MAX_DISTANCE_METERS + accuracyBonus;
}
function requestRestaurantGeolocation() {
const geoOptions = {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0,
};
return new Promise((resolve, reject) => {
let bestSample = null;
let watchId = null;
let settled = false;
const finish = (result) => {
if (settled) return;
settled = true;
if (watchId != null) navigator.geolocation.clearWatch(watchId);
clearTimeout(timeoutId);
resolve(result);
};
const fail = (error) => {
if (settled) return;
settled = true;
if (watchId != null) navigator.geolocation.clearWatch(watchId);
clearTimeout(timeoutId);
reject(error);
};
const handlePosition = (position) => {
const dist = distanceToRestaurant(position.coords.latitude, position.coords.longitude);
const accuracy = position.coords.accuracy;
const sample = { position, dist, accuracy };
if (!bestSample || dist < bestSample.dist) {
bestSample = sample;
}
if (isInsideRestaurantGeofence(dist, accuracy)) {
finish({ passed: true, ...sample });
}
};
const timeoutId = setTimeout(() => {
if (bestSample) {
finish({
passed: isInsideRestaurantGeofence(bestSample.dist, bestSample.accuracy),
...bestSample,
});
return;
}
fail({ code: 3, message: "Geolocation timeout" });
}, 12000);
watchId = navigator.geolocation.watchPosition(handlePosition, fail, geoOptions);
});
}
function startApp() {
appAccessLevel = "full";
updateNavAccessState();
@@ -1512,28 +1702,30 @@ function startApp() {
function showGeoConsentScreen() {
const geoScreen = document.getElementById("geoScreen");
const loadingScreen = document.getElementById("loadingScreen");
const geoMsg = document.getElementById("geoMsg");
const geoActionBtn = document.getElementById("geoActionBtn");
const menuOnlyBtn = document.getElementById("geoMenuOnlyBtn");
loadingScreen.classList.add("hidden");
geoScreen.classList.remove("hidden");
if (geoMsg) {
geoMsg.innerHTML = `Aby zapewnić bezpieczeństwo Twojego zamówienia, musimy upewnić się, że znajdujesz się na terenie restauracji.<br><br>Prosimy o udzielenie zgody na dostęp do lokalizacji w przeglądarce.`;
}
setGeoLead(GEO_DEFAULT_LEAD);
setGeoStatus("");
hideGeoInstructions();
if (geoActionBtn) {
geoActionBtn.disabled = false;
geoActionBtn.textContent = "Udziel zgody / Sprawdź";
}
if (menuOnlyBtn) {
configureGeoSecondaryButton("menu_only");
setGeoActionBusy(false);
setGeoActionLabel("Zgoda, sprawdź lokalizację");
}
configureGeoSecondaryButton("menu_only");
}
function isIOSDevice() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
function showGeoPermissionBlockedState() {
setGeoStatus("Przeglądarka zablokowała dostęp do lokalizacji.", { error: true });
showGeoInstructions(
`${getGeoPermissionInstructions()}<br><br>Po zmianie ustawień <b>odśwież stronę</b>, a potem kliknij „Spróbuj ponownie”.`
);
setGeoActionBusy(false);
setGeoActionLabel("Spróbuj ponownie");
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
}
function shouldBypassGeolocationHost() {
@@ -1541,135 +1733,189 @@ function shouldBypassGeolocationHost() {
return bypassHosts.includes(window.location.hostname);
}
window.initGeolocation = function () {
if (shouldBypassGeolocationHost()) {
console.warn("Bypassing geolocation for trusted host.");
trackEvent("geo_bypass_host", { host: window.location.hostname, reason: "trusted_host" });
async function checkGeoBypassByClientIp() {
try {
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
const timeoutId = controller
? setTimeout(() => controller.abort(), 4000)
: null;
const res = await fetch("../api/geo_bypass.php", {
credentials: "same-origin",
cache: "no-store",
signal: controller?.signal,
});
if (timeoutId) clearTimeout(timeoutId);
const data = await res.json();
return data.status === "success" && data.bypassGeo === true;
} catch {
return false;
}
}
function bypassGeolocation(reason, extra = {}) {
trackEvent('geo_bypass_host', { reason, ...extra });
if (appAccessLevel === 'menu') {
unlockFullApp();
} else {
startApp();
}
}
async function queryGeolocationPermissionState() {
if (!navigator.permissions?.query) {
return "unknown";
}
try {
const status = await navigator.permissions.query({ name: "geolocation" });
return status.state;
} catch {
return "unknown";
}
}
async function bootstrapGeolocation() {
if (shouldBypassGeolocationHost()) {
bypassGeolocation('trusted_host', { host: window.location.hostname });
return;
}
if (await checkGeoBypassByClientIp()) {
bypassGeolocation('trusted_ip');
return;
}
showGeoConsentScreen();
}
window.initGeolocation = function () {
if (shouldBypassGeolocationHost()) {
console.warn("Bypassing geolocation for trusted host.");
bypassGeolocation("trusted_host", { host: window.location.hostname });
return;
}
checkGeoBypassByClientIp().then((bypassByIp) => {
if (bypassByIp) {
console.warn("Bypassing geolocation for trusted client IP.");
bypassGeolocation("trusted_ip");
return;
}
initGeolocationAfterBypassChecks();
});
};
async function initGeolocationAfterBypassChecks(options = {}) {
const geoScreen = document.getElementById("geoScreen");
const loadingScreen = document.getElementById("loadingScreen");
const geoMsg = document.getElementById("geoMsg");
const geoActionBtn = document.getElementById("geoActionBtn");
const menuOnlyBtn = document.getElementById("geoMenuOnlyBtn");
const bypassHosts = ['localhost', '127.0.0.1', '192.168.20.84'];
if (window.location.protocol === 'http:' && bypassHosts.includes(window.location.hostname)) {
console.warn("Bypassing geolocation on local HTTP environment.");
trackEvent("geo_bypass_host", { host: window.location.hostname, reason: "local_http" });
unlockFullApp();
bypassGeolocation("local_http", { host: window.location.hostname });
return;
}
if (!window.isSecureContext) {
geoMsg.innerHTML = `<b style="color: #ff6b6b;">Ta strona nie jest uruchomiona w bezpiecznym trybie HTTPS.</b><br><br>
Przeglądarki mobilne blokują geolokalizację bez pytania, jeśli adres nie zaczyna się od <b>https://</b>.<br><br>
Otwórz aplikację przez HTTPS i spróbuj ponownie.<br><br>
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
Hasło: <b>karczmabiesiada</b>`;
if (geoActionBtn) {
geoActionBtn.disabled = false;
geoActionBtn.textContent = "Spróbuj ponownie";
}
if (menuOnlyBtn) configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
return;
}
loadingScreen.classList.add("hidden");
geoScreen.classList.remove("hidden");
loadingScreen?.classList.add("hidden");
geoScreen?.classList.remove("hidden");
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
if (!navigator.geolocation) {
geoMsg.innerHTML = "Twoja przeglądarka nie wspiera geolokalizacji. Aplikacja wymaga nowszej przeglądarki.";
if (!window.isSecureContext) {
setGeoLead(GEO_DEFAULT_LEAD);
setGeoStatus("Ta strona wymaga bezpiecznego połączenia HTTPS.", { error: true });
showGeoInstructions("Przeglądarki mobilne blokują geolokalizację bez <b>https://</b>. Otwórz aplikację przez HTTPS i spróbuj ponownie.");
setGeoActionBusy(false);
setGeoActionLabel("Spróbuj ponownie");
return;
}
geoMsg.innerHTML = "Sprawdzamy Twoją lokalizację...";
trackEvent("geo_check_started");
if (geoActionBtn) {
geoActionBtn.disabled = true;
geoActionBtn.textContent = "Sprawdzanie...";
if (!navigator.geolocation) {
setGeoLead(GEO_DEFAULT_LEAD);
setGeoStatus("Twoja przeglądarka nie wspiera geolokalizacji.", { error: true });
hideGeoInstructions();
setGeoActionBusy(false);
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
const dist = haversineDistance(
RESTAURANT_LAT, RESTAURANT_LNG,
position.coords.latitude, position.coords.longitude
);
setGeoLead(GEO_DEFAULT_LEAD);
setGeoStatus("Sprawdzamy Twoją lokalizację…", { info: true });
hideGeoInstructions();
setGeoActionBusy(true);
const accuracy = position.coords.accuracy;
console.log(`[GEO] Lat: ${position.coords.latitude}, Lng: ${position.coords.longitude}, Dist: ${dist}m, Accuracy: ${accuracy}m`);
const permissionState = await queryGeolocationPermissionState();
if (permissionState === "denied") {
showGeoPermissionBlockedState();
return;
}
if (dist <= MAX_DISTANCE_METERS) {
trackEvent("geo_check_passed", { distanceMeters: Math.round(dist), accuracyMeters: Math.round(accuracy) });
unlockFullApp();
// setTimeout(() => showToast(`Lokalizacja zweryfikowana (Dystans: ${Math.round(dist)}m, Dokładność: ${Math.round(accuracy)}m)`), 2000);
} else {
trackEvent("geo_check_failed", { reason: "outside_restaurant", distanceMeters: Math.round(dist), accuracyMeters: Math.round(accuracy) });
if (geoActionBtn) {
geoActionBtn.disabled = false;
geoActionBtn.textContent = "Spróbuj ponownie";
}
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
geoMsg.innerHTML = `Wydaje się, że jesteś poza restauracją (ok. ${Math.round(dist)}m od nas).<br>Nasza aplikacja działa tylko na miejscu.<br><br>
<small style="color: #888;">Debug: Twoja odległość: ${Math.round(dist)}m, Dokładność sygnału: ${Math.round(accuracy)}m</small><br><br>
Jeśli to błąd GPS lub słaby sygnał, spróbuj ponownie za chwilę.`;
}
},
(error) => {
trackEvent("geo_check_failed", { reason: "browser_error", code: error.code || null, message: String(error.message || "") });
if (geoActionBtn) {
geoActionBtn.disabled = false;
geoActionBtn.textContent = "Spróbuj ponownie";
}
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
const deniedBecauseInsecure = /secure origins|only secure|https/i.test(String(error.message || ""));
if (deniedBecauseInsecure) {
geoMsg.innerHTML = `<b style="color: #ff6b6b;">Przeglądarka zablokowała lokalizację z powodu braku HTTPS.</b><br><br>
Geolokalizacja działa tylko na bezpiecznym adresie <b>https://</b> (lub localhost).<br>
Otwórz aplikację przez HTTPS i spróbuj ponownie.<br><br>
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
Hasło: <b>karczmabiesiada</b>`;
} else if (error.code === error.PERMISSION_DENIED) {
const isIOS = isIOSDevice();
let instructions = '';
if (isIOS) {
instructions = `<b>Instrukcja dla iPhone (Safari):</b><br>
1. Kliknij ikonę <b>"aA"</b> po lewej stronie paska adresu.<br>
2. Wybierz <b>"Ustawienia witryny"</b> (Website Settings).<br>
3. Zmień opcję <b>"Położenie"</b> (Location) na "Zapytaj" lub "Pozwalaj".<br>
4. Odśwież stronę.<br><br>
<i>Uwaga: Na urządzeniach Apple lokalizacja działa WYŁĄCZNIE, gdy adres strony zaczyna się od bezpiecznego <b>https://</b>. Jeżeli jesteś na http://, system zablokuje to automatycznie.</i><br><br>
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
Hasło: <b>karczmabiesiada</b>`;
} else {
instructions = `<b>Instrukcja dla Android / Chrome:</b><br>
1. Kliknij ikonkę <b>kłódki / ustawień</b> 🔒 obok adresu strony na górze przeglądarki.<br>
2. Znajdź <b>Uprawnienia</b> (Lokalizacja) i zmień z "Zablokuj" na "Zezwalaj".<br>
3. Odśwież stronę.<br><br>
<b>Masz problem z lokalizacją?</b> Połącz się z <b>HotSpot Karczmy</b>, a wtedy wejdziesz do aplikacji bez geolokalizacji.<br>
Hasło: <b>karczmabiesiada</b>`;
}
trackEvent("geo_check_started");
geoMsg.innerHTML = `<b style="color: #ff6b6b;">Przeglądarka zablokowała dostęp do lokalizacji.</b><br><br>
Bez tego nie możemy zweryfikować, czy jesteś w restauracji.<br><br>
${instructions}`;
} else {
geoMsg.innerHTML = "Nie udało się pobrać lokalizacji. Sprawdź zasięg lub włącz GPS i spróbuj ponownie.";
}
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
);
try {
const result = await requestRestaurantGeolocation();
const dist = result.dist;
const accuracy = result.accuracy;
console.log(
`[GEO] Lat: ${result.position.coords.latitude}, Lng: ${result.position.coords.longitude}, ` +
`MinDist: ${Math.round(dist)}m, Accuracy: ${Math.round(accuracy)}m`
);
if (result.passed) {
trackEvent("geo_check_passed", {
distanceMeters: Math.round(dist),
accuracyMeters: Math.round(accuracy),
});
unlockFullApp();
return;
}
trackEvent("geo_check_failed", {
reason: "outside_restaurant",
distanceMeters: Math.round(dist),
accuracyMeters: Math.round(accuracy),
});
setGeoActionBusy(false);
setGeoActionLabel("Spróbuj ponownie");
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
setGeoStatus(
`Wygląda na to, że jesteś poza restauracją (ok. ${Math.round(dist)} m, dokładność GPS: ±${Math.round(accuracy)} m).`,
{ error: true }
);
showGeoInstructions(
"Przeglądarka często podaje inną lokalizację niż aplikacja Map Google. " +
"Spróbuj ponownie na zewnątrz lub bliżej okna — albo przejdź do menu bez lokalizacji."
);
} catch (error) {
trackEvent("geo_check_failed", {
reason: "browser_error",
code: error.code || null,
message: String(error.message || ""),
});
setGeoActionBusy(false);
setGeoActionLabel("Spróbuj ponownie");
configureGeoSecondaryButton(appAccessLevel === "menu" ? "back_to_menu" : "menu_only");
const deniedBecauseInsecure = /secure origins|only secure|https/i.test(String(error.message || ""));
if (deniedBecauseInsecure) {
setGeoStatus("Geolokalizacja wymaga HTTPS.", { error: true });
showGeoInstructions("Otwórz aplikację przez bezpieczny adres <b>https://</b> i spróbuj ponownie.");
} else if (isGeoPermissionDenied(error)) {
showGeoPermissionBlockedState();
} else {
setGeoStatus("Nie udało się pobrać lokalizacji.", { error: true });
showGeoInstructions("Sprawdź zasięg, włącz GPS i spróbuj ponownie.");
}
}
};
bindGeoScreenButtons();
if (shouldBypassGeolocationHost()) {
startApp();
} else if (isIOSDevice()) {
showGeoConsentScreen();
bypassGeolocation("trusted_host", { host: window.location.hostname });
} else {
initGeolocation();
bootstrapGeolocation();
}