PageSourceSearch

https://effortless-axolotl-51aa38.netlify.app/js/main.js

js effortless-axolotl-51aa38.netlify.app collected 2026-10-03 10:41:06 UTC 18,551 bytes, 340 lines download raw bytes

1// =============================================
2//  main.js — Fast Chicken AQP
3// =============================================
4
5let whatsappNumber = DEFAULT_WHATSAPP;
6let msgPedido   = '¡Hola! Quisiera hacer un pedido 🍗';
7let msgDelivery = 'Quiero pedir un delivery 🍗';
8let msgItem     = '¡Hola! Quisiera pedir: *{plato}* (S/ {precio}) 🍗';
9let msgReserva  = '🍗 *RESERVA — Fast Chicken AQP*\n\n👤 Nombre: {nombre}\n📱 Teléfono: {telefono}\n📅 Fecha: {fecha}\n👥 Personas: {personas}\n💬 Mensaje: {mensaje}';
10let msgTrabajo  = 'Hola, me gustaría postular para trabajar en Fast Chicken AQP 👔';
11let allMenuItems = [], slides = [], currentSlide = 0, sliderTimer = null;
12
13// ── INIT ──────────────────────────────────────────────
14document.addEventListener('DOMContentLoaded', async () => {
15  initNavbar();
16  initScrollReveal();
17  await Promise.all([initSlider(), loadConfig(), loadMenu(), loadNosotrosPublic()]);
18  loadPromoPopup();
19});
20
21// ── NAVBAR ────────────────────────────────────────────
22function initNavbar() {
23  const btn = document.getElementById('hamburger');
24  const links = document.getElementById('navLinks');
25  if (btn && links) {
26    btn.addEventListener('click', () => links.classList.toggle('open'));
27    links.querySelectorAll('a').forEach(a =>
28      a.addEventListener('click', () => links.classList.remove('open'))
29    );
30  }
31}
32
33// ── SCROLL REVEAL ─────────────────────────────────────
34function initScrollReveal() {
35  const obs = new IntersectionObserver(
36    entries => entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); }),
37    { threshold: 0.1 }
38  );
39  document.querySelectorAll('.reveal').forEach(el => obs.observe(el));
40}
41
42// ── HERO SLIDER ───────────────────────────────────────
43async function initSlider() {
44  try {
45    const { data } = await _supabase.from('campaigns').select('*')
46      .eq('is_active', true).order('created_at', { ascending: false }).limit(5);
47    if (data && data.length) {
48      slides = data; renderDots(); showSlide(0);
49      if (slides.length > 1) sliderTimer = setInterval(() => nextSlide(), 5500);
50    }
51  } catch(_) {}
52}
53
54function renderDots() {
55  const dots = document.getElementById('heroDots');
56  if (!dots || slides.length <= 1) return;
57  dots.innerHTML = slides.map((_, i) =>
58    `<button class="hero-dot ${i===0?'active':''}" onclick="goSlide(${i})"></button>`
59  ).join('');
60}
61
62function showSlide(idx) {
63  if (!slides.length) return;
64  currentSlide = (idx + slides.length) % slides.length;
65  const s = slides[currentSlide];
66  const bg    = document.getElementById('heroBg');    if (bg    && s.hero_image_url) bg.src           = s.hero_image_url;
67  const badge = document.getElementById('heroBadge'); if (badge && s.name)           badge.textContent = '🔥 ' + s.name;
68  const title = document.getElementById('heroTitle'); if (title && s.hero_title)     title.innerHTML   = s.hero_title;
69  const sub   = document.getElementById('heroSub');   if (sub   && s.hero_subtitle)  sub.textContent   = s.hero_subtitle;
70  const cta   = document.getElementById('heroCtaBtn');if (cta   && s.hero_cta_text)  cta.textContent   = '🍗 ' + s.hero_cta_text;
71  document.querySelectorAll('.hero-dot').forEach((d,i) => d.classList.toggle('active', i === currentSlide));
72}
73
74function nextSlide() { showSlide(currentSlide + 1); }
75function prevSlide() { showSlide(currentSlide - 1); }
76function goSlide(i) { clearInterval(sliderTimer); showSlide(i); sliderTimer = setInterval(() => nextSlide(), 5500); }
77
78// ── CONFIG ────────────────────────────────────────────
79async function loadConfig() {
80  try {
81    const { data } = await _supabase.from('site_config').select('*');
82    if (!data) return;
83    data.forEach(r => {
84      if (!r.key || r.value == null) return;
85      const v = r.value;
86      switch (r.key) {
87        case 'whatsapp':
88          whatsappNumber = v;
89          const wp = document.getElementById('contactWhatsapp');
90          if (wp) wp.textContent = '+' + v;
91          break;
92        case 'logo_url':
93          const li = document.getElementById('navLogoImg');
94          const le = document.getElementById('navLogoEmoji');
95          if (li) { li.src = v; li.style.display = 'block'; }
96          if (le) le.style.display = 'none';
97          break;
98        case 'msg_pedido':   msgPedido   = v; break;
99        case 'msg_delivery': msgDelivery = v; break;
100        case 'msg_item':     msgItem     = v; break;
101        case 'msg_reserva':  msgReserva  = v; break;
102        case 'msg_trabajo':  msgTrabajo  = v; break;
103        case 'address_1':
104          document.querySelectorAll('.contact-address').forEach(el => el.textContent = v);
105          break;
106        case 'hours':
107          document.querySelectorAll('.contact-hours').forEach(el => el.textContent = v);
108          break;
109        case 'tiktok':
110          document.querySelectorAll('a[href*="tiktok.com"]').forEach(a => a.href = v);
111          break;
112        case 'maps_query':
113          const frame = document.getElementById('siteMapFrame');
114          if (frame) frame.src = 'https://maps.google.com/maps?q=' + encodeURIComponent(v) + '&output=embed&z=16';
115          break;
116      }
117    });
118  } catch(_) {}
119}
120
121// ── MENÚ ──────────────────────────────────────────────
122async function loadMenu() {
123  try {
124    const { data, error } = await _supabase.from('menu_items').select('*')
125      .eq('available', true).order('sort_order', { ascending: true });
126    if (error || !data || !data.length) { renderMenuFallback(); return; }
127    allMenuItems = data;
128    renderMenuTabs([...new Set(data.map(i => i.category))]);
129    renderMenuItems(data);
130  } catch(_) { renderMenuFallback(); }
131}
132
133function renderMenuTabs(cats) {
134  const el = document.getElementById('menuTabs');
135  if (!el) return;
136  el.innerHTML = `<button class="menu-tab active" data-cat="all" onclick="filterMenu('all',this)">Todos</button>` +
137    cats.map(c => `<button class="menu-tab" data-cat="${c}" onclick="filterMenu('${c}',this)">${c}</button>`).join('');
138}
139
140function filterMenu(cat, btn) {
141  document.querySelectorAll('.menu-tab').forEach(t => t.classList.remove('active'));
142  btn.classList.add('active');
143  renderMenuItems(cat === 'all' ? allMenuItems : allMenuItems.filter(i => i.category === cat));
144}
145
146function renderMenuItems(items) {
147  const grid = document.getElementById('menuGrid');
148  if (!grid) return;
149  if (!items.length) { grid.innerHTML = `<p class="menu-empty">No hay platos en esta categoría</p>`; return; }
150  const waIcon = `<svg viewBox="0 0 24 24" style="width:14px;
150height:14px;fill:white;flex-shrink:0"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>`;
151  grid.innerHTML = items.map(item => `
152    <div class="menu-card">
153      ${item.image_url ? `<img class="menu-card-img" src="${item.image_url}" alt="${item.name}" loading="lazy">` : `<div class="menu-card-img-emoji">${item.emoji || '🍗'}</div>`}
154      <div class="menu-card-body">
155        <div class="menu-card-cat">${item.category}</div>
156        <div class="menu-card-name">${item.name}</div>
157        <div class="menu-card-desc">${item.description || ''}</div>
158        <div class="menu-card-footer">
159          <div class="menu-card-price"><small>S/</small> ${parseFloat(item.price).toFixed(2)}</div>
160          <button class="btn-agregar" onclick="pedirItem('${item.name}', ${item.price})">${waIcon} Pedir</button>
161        </div>
162      </div>
163    </div>`).join('');
164}
165
166function renderMenuFallback() {
167  const d = [
168    {category:'Pollos a la Brasa',name:'Pollo Entero',  description:'Con papas fritas, ensalada y salsas',price:65,emoji:'🍗'},
169    {category:'Pollos a la Brasa',name:'1/2 Pollo',     description:'Media pieza con papas y ensalada',   price:38,emoji:'🍗'},
170    {category:'Pollos a la Brasa',name:'1/4 Pollo',     description:'Un cuarto con guarnición',            price:22,emoji:'🍗'},
171    {category:'Combos',           name:'Combo Familiar', description:'1 Pollo + 4 gaseosas + papas',       price:85,emoji:'🎉'},
172    {category:'Combos',           name:'Combo Pareja',   description:'1/2 Pollo + 2 gaseosas + papas',    price:49,emoji:'💑'},
173    {category:'Entradas',         name:'Anticuchos',     description:'4 brochetas con choclo y papa',      price:18,emoji:'🔥'},
174    {category:'Entradas',         name:'Tequeños',       description:'6 unidades con dip de huancaína',    price:14,emoji:'🧀'},
175    {category:'Bebidas',          name:'Gaseosa Personal',description:'Coca-Cola, Inca Kola — 500ml',     price: 5,emoji:'🥤'},
176    {category:'Bebidas',          name:'Chicha Morada',  description:'Preparada en casa — 1 litro',       price: 9,emoji:'🫗'},
177    {category:'Postres',          name:'Tres Leches',    description:'Pastel tres leches artesanal',       price:12,emoji:'🍰'},
178  ];
179  allMenuItems = d;
180  renderMenuTabs([...new Set(d.map(i => i.category))]);
181  renderMenuItems(d);
182}
183
184function filterAndScroll(cat) {
185  document.getElementById('carta').scrollIntoView({ behavior: 'smooth' });
186  setTimeout(() => {
187    const btn = [...document.querySelectorAll('.menu-tab')].find(b => b.dataset.cat === cat);
188    if (btn) btn.click();
189  }, 700);
190}
191
192// ── WHATSAPP ──────────────────────────────────────────
193function openDelivery() {
194  window.open(`https://wa.me/${whatsappNumber}?text=${encodeURIComponent(msgDelivery)}`, '_blank');
195}
196function openWhatsApp(msg) {
197  window.open(`https://wa.me/${whatsappNumber}?text=${encodeURIComponent(msg || msgPedido)}`, '_blank');
198}
199function pedirItem(name, price) {
200  openWhatsApp(msgItem.replace('{plato}', name).replace('{precio}', parseFloat(price).toFixed(2)));
201}
202
203// ── RESERVA ───────────────────────────────────────────
204function sendReservation() {
205  const name    = document.getElementById('formName').value.trim();
206  const phone   = document.getElementById('formPhone').value.trim();
207  const date    = document.getElementById('formDate').value;
208  const persons = document.getElementById('formPersons').value;
209  const msg     = document.getElementById('formMessage').value.trim();
210  if (!name || !phone) { alert('Por favor ingresa tu nombre y teléfono'); return; }
211  openWhatsApp(
212    msgReserva
213      .replace('{nombre}', name).replace('{telefono}', phone)
214      .replace('{fecha}
214', date || 'Por coordinar').replace('{personas}', persons)
215      .replace('{mensaje}', msg || 'Sin mensaje adicional')
216  );
217}
218
219// ── NOSOTROS ──────────────────────────────────────────
220async function loadNosotrosPublic() {
221  try {
222    const { data } = await _supabase.from('site_config').select('*');
223    if (!data) return;
224    const cfg = {};
225    data.forEach(r => { if (r.key) cfg[r.key] = r.value; });
226
227    const set = (id, val) => { const el = document.getElementById(id); if (el && val) el.textContent = val; };
228    set('nosotrosIntro1', cfg.nosotros_intro_1);
229    set('nosotrosIntro2', cfg.nosotros_intro_2);
230    set('statAnios',    cfg.nosotros_stat_anios);
231    set('statLocales',  cfg.nosotros_stat_locales);
232    set('statClientes', cfg.nosotros_stat_clientes);
233
234    let premios = [];
235    try { premios = JSON.parse(cfg.nosotros_premios || '[]'); } catch(_) {}
236
237    const valores = (cfg.nosotros_valores || 'Verdad\nLealtad\nResponsabilidad\nRespeto\nCooperación\nComunicación')
238      .split('\n').filter(v => v.trim());
239
240    const mkBody = (text, ph) => !text || !text.trim()
241      ? `<p class="accordion-placeholder">${ph}</p>`
242      : text.trim().split('\n').map(p => p.trim() ? `<p>${p}</p>` : '').join('');
243
244    const waNum = cfg.whatsapp || DEFAULT_WHATSAPP;
245
246    const items = [
247      { id:'acc-historia', icon:'📖', label:'Historia',        content: mkBody(cfg.nosotros_historia,      '✏️ Próximamente — historia de la marca.') },
248      { id:'acc-vision',   icon:'🎯', label:'Visión',          content: mkBody(cfg.nosotros_vision,        '✏️ Próximamente.') },
249      { id:'acc-mision',   icon:'🚀', label:'Misión',          content: mkBody(cfg.nosotros_mision,        '✏️ Próximamente.') },
250      { id:'acc-valores',  icon:'💎', label:'Valores',         content: `<ul>${valores.map(v => `<li>${v.trim()}</li>`).join('')}</ul>` },
251      { id:'acc-cert',     icon:'🏅', label:'Certificaciones', content: mkBody(cfg.nosotros_certificaciones,'✏️ Próximamente.') },
252      { id:'acc-premios',  icon:'🏆', label:'Premios y Reconocimientos',
253        content: premios.length === 0
254          ? `<p class="accordion-placeholder">✏️ Próximamente — premios obtenidos.</p>`
255          : premios.map(p => `<div style="display:flex;gap:12px;align-items:flex-start;padding:10px 0;border-bottom:1px solid var(--border)">
256              <span style="font-size:1.6rem;flex-shrink:0">${p.emoji||'🏆'}</span>
257              <div>
258                <div style="font-weight:900;color:var(--text);font-size:0.92rem">${p.titulo}</div>
259                ${p.descripcion?`<div style="color:var(--gray);font-size:0.82rem;margin-top:3px">${p.descripcion}</div>`:''}
260                ${p.fecha?`<div style="color:var(--red);font-size:0.75rem;font-weight:700;margin-top:3px">📅 ${p.fecha}</div>`:''}
261              </div></div>`).join('')
262      },
263      { id:'acc-llamanos', icon:'📞', label:'Llámanos',
264        content: `<p style="font-size:1.3rem;font-weight:900;color:var(--text);margin-bottom:8px">📱 ${waNum.replace('51','')}</p>
265          <p>Número oficial de Fast Chicken AQP.</p>
266          <a href="https://wa.me/${waNum}?text=${encodeURIComponent(msgPedido)}" target="_blank" class="contact-link">💬 WhatsApp</a>`
267      },
268      { id:'acc-encuesta', icon:'📋', label:'Encuesta de Satisfacción',
269        content: cfg.nosotros_encuesta_url && cfg.nosotros_encuesta_url.startsWith('http')
270          ? `<p>Tu opinión nos ayuda a mejorar.</p><a href="${cfg.nosotros_encuesta_url}" target="_blank" class="contact-link">📝 Completar Encuesta</a>`
271          : `<p>Tu opinión nos ayuda a mejorar.</p><a href="#contacto" class="survey-link" onclick="document.getElementById('contacto').scrollIntoView({behavior:'smooth'})">📝 Ir al formulario</a>`
272      },
273      { id:'acc-trabajo', icon:'👔', label:'Trabaja con Nosotros', wide: true,
274        content: `<p>¿Te interesa ser parte del equipo? Envíanos tu CV.</p>
275          <a href="https://wa.me/${waNum}?text=${encodeURIComponent(msgTrabajo)}" target="_blank" class="contact-link">📨 Postular por WhatsApp</a>`
276      },
277    ];
278
279    const grid = document.getElementById('accordionGrid');
280    if (!grid) return;
281    grid.innerHTML = items.map(item => `
282      <div class="accordion-item" id="${item.id}" ${item.wide?'style="grid-column:1/-1"':''}>
283        <div class="accordion-header" onclick="toggleAccordion('${item.id}')">
284          <div class="accordion-header-left">
285            <span class="accordion-icon">${item.icon}</span>
286            <span class="accordion-label">${item.label}</span>
287          </div>
288          <div class="accordion-arrow">▼</div>
289        </div>
290        <div class="accordion-body">${item.content}</div>
291      </div>`).join('');
292  } catch(e) { console.error('nosotros:', e); }
293}
294
295// ── POPUP ─────────────────────────────────────────────
296async function loadPromoPopup() {
297  try {
298    const { data } = await _supabase.from('site_config').select('value')
299      .eq('key', 'popup_config').maybeSingle();
300    if (!data || !data.value) return;
301
302    const cfg = typeof data.value === 'string' ? JSON.parse(data.value) : data.value;
303    if (!cfg.active || !cfg.image_url) return;
304
305    // Frecuencia
306    const KEY = 'fca_popup';
307    const freq = cfg.frequency || 'session';
308    if (freq === 'session' && sessionStorage.getItem(KEY)) return;
309    if (freq === 'daily') {
310      const last = localStorage.getItem(KEY);
311      if (last && Date.now() - parseInt(last) < 86400000) return;
312    }
313
314    const popup = document.getElementById('promoPopup');
315    const img   = document.getElementById('popupImg');
316    if (!popup || !img) return;
317
318    img.src = cfg.image_url;
319    const delay = (cfg.delay_ms != null ? cfg.delay_ms : 1000);
320
321    setTimeout(() => {
322      popup.classList.add('open');
323      // Registrar AQUÍ, cuando el popup realmente se muestra
324      if (freq === 'session') sessionStorage.setItem(KEY, '1');
325      if (freq === 'daily') localStorage.setItem(KEY, String(Date.now()));
326      if (cfg.duracion_s > 0) {
327        setTimeout(() => popup.classList.remove('open'), cfg.duracion_s * 1000);
328      }
329    }, delay);
330  } catch(e) { console.warn('popup:', e); }
331}
332
333function closePopup(e) {
334  if (e && e.target !== e.currentTarget) return;
335  closePromoPopup();
336}
337function closePromoPopup() {
338  const p = document.getElementById('promoPopup');
339  if (p) p.classList.remove('open');
340}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.