-
+
+
+
+
+
+
+
+ In lista d'attesa
+
+ format("d");
+ $wMonth = $italianMonths[$wDateObj->format("F")];
+ $wWhen = $wDateObj->format("d-m-Y H:i");
+ $wColor = $wrow['colorclass'] ?? '#1ebf73';
+ $wName = $wrow['servicename'] ?? '';
+ $wPos = $waitingPositions[(int) $wrow['idbookingclass']] ?? 0;
+ ?>
+
+
+
+
+
+ In coda
+
+
Quando:
+
+
Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga
+ 0) : ?>
+
+ La tua posizione in coda:
+
+
+
+ Se si libera un posto verrai avvisata/o. Non serve fare nulla.
+
+
+
+ Esci dalla lista d'attesa
+
+
+
+
+
+
+
+
+
+
+
@@ -694,6 +858,7 @@ $italianMonths = [
Quando:
+
Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga
@@ -741,6 +906,23 @@ $italianMonths = [
const outlookCalendarLink = `webcal://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&subject=${encodeURIComponent(eventName)}&startdt=${encodeURIComponent(eventDate)}`;
window.open(outlookCalendarLink, '_blank');
}
+
+ function confirmLeaveWaiting(id) {
+ Swal.fire({
+ title: "Uscire dalla lista d'attesa?",
+ text: "Verrai rimossa/o dalla coda per questa lezione.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#d1698a",
+ cancelButtonColor: "#3085d6",
+ confirmButtonText: "Sì, esci",
+ cancelButtonText: "Annulla"
+ }).then((result) => {
+ if (result.isConfirmed) {
+ window.location.href = "leavewaiting.php?id=" + id;
+ }
+ });
+ }
diff --git a/wp plugin/yogasoul-eventi.zip b/wp plugin/yogasoul-eventi.zip
new file mode 100644
index 0000000..e5861a3
Binary files /dev/null and b/wp plugin/yogasoul-eventi.zip differ
diff --git a/wp plugin/yogasoul-eventi/assets/yogasoul-eventi.css b/wp plugin/yogasoul-eventi/assets/yogasoul-eventi.css
new file mode 100644
index 0000000..3b3d250
--- /dev/null
+++ b/wp plugin/yogasoul-eventi/assets/yogasoul-eventi.css
@@ -0,0 +1,153 @@
+/* YogaSoul Eventi — stili widget.
+ Tutti i selettori sono prefissati con .yse-widget per alzare la specificità
+ sopra le regole del tema WordPress (evita che tema "vinca" su colori/font). */
+
+.yse-widget {
+ --yse-ink: #2b2622;
+ --yse-muted: #8c837c;
+ --yse-line: #ece7e2;
+ --yse-card: #ffffff;
+ max-width: 1140px; /* largo come Amelia; su schermi piccoli riempie il contenitore */
+ width: 100%;
+ margin: 0 auto;
+ font-family: inherit;
+ color: var(--yse-ink);
+ line-height: 1.55;
+ text-align: left;
+}
+.yse-widget, .yse-widget * { box-sizing: border-box; }
+
+/* --- Reset mirati contro il tema --- */
+.yse-widget button {
+ font-family: inherit;
+ background: none !important;
+ border: none !important;
+ box-shadow: none !important;
+ text-transform: none !important;
+ letter-spacing: normal !important;
+ min-height: 0 !important;
+ width: auto;
+ margin: 0;
+ line-height: 1.2;
+}
+.yse-widget button:hover { filter: none; }
+.yse-widget input {
+ font-family: inherit;
+ margin: 0;
+ width: 100%;
+ min-height: 0;
+ box-shadow: none;
+}
+.yse-widget h3, .yse-widget h4, .yse-widget p { margin: 0; padding: 0; }
+.yse-widget img { max-width: 100%; height: auto; }
+.yse-widget a { text-decoration: none; }
+.yse-widget svg { vertical-align: middle; }
+
+.yse-empty { text-align: center; color: var(--yse-muted); padding: 26px; }
+
+.yse-widget .yse-card {
+ --yse-accent: #4CAF50;
+ --yse-accent-soft: color-mix(in srgb, var(--yse-accent) 10%, white);
+ --yse-accent-ink: color-mix(in srgb, var(--yse-accent) 62%, black);
+ background: var(--yse-card);
+ border-radius: 18px;
+ box-shadow: 0 14px 40px rgba(43, 38, 34, 0.10);
+ overflow: hidden;
+}
+.yse-widget .yse-card + .yse-card { margin-top: 24px; }
+
+/* Header compatto */
+.yse-widget .yse-chead { display: flex; gap: 18px; padding: 20px 26px; align-items: flex-start; }
+.yse-widget .yse-datechip { flex-shrink: 0; border: 1px solid var(--yse-line); border-radius: 12px; padding: 7px 12px 5px; text-align: center; min-width: 58px; }
+.yse-widget .yse-datechip .yse-m { font-size: 11px; font-weight: 600; letter-spacing: .12em; color: var(--yse-accent-ink); }
+.yse-widget .yse-datechip .yse-d { font-family: inherit; font-size: 24px; font-weight: 600; color: var(--yse-ink); line-height: 1; }
+.yse-widget .yse-main { flex: 1; min-width: 0; }
+.yse-widget .yse-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
+.yse-widget .yse-title-row h3 { font-family: inherit; font-weight: 500; font-size: 23px; margin: 0; color: var(--yse-ink); }
+.yse-widget .yse-toggle { background: none !important; border: none !important; color: var(--yse-accent-ink) !important; font-weight: 600; font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; padding: 2px; border-radius: 0; }
+.yse-widget .yse-toggle svg { width: 15px; height: 15px; transition: transform .2s; }
+.yse-widget .yse-toggle.yse-on svg { transform: rotate(180deg); }
+.yse-widget .yse-meta { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 10px; }
+.yse-widget .yse-item { display: flex; align-items: center; gap: 7px; color: var(--yse-muted); font-size: 14px; }
+.yse-widget .yse-item svg { width: 17px; height: 17px; color: var(--yse-accent-ink); flex-shrink: 0; }
+.yse-widget .yse-item strong { color: var(--yse-ink); font-weight: 600; }
+.yse-widget .yse-price { flex-shrink: 0; align-self: flex-start; background: var(--yse-accent) !important; color: #fff; font-weight: 600; font-size: 15px; padding: 8px 15px; border-radius: 999px; }
+
+/* Dettagli espandibili */
+.yse-widget .yse-details { display: none; border-top: 1px solid var(--yse-line); }
+.yse-widget .yse-details.yse-open { display: block; }
+.yse-widget .yse-hero { position: relative; height: 300px; background: linear-gradient(135deg, var(--yse-accent-ink), var(--yse-accent)); }
+.yse-widget .yse-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
+.yse-widget .yse-emblem { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,.35); }
+.yse-widget .yse-emblem svg { width: 78px; height: 78px; }
+.yse-widget .yse-body { padding: 24px 26px; }
+.yse-widget .yse-section { margin-top: 26px; }
+.yse-widget .yse-body h4 { font-family: inherit; font-weight: 600; font-size: 19px; color: var(--yse-ink); margin: 0 0 10px; display: flex; align-items: center; gap: 9px; }
+.yse-widget .yse-body h4 svg { width: 20px; height: 20px; color: var(--yse-accent-ink); }
+.yse-widget .yse-desc { font-size: 15.5px; color: #4a433d; }
+.yse-widget .yse-desc p { margin: 0 0 10px; }
+.yse-widget .yse-desc img { max-width: 100%; border-radius: 10px; }
+.yse-widget .yse-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-top: 24px; }
+.yse-widget .yse-detail { background: var(--yse-accent-soft); border: 1px solid color-mix(in srgb, var(--yse-accent) 18%, white); border-radius: 14px; padding: 15px 16px; }
+.yse-widget .yse-dh { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--yse-accent-ink); margin-bottom: 6px; font-size: 14px; }
+.yse-widget .yse-dh svg { width: 17px; height: 17px; }
+.yse-widget .yse-dt { font-size: 14px; color: #4a433d; }
+.yse-widget .yse-gallery { display: flex; gap: 10px; flex-wrap: wrap; }
+.yse-widget .yse-gallery img { width: 108px; height: 108px; object-fit: cover; border-radius: 12px; cursor: pointer; transition: transform .15s, box-shadow .15s; }
+.yse-widget .yse-gallery img:hover { transform: translateY(-2px); box-shadow: 0 8px 18px rgba(0,0,0,.15); }
+.yse-widget .yse-map { border: 0; width: 100%; height: 220px; border-radius: 14px; display: block; }
+
+/* Booking */
+.yse-widget .yse-booking { padding: 22px 26px; border-top: 1px solid var(--yse-line); }
+.yse-widget .yse-soldout { text-align: center; font-weight: 600; color: #c0392b; }
+.yse-widget .yse-wl-banner { background: color-mix(in srgb, var(--yse-accent) 12%, white); color: var(--yse-accent-ink); border-radius: 12px; padding: 12px 14px; font-size: 13.5px; margin-bottom: 16px; }
+.yse-widget .yse-bcompact { display: flex; align-items: flex-end; gap: 22px; flex-wrap: wrap; }
+.yse-widget .yse-btitle { font-family: inherit; font-weight: 600; font-size: 19px; color: var(--yse-ink); margin-right: auto; align-self: center; }
+.yse-widget .yse-lbl { font-size: 12px; color: var(--yse-muted); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 5px; }
+.yse-widget .yse-stepper { display: flex; align-items: center; border: 1px solid var(--yse-line); border-radius: 11px; overflow: hidden; background: #fff; }
+.yse-widget .yse-stepper button { background: #fff !important; width: 44px; height: 46px; font-size: 22px; color: var(--yse-accent-ink) !important; cursor: pointer; }
+.yse-widget .yse-stepper button:disabled { color: #cfc8c2 !important; cursor: not-allowed; }
+.yse-widget .yse-num { width: 54px; text-align: center; font-size: 17px; font-weight: 600; }
+.yse-widget .yse-total .yse-val { font-family: inherit; font-size: 26px; font-weight: 600; color: var(--yse-accent-ink); }
+.yse-widget .yse-cta { background: var(--yse-accent) !important; color: #fff !important; font-size: 15px; font-weight: 600; border-radius: 11px; cursor: pointer; transition: filter .15s; }
+.yse-widget .yse-cta:hover { filter: brightness(.94) !important; }
+.yse-widget .yse-cta:disabled { background: #cfc8c2 !important; cursor: not-allowed; }
+.yse-widget .yse-open { padding: 14px 26px; }
+.yse-widget .yse-full { width: 100%; padding: 15px; margin-top: 6px; }
+.yse-widget .yse-bform { display: none; margin-top: 20px; padding-top: 20px; border-top: 1px dashed var(--yse-line); }
+.yse-widget .yse-bform.yse-open { display: block; }
+.yse-widget .yse-frow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
+.yse-widget .yse-field { margin-bottom: 12px; }
+.yse-widget .yse-field label { display: block; font-size: 13px; font-weight: 600; color: var(--yse-ink); margin-bottom: 5px; }
+.yse-widget .yse-field input { border: 1px solid var(--yse-line) !important; border-radius: 10px; padding: 12px 13px; font-size: 15px; background: #fff !important; color: var(--yse-ink) !important; }
+.yse-widget .yse-field input:focus { outline: none; border-color: var(--yse-accent) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--yse-accent) 22%, white) !important; }
+.yse-widget .yse-consent { font-size: 13px; color: #4a433d; display: flex; gap: 9px; align-items: flex-start; margin-bottom: 9px; }
+.yse-widget .yse-consent input { width: auto; margin-top: 3px; }
+.yse-widget .yse-hp { position: absolute !important; left: -9999px !important; width: 1px !important; height: 1px !important; }
+.yse-widget .yse-error { color: #c0392b; font-size: 13px; margin-top: 8px; min-height: 18px; }
+.yse-widget .yse-done { text-align: center; padding: 10px 4px; }
+.yse-widget .yse-done .yse-ok { width: 54px; height: 54px; border-radius: 50%; background: var(--yse-accent); color: #fff; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; }
+.yse-widget .yse-done h3 { margin: 0 0 6px; font-family: inherit; font-size: 21px; }
+.yse-widget .yse-done p { color: #4a433d; font-size: 14px; margin: 2px 0; }
+
+/* Lightbox (montato su , fuori da .yse-widget) */
+.yse-lightbox { position: fixed; inset: 0; background: rgba(24,19,16,.9); display: none; align-items: center; justify-content: center; z-index: 99999; padding: 24px; }
+.yse-lightbox.yse-open { display: flex; }
+.yse-lightbox img { max-width: 92vw; max-height: 86vh; border-radius: 12px; }
+.yse-lightbox .yse-lbclose { position: absolute; top: 18px; right: 22px; color: #fff !important; font-size: 34px; cursor: pointer; background: none !important; border: none !important; line-height: 1; padding: 0; }
+
+@media (max-width: 600px) {
+ .yse-widget .yse-hero { height: 210px; }
+ .yse-widget .yse-title-row h3 { font-size: 20px; }
+ .yse-widget .yse-frow { grid-template-columns: 1fr; }
+ .yse-widget .yse-btitle { width: 100%; margin-bottom: 4px; }
+ .yse-widget .yse-chead, .yse-widget .yse-booking, .yse-widget .yse-body { padding-left: 18px; padding-right: 18px; }
+}
+@media (prefers-reduced-motion: reduce) { .yse-widget * { transition: none !important; } }
+
+/* Miniatura copertina nella testata compatta */
+.yse-widget .yse-thumb { width: 76px; height: 76px; border-radius: 12px; object-fit: cover; flex-shrink: 0; }
+@media (max-width: 600px) { .yse-widget .yse-thumb { width: 60px; height: 60px; } }
+
+/* Evento programmato (prenotazioni non ancora aperte) */
+.yse-widget .yse-scheduled { text-align: center; font-weight: 600; color: var(--yse-accent-ink); background: color-mix(in srgb, var(--yse-accent) 12%, white); border-radius: 12px; padding: 12px 14px; }
\ No newline at end of file
diff --git a/wp plugin/yogasoul-eventi/assets/yogasoul-eventi.js b/wp plugin/yogasoul-eventi/assets/yogasoul-eventi.js
new file mode 100644
index 0000000..2c0a753
--- /dev/null
+++ b/wp plugin/yogasoul-eventi/assets/yogasoul-eventi.js
@@ -0,0 +1,246 @@
+/* YogaSoul Eventi — interazioni widget */
+(function () {
+ "use strict";
+
+ function fmtMoney(v, sym) {
+ if (v <= 0) return "Gratuito";
+ return (
+ sym +
+ v.toLocaleString("it-IT", {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })
+ );
+ }
+
+ function apiBase(el) {
+ var w = el.closest(".yse-widget");
+ return (w && w.getAttribute("data-api")) || "";
+ }
+
+ function initCard(card) {
+ var api = apiBase(card);
+ var eid = parseInt(card.getAttribute("data-eid"), 10);
+ var cost = parseFloat(card.getAttribute("data-cost")) || 0;
+ var sym = card.getAttribute("data-sym") || "€";
+ var maxQty = parseInt(card.getAttribute("data-maxqty"), 10) || 0;
+ var waitMode = card.getAttribute("data-waitmode") === "1";
+
+ // Toggle dettagli
+ var toggle = card.querySelector(".yse-toggle");
+ if (toggle) {
+ toggle.addEventListener("click", function () {
+ var det = card.querySelector(".yse-details");
+ if (!det) return;
+ var open = det.classList.toggle("yse-open");
+ toggle.classList.toggle("yse-on", open);
+ toggle.setAttribute("aria-expanded", open ? "true" : "false");
+ var lbl = toggle.querySelector(".yse-tlabel");
+ if (lbl) lbl.textContent = open ? "Chiudi" : "Dettagli";
+ });
+ }
+
+ var booking = card.querySelector(".yse-booking");
+ var numEl = card.querySelector(".yse-num");
+ if (!numEl) return; // esaurito senza lista d'attesa: nessuna prenotazione
+
+ var totEl = card.querySelector(".yse-total .yse-val");
+ var minus = card.querySelector(".yse-minus");
+ var plus = card.querySelector(".yse-plus");
+ var openBtn = card.querySelector(".yse-open");
+ var form = card.querySelector(".yse-bform");
+ var submit = card.querySelector(".yse-submit");
+ var qty = 1;
+
+ function refresh() {
+ numEl.textContent = qty;
+ if (totEl) totEl.textContent = fmtMoney(qty * cost, sym);
+ minus.disabled = qty <= 1;
+ plus.disabled = qty >= maxQty;
+ if (submit) {
+ submit.textContent = waitMode
+ ? "Iscriviti alla lista d'attesa"
+ : "Conferma prenotazione" +
+ (cost > 0 ? " · " + fmtMoney(qty * cost, sym) : "");
+ }
+ }
+ minus.addEventListener("click", function () {
+ if (qty > 1) {
+ qty--;
+ refresh();
+ }
+ });
+ plus.addEventListener("click", function () {
+ if (qty < maxQty) {
+ qty++;
+ refresh();
+ }
+ });
+ refresh();
+
+ openBtn.addEventListener("click", function () {
+ form.classList.add("yse-open");
+ openBtn.style.display = "none";
+ var n = card.querySelector(".yse-name");
+ if (n) n.focus();
+ });
+
+ submit.addEventListener("click", function () {
+ var name = card.querySelector(".yse-name").value.trim();
+ var surname = card.querySelector(".yse-surname").value.trim();
+ var email = card.querySelector(".yse-email").value.trim();
+ var privacy = card.querySelector(".yse-c-privacy").checked;
+ var mkt = card.querySelector(".yse-c-mkt").checked;
+ var hp = card.querySelector(".yse-hp").value;
+ var err = card.querySelector(".yse-error");
+
+ if (!name || !surname) {
+ err.textContent = "Inserisci nome e cognome.";
+ return;
+ }
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
+ err.textContent = "Inserisci un'email valida.";
+ return;
+ }
+ if (!privacy) {
+ err.textContent = "Devi accettare l'informativa privacy.";
+ return;
+ }
+ err.textContent = "";
+
+ submit.disabled = true;
+ submit.textContent = "Invio in corso…";
+
+ var body = new URLSearchParams();
+ body.set("event_id", eid);
+ body.set("name", name);
+ body.set("surname", surname);
+ body.set("email", email);
+ body.set("seats", qty);
+ body.set("consent", "1");
+ body.set("marketing", mkt ? "1" : "0");
+ body.set("website", hp); // honeypot
+
+ fetch(api + "/book.php", {
+ method: "POST",
+ headers: {
+ "Content-Type":
+ "application/x-www-form-urlencoded;charset=UTF-8",
+ },
+ body: body.toString(),
+ })
+ .then(function (r) {
+ return r.json().then(function (j) {
+ return { status: r.status, j: j };
+ });
+ })
+ .then(function (res) {
+ var j = res.j || {};
+ if (j.ok) {
+ showDone(booking, j, name, surname, email);
+ return;
+ }
+ // Errori gestiti
+ submit.disabled = false;
+ refresh();
+ if (j.code === "partial") {
+ err.textContent =
+ j.error ||
+ "Sono rimasti solo " + j.remaining + " posti.";
+ if (
+ typeof j.remaining === "number" &&
+ j.remaining > 0
+ ) {
+ maxQty = j.remaining;
+ if (qty > maxQty) {
+ qty = maxQty;
+ refresh();
+ }
+ }
+ } else {
+ err.textContent =
+ j.error || "Prenotazione non riuscita. Riprova.";
+ }
+ })
+ .catch(function () {
+ submit.disabled = false;
+ refresh();
+ err.textContent = "Errore di rete. Riprova.";
+ });
+ });
+
+ function showDone(container, j, name, surname, email) {
+ var waiting = j.status === "waiting";
+ var title = waiting
+ ? "Sei in lista d'attesa"
+ : "Prenotazione confermata!";
+ var msg =
+ j.message ||
+ (waiting
+ ? "Ti avviseremo appena si libera un posto."
+ : "Ti abbiamo inviato una email di riepilogo.");
+ container.innerHTML =
+ '' +
+ '
' +
+ "
" +
+ title +
+ " " +
+ "
" +
+ escapeHtml(name + " " + surname) +
+ " · " +
+ escapeHtml(email) +
+ "
" +
+ "
" +
+ escapeHtml(msg) +
+ "
" +
+ "
";
+ }
+ }
+
+ function escapeHtml(s) {
+ var d = document.createElement("div");
+ d.textContent = s;
+ return d.innerHTML;
+ }
+
+ // Lightbox condiviso
+ function ensureLightbox() {
+ var lb = document.querySelector(".yse-lightbox");
+ if (lb) return lb;
+ lb = document.createElement("div");
+ lb.className = "yse-lightbox";
+ lb.innerHTML =
+ '× ';
+ document.body.appendChild(lb);
+ function close() {
+ lb.classList.remove("yse-open");
+ lb.querySelector("img").src = "";
+ }
+ lb.addEventListener("click", function (e) {
+ if (e.target === lb || e.target.classList.contains("yse-lbclose"))
+ close();
+ });
+ document.addEventListener("keydown", function (e) {
+ if (e.key === "Escape") close();
+ });
+ return lb;
+ }
+
+ function init() {
+ document.querySelectorAll(".yse-card").forEach(initCard);
+ document.querySelectorAll(".yse-gallery img").forEach(function (img) {
+ img.addEventListener("click", function () {
+ var lb = ensureLightbox();
+ lb.querySelector("img").src = img.getAttribute("data-full");
+ lb.classList.add("yse-open");
+ });
+ });
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
+ } else {
+ init();
+ }
+})();
diff --git a/wp plugin/yogasoul-eventi/yogasoul-eventi.php b/wp plugin/yogasoul-eventi/yogasoul-eventi.php
new file mode 100644
index 0000000..62664a4
--- /dev/null
+++ b/wp plugin/yogasoul-eventi/yogasoul-eventi.php
@@ -0,0 +1,276 @@
+ '',
+ 'id' => '',
+ 'limit' => '',
+ ], $atts, 'yogasoul_eventi');
+
+ wp_enqueue_style('yse-eventi');
+ wp_enqueue_script('yse-eventi');
+
+ // Costruisce l'URL dell'endpoint lista
+ $query = [];
+ if ($atts['id'] !== '') {
+ $query['id'] = (int) $atts['id'];
+ }
+ if ($atts['ids'] !== '') {
+ $clean = preg_replace('/[^0-9,]/', '', $atts['ids']);
+ if ($clean !== '') {
+ $query['ids'] = $clean;
+ }
+ }
+ if ($atts['limit'] !== '') {
+ $query['limit'] = (int) $atts['limit'];
+ }
+ $url = YSE_API_BASE . '/events.php' . (!empty($query) ? '?' . http_build_query($query) : '');
+
+ // Lettura server-side (niente CORS) + anti-cache
+ $resp = wp_remote_get($url . (strpos($url, '?') !== false ? '&' : '?') . '_=' . time(), [
+ 'timeout' => 8,
+ 'headers' => ['Cache-Control' => 'no-cache'],
+ ]);
+ if (is_wp_error($resp) || (int) wp_remote_retrieve_response_code($resp) !== 200) {
+ return '';
+ }
+ $data = json_decode(wp_remote_retrieve_body($resp), true);
+ if (!is_array($data) || empty($data['ok'])) {
+ return '';
+ }
+ if (empty($data['events'])) {
+ return '';
+ }
+
+ $out = '';
+ foreach ($data['events'] as $ev) {
+ $out .= yse_render_card($ev);
+ }
+ $out .= '
';
+ return $out;
+}
+
+/* Icona SVG inline (stroke = currentColor) */
+function yse_icon($name)
+{
+ $p = [
+ 'user' => ' ',
+ 'pin' => ' ',
+ 'clock' => ' ',
+ 'heart' => ' ',
+ 'bag' => ' ',
+ 'info' => ' ',
+ 'img' => ' ',
+ 'chev' => ' ',
+ ];
+ $inner = $p[$name] ?? '';
+ return '' . $inner . ' ';
+}
+
+/* Rende una singola card evento */
+function yse_render_card($ev)
+{
+ $mesiAbbr = [1 => 'GEN', 2 => 'FEB', 3 => 'MAR', 4 => 'APR', 5 => 'MAG', 6 => 'GIU', 7 => 'LUG', 8 => 'AGO', 9 => 'SET', 10 => 'OTT', 11 => 'NOV', 12 => 'DIC'];
+ $mesi = [1 => 'gennaio', 2 => 'febbraio', 3 => 'marzo', 4 => 'aprile', 5 => 'maggio', 6 => 'giugno', 7 => 'luglio', 8 => 'agosto', 9 => 'settembre', 10 => 'ottobre', 11 => 'novembre', 12 => 'dicembre'];
+
+ $id = (int) $ev['id'];
+ $cost = (float) $ev['cost'];
+ $sym = (string) $ev['currency_symbol'];
+ $price = $cost > 0 ? $sym . number_format($cost, 2, ',', '.') : 'Gratuito';
+ $color = (isset($ev['color']) && preg_match('/^#[0-9a-fA-F]{6}$/', $ev['color'])) ? $ev['color'] : '#4CAF50';
+
+ $soldOut = !empty($ev['sold_out']);
+ $waitOn = !empty($ev['waitlist_enabled']);
+ $waitMode = $soldOut && $waitOn;
+ $hardFull = $soldOut && !$waitOn;
+ $maxPer = max(1, (int) $ev['max_per_booking']);
+ $remaining = (int) $ev['remaining'];
+ $maxQty = $soldOut ? ($waitMode ? $maxPer : 0) : max(1, min($maxPer, $remaining));
+
+ $startTs = strtotime($ev['starts_at']);
+ $dayNum = (int) date('j', $startTs);
+ $monAbbr = $mesiAbbr[(int) date('n', $startTs)];
+ $dateLong = $dayNum . ' ' . $mesi[(int) date('n', $startTs)] . ' ' . date('Y', $startTs);
+ $timeLabel = date('H:i', $startTs) . (!empty($ev['ends_at']) ? '–' . date('H:i', strtotime($ev['ends_at'])) : '');
+
+ $address = trim((string) ($ev['address'] ?? ''));
+ $organizer = trim((string) ($ev['organizer'] ?? ''));
+ $mapQuery = $address !== '' ? $address : ((isset($ev['lat'], $ev['lng']) && $ev['lat'] && $ev['lng']) ? ($ev['lat'] . ',' . $ev['lng']) : '');
+
+ $gallery = is_array($ev['gallery'] ?? null) ? $ev['gallery'] : [];
+ $hasDetails = trim((string) ($ev['description'] ?? '')) !== ''
+ || trim((string) ($ev['suitable_for'] ?? '')) !== ''
+ || trim((string) ($ev['what_to_bring'] ?? '')) !== ''
+ || trim((string) ($ev['instructions'] ?? '')) !== ''
+ || !empty($ev['cover_image']) || !empty($gallery) || $mapQuery !== '';
+
+ ob_start();
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dettagli
+
+
+
+
+ Capacità /
+
+
+
+ ·
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Galleria
+
+
+
+
+
+
+
+
+
+
+
Dove
+
+
+
+
+
+
+
+
+
+
+
Prenotazioni aperte dal
+
+
Evento esaurito — non ci sono più posti disponibili.
+
+ 0
+ ? ' Al momento ' . ($waitingCount === 1 ? "c'è 1 persona" : "ci sono {$waitingCount} persone") . ' in coda.'
+ : '';
+ ?>
+
Posti esauriti — puoi iscriverti alla lista d'attesa : ti avviseremo se si libera un posto.
+
+
+
+
+
Persone
+
+ −
+ 1
+ +
+
+
+
+
+
+
+
+
+
+
+