Files
saborflow/js/main.js
Javier Blanco 3d353ed00f Redesign: Tropical Heat (Direction B) visual overhaul
Full aesthetic rewrite across all pages — new design system based on
the Claude Design handoff (Direction B). New color tokens, Bricolage
Grotesque + Caveat Brush typography, blob backgrounds, sticky blur nav,
responsive grid layouts, JS-driven schedule renderer, filter pills,
marquee, and connect page with ig grid and collab banner.
All internal Home links use href=/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 09:42:34 +07:00

150 lines
7.3 KiB
JavaScript

// ============================================================
// Sabor Flow Da Nang — main.js
// Update SCHEDULE array monthly when the weekly program changes.
// ============================================================
const SCHEDULE = [
{ day: 'Monday', when: '8:30 pm', venue: 'Webe Coffee', social: 'BOM', music: '8 Bachata · 1 Salsa · 1 Kizomba', city: 'Da Nang', organizer: 'Luu Phuong & Shai', mapUrl: 'https://maps.app.goo.gl/jvaQgcNt4doYwZ2N7' },
{ day: 'Tuesday', when: '9 pm', venue: 'La Riva', social: 'Latino Dancing', music: '2 Salsa · 2 Bachata · 2 Kizomba', city: 'Hoi An', organizer: 'David Tavares', mapUrl: 'https://www.google.com/maps/search/?api=1&query=La+Riva+An+Bang+Hoi+An+Vietnam' },
{ day: 'Tuesday', when: '9 pm', venue: 'Caliz Bar', social: 'Sensual Night', music: '3 Bachata · 2 Salsa · 2 Kizomba', city: 'Da Nang', organizer: 'Daisy Nguyen', mapUrl: 'https://www.google.com/maps/place/C%C3%A1liz+Wine+Bar/@16.0485026,108.2470699,17z' },
{ day: 'Wednesday', when: '7:30 pm', venue: 'Ket Fai Bar', social: 'Bachata Kiz Night', music: 'Kizomba 7:30 · Bachata 9 pm', city: 'Da Nang', organizer: 'Sean Kim', mapUrl: 'https://www.google.com/maps/search/?api=1&query=Ket+Fai+Bar+Da+Nang+Vietnam' },
{ day: 'Thursday', when: '9 pm', venue: 'Malibu', social: 'Salsa Nights', music: '2 Salsa · 2 Bachata', city: 'Da Nang', organizer: 'Lucho Giraldes', mapUrl: 'https://www.google.com/maps/search/?api=1&query=Malibu+Beach+Club+Da+Nang+Vietnam' },
{ day: 'Friday', when: '9 pm', venue: 'An Thuong By Night', social: 'Dance Unity Party', music: '2 Salsa · 2 Bachata · 2 Kizomba', city: 'Da Nang', organizer: 'Nadiya Yagfarova', mapUrl: 'https://www.google.com/maps/search/?api=1&query=An+Thuong+Tourist+Street+Da+Nang+Vietnam' },
{ day: 'Saturday', when: '9 pm', venue: 'La Riva', social: 'Latino Dancing', music: '2 Salsa · 2 Bachata · 2 Kizomba', city: 'Hoi An', organizer: 'David Tavares', mapUrl: 'https://www.google.com/maps/search/?api=1&query=La+Riva+An+Bang+Hoi+An+Vietnam' },
{ day: 'Saturday', when: '9 pm', venue: 'An Thuong By Night', social: 'Bachata Party', music: 'Only Bachata', city: 'Da Nang', organizer: 'Vaclav & Kseniya', mapUrl: 'https://www.google.com/maps/search/?api=1&query=An+Thuong+Tourist+Street+Da+Nang+Vietnam' },
{ day: 'Sunday', when: '9 pm', venue: 'Last Call', social: 'Latin Dance Social', music: '3 Bachata · 2 Salsa', city: 'Da Nang', organizer: 'Vaclav & Kseniya', mapUrl: 'https://www.google.com/maps/search/?api=1&query=Last+Call+Bar+Da+Nang+Vietnam' },
{ day: 'Sunday', when: '9 pm', venue: 'Corner Bar', social: 'Sunday Latin', music: '3 Bachata · 2 Salsa · 3 Kizomba', city: 'Da Nang', organizer: 'Daisy Nguyen', mapUrl: 'https://www.google.com/maps/search/?api=1&query=Corner+Bar+Da+Nang+Vietnam' },
];
const DAYS_ORDER = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
function getTodayName() {
return DAYS_ORDER[new Date().getDay()];
}
// Parse "2 Salsa · 3 Bachata · 1 Kizomba" into sfB-chip spans
function musicToChips(music) {
const styleMap = { salsa: 'salsa', bachata: 'bachata', kizomba: 'kizomba', zouk: 'zouk' };
return music.split('·').map(s => s.trim()).filter(Boolean).map(segment => {
let cls = '';
for (const [key, val] of Object.entries(styleMap)) {
if (segment.toLowerCase().includes(key)) { cls = val; break; }
}
return `<span class="sfB-chip ${cls}">${segment}</span>`;
}).join('');
}
// ---- Nav (mobile) ----
function initNav() {
const hamburger = document.getElementById('sfB-hamburger');
const mobileNav = document.getElementById('sfB-mobile-nav');
const closeBtn = document.getElementById('sfB-mobile-nav-close');
if (!hamburger || !mobileNav) return;
hamburger.addEventListener('click', () => mobileNav.classList.add('open'));
if (closeBtn) closeBtn.addEventListener('click', () => mobileNav.classList.remove('open'));
mobileNav.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => mobileNav.classList.remove('open'));
});
// close on backdrop click
mobileNav.addEventListener('click', e => {
if (e.target === mobileNav) mobileNav.classList.remove('open');
});
}
// ---- Schedule: full list (schedule.html) ----
function renderScheduleList() {
const list = document.getElementById('sfB-schedule-list');
if (!list) return;
const today = getTodayName();
list.innerHTML = SCHEDULE.map(row => {
const isToday = row.day === today;
const cityClass = row.city === 'Hoi An' ? 'hoian' : 'danang';
const venueEl = row.mapUrl
? `<a href="${row.mapUrl}" target="_blank" rel="noopener">${row.venue}</a>`
: row.venue;
return `
<div class="sfB-row${isToday ? ' today' : ''}">
<div class="col-day">
<span class="name">${row.day}</span>
<span class="when">${row.when || ''}</span>
${isToday ? '<span class="tonight-label">▶ Tonight</span>' : ''}
</div>
<div class="col-venue">
<span class="pin">📍</span>
${venueEl}
</div>
<div class="col-social">
${row.social}
<span class="sub">${row.organizer}</span>
</div>
<div class="col-music">${musicToChips(row.music)}</div>
<div><span class="col-city ${cityClass}">${row.city}</span></div>
<div class="col-org">${row.organizer}</div>
</div>`;
}).join('');
}
// ---- Home: "Coming up" teaser (4 cards starting from today) ----
function renderScheduleTeaser() {
const container = document.getElementById('sfB-coming-row');
if (!container) return;
const today = getTodayName();
const startIdx = DAYS_ORDER.indexOf(today);
const order = [...DAYS_ORDER.slice(startIdx), ...DAYS_ORDER.slice(0, startIdx)];
const sorted = [...SCHEDULE].sort(
(a, b) => order.indexOf(a.day) - order.indexOf(b.day)
);
container.innerHTML = sorted.slice(0, 4).map(row => `
<a href="schedule.html" class="sfB-tcard">
<div class="day">${row.day}</div>
<div class="when">${row.when || ''}</div>
<h4>${row.social}</h4>
<div class="at">@ ${row.venue} · ${row.city}</div>
<div class="chips">${musicToChips(row.music)}</div>
</a>
`).join('');
}
// ---- Filter pills (studios.html / classes.html) ----
function initFilters() {
const pills = document.querySelectorAll('.filter-pill');
if (!pills.length) return;
pills.forEach(pill => {
pill.addEventListener('click', () => {
pills.forEach(p => p.classList.remove('active'));
pill.classList.add('active');
const filter = pill.dataset.filter;
document.querySelectorAll('.filterable-card').forEach(card => {
if (filter === 'all') {
card.classList.remove('hidden');
} else {
const styles = (card.dataset.styles || '')
.split(',')
.map(s => s.trim().toLowerCase());
card.classList.toggle('hidden', !styles.includes(filter));
}
});
});
});
}
// ---- Init ----
document.addEventListener('DOMContentLoaded', () => {
initNav();
renderScheduleList();
renderScheduleTeaser();
initFilters();
});