create events api wp

This commit is contained in:
2026-09-17 16:53:51 +02:00
parent 5df1f2d261
commit dea86cb6df
11 changed files with 1433 additions and 26 deletions
Binary file not shown.
@@ -0,0 +1,497 @@
/* YogaSoul Eventi — stili widget (namespaced .yse-) */
.yse-widget {
--yse-ink: #2b2622;
--yse-muted: #8c837c;
--yse-line: #ece7e2;
--yse-card: #ffffff;
max-width: 720px;
margin: 0 auto;
font-family:
"Inter",
system-ui,
-apple-system,
sans-serif;
color: var(--yse-ink);
line-height: 1.55;
}
.yse-widget * {
box-sizing: border-box;
}
.yse-empty {
text-align: center;
color: var(--yse-muted);
padding: 26px;
}
.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.1);
overflow: hidden;
}
.yse-card + .yse-card {
margin-top: 22px;
}
/* Header compatto */
.yse-chead {
display: flex;
gap: 16px;
padding: 18px 22px;
align-items: flex-start;
}
.yse-datechip {
flex-shrink: 0;
border: 1px solid var(--yse-line);
border-radius: 12px;
padding: 7px 11px 5px;
text-align: center;
min-width: 54px;
}
.yse-datechip .yse-m {
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.12em;
color: var(--yse-accent-ink);
}
.yse-datechip .yse-d {
font-family: "Fraunces", serif;
font-size: 23px;
font-weight: 600;
color: var(--yse-ink);
line-height: 1;
}
.yse-main {
flex: 1;
min-width: 0;
}
.yse-title-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.yse-title-row h3 {
font-family: "Fraunces", serif;
font-weight: 500;
font-size: 21px;
margin: 0;
color: var(--yse-ink);
}
.yse-toggle {
background: none;
border: none;
color: var(--yse-accent-ink);
font-weight: 600;
font-size: 13px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px;
}
.yse-toggle svg {
width: 15px;
height: 15px;
transition: transform 0.2s;
}
.yse-toggle.yse-on svg {
transform: rotate(180deg);
}
.yse-meta {
display: flex;
flex-wrap: wrap;
gap: 6px 20px;
margin-top: 9px;
}
.yse-item {
display: flex;
align-items: center;
gap: 7px;
color: var(--yse-muted);
font-size: 13.5px;
}
.yse-item svg {
width: 16px;
height: 16px;
color: var(--yse-accent-ink);
flex-shrink: 0;
}
.yse-item strong {
color: var(--yse-ink);
font-weight: 600;
}
.yse-price {
flex-shrink: 0;
align-self: flex-start;
background: var(--yse-accent);
color: #fff;
font-weight: 600;
font-size: 14px;
padding: 7px 13px;
border-radius: 999px;
}
/* Dettagli espandibili */
.yse-details {
display: none;
border-top: 1px solid var(--yse-line);
}
.yse-details.yse-open {
display: block;
}
.yse-hero {
position: relative;
height: 260px;
background: linear-gradient(
135deg,
var(--yse-accent-ink),
var(--yse-accent)
);
}
.yse-hero img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.yse-emblem {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.35);
}
.yse-emblem svg {
width: 72px;
height: 72px;
}
.yse-body {
padding: 22px;
}
.yse-section {
margin-top: 24px;
}
.yse-body h4 {
font-family: "Fraunces", serif;
font-weight: 600;
font-size: 18px;
color: var(--yse-ink);
margin: 0 0 10px;
display: flex;
align-items: center;
gap: 9px;
}
.yse-body h4 svg {
width: 19px;
height: 19px;
color: var(--yse-accent-ink);
}
.yse-desc {
font-size: 15px;
color: #4a433d;
}
.yse-desc p {
margin: 0 0 10px;
}
.yse-desc img {
max-width: 100%;
border-radius: 10px;
}
.yse-detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 12px;
margin-top: 22px;
}
.yse-detail {
background: var(--yse-accent-soft);
border: 1px solid color-mix(in srgb, var(--yse-accent) 18%, white);
border-radius: 13px;
padding: 14px 15px;
}
.yse-dh {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--yse-accent-ink);
margin-bottom: 6px;
font-size: 13.5px;
}
.yse-dh svg {
width: 16px;
height: 16px;
}
.yse-dt {
font-size: 13.5px;
color: #4a433d;
}
.yse-gallery {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.yse-gallery img {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 11px;
cursor: pointer;
transition:
transform 0.15s,
box-shadow 0.15s;
}
.yse-gallery img:hover {
transform: translateY(-2px);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.15);
}
.yse-map {
border: 0;
width: 100%;
height: 210px;
border-radius: 13px;
display: block;
}
/* Booking */
.yse-booking {
padding: 18px 22px;
border-top: 1px solid var(--yse-line);
}
.yse-soldout {
text-align: center;
font-weight: 600;
color: #c0392b;
}
.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: 14px;
}
.yse-bcompact {
display: flex;
align-items: flex-end;
gap: 18px;
flex-wrap: wrap;
}
.yse-btitle {
font-family: "Fraunces", serif;
font-weight: 600;
font-size: 18px;
color: var(--yse-ink);
margin-right: auto;
align-self: center;
}
.yse-lbl {
font-size: 12px;
color: var(--yse-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 5px;
}
.yse-stepper {
display: flex;
align-items: center;
border: 1px solid var(--yse-line);
border-radius: 11px;
overflow: hidden;
background: #fff;
}
.yse-stepper button {
border: none;
background: #fff;
width: 42px;
height: 44px;
font-size: 21px;
color: var(--yse-accent-ink);
cursor: pointer;
line-height: 1;
}
.yse-stepper button:disabled {
color: #cfc8c2;
cursor: not-allowed;
}
.yse-num {
width: 50px;
text-align: center;
font-size: 16px;
font-weight: 600;
}
.yse-total .yse-val {
font-family: "Fraunces", serif;
font-size: 24px;
font-weight: 600;
color: var(--yse-accent-ink);
}
.yse-cta {
border: none;
border-radius: 11px;
background: var(--yse-accent);
color: #fff;
font-size: 15px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: filter 0.15s;
}
.yse-cta:hover {
filter: brightness(0.94);
}
.yse-cta:disabled {
background: #cfc8c2;
cursor: not-allowed;
}
.yse-open {
padding: 13px 24px;
}
.yse-full {
width: 100%;
padding: 14px;
margin-top: 6px;
}
.yse-bform {
display: none;
margin-top: 18px;
padding-top: 18px;
border-top: 1px dashed var(--yse-line);
}
.yse-bform.yse-open {
display: block;
}
.yse-frow {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.yse-field {
margin-bottom: 12px;
}
.yse-field label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--yse-ink);
margin-bottom: 5px;
}
.yse-field input {
width: 100%;
border: 1px solid var(--yse-line);
border-radius: 10px;
padding: 11px 13px;
font-size: 15px;
font-family: inherit;
background: #fff;
color: var(--yse-ink);
}
.yse-field input:focus {
outline: none;
border-color: var(--yse-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--yse-accent) 22%, white);
}
.yse-consent {
font-size: 13px;
color: #4a433d;
display: flex;
gap: 9px;
align-items: flex-start;
margin-bottom: 9px;
}
.yse-consent input {
margin-top: 3px;
}
.yse-hp {
position: absolute !important;
left: -9999px !important;
width: 1px;
height: 1px;
}
.yse-error {
color: #c0392b;
font-size: 13px;
margin-top: 8px;
min-height: 18px;
}
.yse-done {
text-align: center;
padding: 8px 4px;
}
.yse-done .yse-ok {
width: 52px;
height: 52px;
border-radius: 50%;
background: var(--yse-accent);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 10px;
}
.yse-done h3 {
margin: 0 0 6px;
font-family: "Fraunces", serif;
}
.yse-done p {
color: #4a433d;
font-size: 14px;
margin: 2px 0;
}
/* Lightbox */
.yse-lightbox {
position: fixed;
inset: 0;
background: rgba(24, 19, 16, 0.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;
font-size: 34px;
cursor: pointer;
background: none;
border: none;
line-height: 1;
}
@media (max-width: 560px) {
.yse-hero {
height: 200px;
}
.yse-title-row h3 {
font-size: 19px;
}
.yse-frow {
grid-template-columns: 1fr;
}
.yse-btitle {
width: 100%;
margin-bottom: 4px;
}
}
@media (prefers-reduced-motion: reduce) {
.yse-widget * {
transition: none !important;
}
}
@@ -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 =
'<div class="yse-done">' +
' <div class="yse-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" width="24" height="24">' +
' <path d="M20 6 9 17l-5-5"/></svg></div>' +
" <h3>" +
title +
"</h3>" +
" <p><strong>" +
escapeHtml(name + " " + surname) +
"</strong> · " +
escapeHtml(email) +
"</p>" +
" <p>" +
escapeHtml(msg) +
"</p>" +
"</div>";
}
}
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 =
'<button class="yse-lbclose" aria-label="Chiudi">&times;</button><img src="" alt="">';
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();
}
})();
@@ -0,0 +1,275 @@
<?php
/**
* Plugin Name: YogaSoul Eventi
* Description: Mostra gli eventi di YogiBook con card di prenotazione (lista d'attesa inclusa). Shortcode: [yogasoul_eventi], [yogasoul_eventi ids="12,15"], [yogasoul_eventi id="12"], [yogasoul_eventi limit="3"].
* Version: 1.0.0
* Author: YogaSoul
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Base dell'API YogiBook. Sovrascrivibile in wp-config.php con:
* define('YSE_API_BASE', 'https://yogibook.yogasoul.it/apiwp');
*/
if (!defined('YSE_API_BASE')) {
define('YSE_API_BASE', 'https://yogibook.yogasoul.it/apiwp');
}
/* Registra gli asset (accodati solo quando lo shortcode è in pagina) */
add_action('wp_enqueue_scripts', function () {
wp_register_style(
'yse-fonts',
'https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap',
[],
null
);
wp_register_style('yse-eventi', plugins_url('assets/yogasoul-eventi.css', __FILE__), ['yse-fonts'], '1.0.0');
wp_register_script('yse-eventi', plugins_url('assets/yogasoul-eventi.js', __FILE__), [], '1.0.0', true);
});
add_shortcode('yogasoul_eventi', 'yse_render_shortcode');
function yse_render_shortcode($atts)
{
$atts = shortcode_atts([
'ids' => '',
'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)
$resp = wp_remote_get($url, ['timeout' => 8]);
if (is_wp_error($resp) || (int) wp_remote_retrieve_response_code($resp) !== 200) {
return '<div class="yse-widget"><p class="yse-empty">Eventi non disponibili al momento.</p></div>';
}
$data = json_decode(wp_remote_retrieve_body($resp), true);
if (!is_array($data) || empty($data['ok'])) {
return '<div class="yse-widget"><p class="yse-empty">Eventi non disponibili al momento.</p></div>';
}
if (empty($data['events'])) {
return '<div class="yse-widget"><p class="yse-empty">Nessun evento in programma al momento.</p></div>';
}
$out = '<div class="yse-widget" data-api="' . esc_attr(YSE_API_BASE) . '">';
foreach ($data['events'] as $ev) {
$out .= yse_render_card($ev);
}
$out .= '</div>';
return $out;
}
/* Icona SVG inline (stroke = currentColor) */
function yse_icon($name)
{
$p = [
'user' => '<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
'pin' => '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/>',
'clock' => '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
'heart' => '<path d="M20.8 5.6a5 5 0 0 0-7-.1L12 7l-1.8-1.5a5 5 0 1 0-6.6 7.5L12 21l8.4-8a5 5 0 0 0 .4-7.4z"/>',
'bag' => '<path d="M6 7h12l1 13H5L6 7z"/><path d="M9 7a3 3 0 0 1 6 0"/>',
'info' => '<circle cx="12" cy="12" r="9"/><path d="M12 11v5"/><path d="M12 8h.01"/>',
'img' => '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-5-5L5 21"/>',
'chev' => '<path d="m6 9 6 6 6-6"/>',
];
$inner = $p[$name] ?? '';
return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">' . $inner . '</svg>';
}
/* 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();
?>
<div class="yse-card" style="--yse-accent: <?php echo esc_attr($color); ?>;"
data-eid="<?php echo $id; ?>"
data-cost="<?php echo esc_attr($cost); ?>"
data-sym="<?php echo esc_attr($sym); ?>"
data-maxqty="<?php echo (int) $maxQty; ?>"
data-waitmode="<?php echo $waitMode ? 1 : 0; ?>">
<!-- HEADER COMPATTO -->
<div class="yse-chead">
<div class="yse-datechip">
<div class="yse-m"><?php echo esc_html($monAbbr); ?></div>
<div class="yse-d"><?php echo $dayNum; ?></div>
</div>
<div class="yse-main">
<div class="yse-title-row">
<h3><?php echo esc_html($ev['title']); ?></h3>
<?php if ($hasDetails) : ?>
<button type="button" class="yse-toggle" aria-expanded="false">
<span class="yse-tlabel">Dettagli</span><?php echo yse_icon('chev'); ?>
</button>
<?php endif; ?>
</div>
<div class="yse-meta">
<span class="yse-item"><?php echo yse_icon('user'); ?> Capacità <strong><?php echo (int) $ev['occupied']; ?>/<?php echo (int) $ev['max_seats']; ?></strong></span>
<?php if ($address !== '') : ?>
<span class="yse-item"><?php echo yse_icon('pin'); ?> <?php echo esc_html($address); ?></span>
<?php endif; ?>
<span class="yse-item"><?php echo yse_icon('clock'); ?> <?php echo esc_html($dateLong); ?> · <?php echo esc_html($timeLabel); ?></span>
</div>
</div>
<div class="yse-price"><?php echo esc_html($price); ?></div>
</div>
<!-- DETTAGLI ESPANDIBILI -->
<?php if ($hasDetails) : ?>
<div class="yse-details">
<div class="yse-hero <?php echo empty($ev['cover_image']) ? 'yse-nocover' : ''; ?>">
<?php if (!empty($ev['cover_image'])) : ?>
<img src="<?php echo esc_url($ev['cover_image']); ?>" alt="<?php echo esc_attr($ev['title']); ?>">
<?php else : ?>
<div class="yse-emblem"><?php echo yse_icon('img'); ?></div>
<?php endif; ?>
</div>
<div class="yse-body">
<?php if (trim((string) ($ev['description'] ?? '')) !== '') : ?>
<div class="yse-desc"><?php echo wp_kses_post($ev['description']); ?></div>
<?php endif; ?>
<?php if (trim((string) ($ev['suitable_for'] ?? '')) !== '' || trim((string) ($ev['what_to_bring'] ?? '')) !== '' || trim((string) ($ev['instructions'] ?? '')) !== '') : ?>
<div class="yse-detail-grid">
<?php if (trim((string) ($ev['suitable_for'] ?? '')) !== '') : ?>
<div class="yse-detail">
<div class="yse-dh"><?php echo yse_icon('heart'); ?> Per chi è adatto</div>
<div class="yse-dt"><?php echo nl2br(esc_html($ev['suitable_for'])); ?></div>
</div>
<?php endif; ?>
<?php if (trim((string) ($ev['what_to_bring'] ?? '')) !== '') : ?>
<div class="yse-detail">
<div class="yse-dh"><?php echo yse_icon('bag'); ?> Cosa portare</div>
<div class="yse-dt"><?php echo nl2br(esc_html($ev['what_to_bring'])); ?></div>
</div>
<?php endif; ?>
<?php if (trim((string) ($ev['instructions'] ?? '')) !== '') : ?>
<div class="yse-detail">
<div class="yse-dh"><?php echo yse_icon('info'); ?> Istruzioni</div>
<div class="yse-dt"><?php echo nl2br(esc_html($ev['instructions'])); ?></div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($gallery)) : ?>
<div class="yse-section">
<h4><?php echo yse_icon('img'); ?> Galleria</h4>
<div class="yse-gallery">
<?php foreach ($gallery as $g) : ?>
<img src="<?php echo esc_url($g); ?>" alt="" data-full="<?php echo esc_url($g); ?>">
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if ($mapQuery !== '') : ?>
<div class="yse-section">
<h4><?php echo yse_icon('pin'); ?> Dove</h4>
<iframe class="yse-map" loading="lazy" src="https://www.google.com/maps?q=<?php echo rawurlencode($mapQuery); ?>&output=embed"></iframe>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<!-- BOOKING -->
<div class="yse-booking">
<?php if ($hardFull) : ?>
<div class="yse-soldout">Evento esaurito — non ci sono più posti disponibili.</div>
<?php else : ?>
<?php if ($waitMode) : ?>
<div class="yse-wl-banner">Posti esauriti — puoi iscriverti alla <strong>lista d'attesa</strong>: ti avviseremo se si libera un posto.</div>
<?php endif; ?>
<div class="yse-bcompact">
<div class="yse-btitle"><?php echo $waitMode ? "Lista d'attesa" : 'Prenota questo evento'; ?></div>
<div class="yse-bblock">
<div class="yse-lbl">Persone</div>
<div class="yse-stepper">
<button type="button" class="yse-minus">−</button>
<span class="yse-num">1</span>
<button type="button" class="yse-plus">+</button>
</div>
</div>
<div class="yse-bblock yse-total">
<div class="yse-lbl">Totale</div>
<div class="yse-val"><?php echo esc_html($price); ?></div>
</div>
<button type="button" class="yse-cta yse-open"><?php echo $waitMode ? "Lista d'attesa" : 'Prenota'; ?></button>
</div>
<div class="yse-bform">
<div class="yse-frow">
<div class="yse-field"><label>Nome *</label><input type="text" class="yse-name" placeholder="Il tuo nome"></div>
<div class="yse-field"><label>Cognome *</label><input type="text" class="yse-surname" placeholder="Il tuo cognome"></div>
</div>
<div class="yse-field"><label>Email *</label><input type="email" class="yse-email" placeholder="latua@email.it"></div>
<label class="yse-consent"><input type="checkbox" class="yse-c-privacy"> <span>Ho letto e accetto l'informativa privacy. *</span></label>
<label class="yse-consent"><input type="checkbox" class="yse-c-mkt"> <span>Desidero ricevere aggiornamenti e novità via email.</span></label>
<!-- honeypot anti-bot -->
<input type="text" class="yse-hp" name="website" tabindex="-1" autocomplete="off">
<button type="button" class="yse-cta yse-full yse-submit"><?php echo $waitMode ? "Iscriviti alla lista d'attesa" : 'Conferma prenotazione'; ?></button>
<div class="yse-error"></div>
</div>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}