feat(app): add initial Berlayar Sinema event landing page

This commit introduces the complete initial version of the "Berlayar Sinema" event landing page application. It includes the core HTML structure, CSS for styling, and JavaScript for interactivity.

Key features implemented:
- Responsive layout with Hero, Sponsors, and Film Screening sections.
- A registration form that submits data to a backend webhook.
- An interactive chat assistant powered by a separate webhook.
- Modals for successful registration and a promotional flyer popup.
- All necessary static assets, including images, logos, and posters.
This commit is contained in:
Emmanuel Rizky
2025-07-25 11:25:35 +07:00
commit bd83b83f25
16 changed files with 961 additions and 0 deletions

View File

@@ -0,0 +1,358 @@
document.addEventListener('DOMContentLoaded', () => {
const registrationForm = document.getElementById('eventRegistration');
const successModal = document.getElementById('successModal');
const closeModal = document.getElementById('closeModal');
const chatButton = document.getElementById('chatButton');
const chatWindow = document.getElementById('chatWindow');
const closeChat = document.getElementById('closeChat');
const imagePopup = document.getElementById('imagePopup');
const closeImagePopup = document.getElementById('closeImagePopup');
// Form submission handling
if (registrationForm) {
registrationForm.addEventListener('submit', function(event) {
event.preventDefault();
let isValid = true;
// Simple validation example
const fullName = document.getElementById('fullName');
const email = document.getElementById('email');
const phone = document.getElementById('phone');
const socialMedia = document.getElementById('socialMedia');
const origin = document.getElementById('origin');
const category = document.getElementById('category');
const discovery = document.getElementById('discovery');
const expectation = document.getElementById('expectation');
if (fullName.value.trim() === '') {
isValid = false;
alert('Please enter your full name.');
} else if (email.value.trim() === '' || !email.value.includes('@')) {
isValid = false;
alert('Please enter a valid email address.');
} else if (phone.value.trim() === '') {
isValid = false;
alert('Please enter your phone number.');
} else if (socialMedia.value.trim() === '') {
isValid = false;
alert('Please enter your social media account.');
} else if (origin.value.trim() === '') {
isValid = false;
alert('Please enter your origin.');
} else if (category.value === '') {
isValid = false;
alert('Please select a category.');
} else if (discovery.value.trim() === '') {
isValid = false;
alert('Please let us know how you discovered this event.');
} else if (expectation.value.trim() === '') {
isValid = false;
alert('Please enter your expectation.');
}
if (isValid) {
let phoneNumber = phone.value.trim();
if (phoneNumber.startsWith('0')) {
phoneNumber = '62' + phoneNumber.substring(1);
} else if (!phoneNumber.startsWith('62')) {
phoneNumber = '62' + phoneNumber;
}
const formData = {
Nama: fullName.value,
Email: email.value,
WhatsApp: phoneNumber,
Akun_Sosial_Media: socialMedia.value,
Asal_Komunitas: origin.value,
Kategori_Peserta: category.value,
Informasi_Event: discovery.value,
Harapan: expectation.value,
Pertanyaan_Diskusi: document.getElementById('discussionTopic').value
};
fetch('https://bot.kediritechnopark.com/webhook/form-berlayar-sinema', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Form submission successful:', data);
// Show success modal
if (successModal) {
successModal.classList.remove('hidden');
successModal.classList.add('flex');
}
// Reset form
registrationForm.reset();
})
.catch(error => {
console.error('Form submission failed:', error);
alert('Pendaftaran gagal. Silakan coba lagi nanti.');
});
// Show success modal
if (successModal) {
successModal.classList.remove('hidden');
successModal.classList.add('flex');
}
// Reset form
registrationForm.reset();
}
});
}
// Close success modal
if (closeModal) {
closeModal.addEventListener('click', () => {
if (successModal) {
successModal.classList.add('hidden');
successModal.classList.remove('flex');
}
});
}
// Image popup on page load
if (imagePopup) {
// Show the popup after a short delay
setTimeout(() => {
imagePopup.classList.remove('hidden');
imagePopup.classList.add('flex');
}, 1000); // Show after 1 second
// Close image popup
if (closeImagePopup) {
closeImagePopup.addEventListener('click', () => {
imagePopup.classList.add('hidden');
imagePopup.classList.remove('flex');
});
}
}
// Chat window functionality
if (chatButton && chatWindow && closeChat) {
// Film carousel functionality
const filmCarousel = document.getElementById('film-carousel');
const prevFilmButton = document.getElementById('prev-film');
const nextFilmButton = document.getElementById('next-film');
if (filmCarousel && prevFilmButton && nextFilmButton) {
let filmInterval;
const startFilmCarousel = () => {
if (window.innerWidth < 768) {
filmInterval = setInterval(() => {
if (filmCarousel.scrollLeft + filmCarousel.clientWidth >= filmCarousel.scrollWidth) {
filmCarousel.scrollTo({ left: 0, behavior: 'smooth' });
} else {
filmCarousel.scrollBy({ left: filmCarousel.clientWidth, behavior: 'smooth' });
}
}, 3000);
}
};
const stopFilmCarousel = () => {
clearInterval(filmInterval);
};
const setupFilmCarousel = () => {
stopFilmCarousel();
startFilmCarousel();
updateCarouselButtons();
};
const updateCarouselButtons = () => {
if (window.innerWidth < 768) {
prevFilmButton.classList.toggle('film-carousel-btn-hidden', filmCarousel.scrollLeft === 0);
nextFilmButton.classList.toggle('film-carousel-btn-hidden', filmCarousel.scrollLeft + filmCarousel.clientWidth >= filmCarousel.scrollWidth - 1);
} else {
prevFilmButton.classList.add('film-carousel-btn-hidden');
nextFilmButton.classList.add('film-carousel-btn-hidden');
}
};
filmCarousel.addEventListener('scroll', () => {
updateCarouselButtons();
});
nextFilmButton.addEventListener('click', () => {
stopFilmCarousel();
filmCarousel.scrollBy({ left: filmCarousel.clientWidth, behavior: 'smooth' });
startFilmCarousel();
});
prevFilmButton.addEventListener('click', () => {
stopFilmCarousel();
filmCarousel.scrollBy({ left: -filmCarousel.clientWidth, behavior: 'smooth' });
startFilmCarousel();
});
window.addEventListener('resize', setupFilmCarousel);
const equalizeCardHeights = () => {
if (window.innerWidth < 768) {
const filmCards = filmCarousel.querySelectorAll('.film-card');
let maxHeight = 0;
filmCards.forEach(card => {
card.style.height = 'auto';
if (card.offsetHeight > maxHeight) {
maxHeight = card.offsetHeight;
}
});
filmCards.forEach(card => {
card.style.height = `${maxHeight}px`;
});
} else {
const filmCards = filmCarousel.querySelectorAll('.film-card');
filmCards.forEach(card => {
card.style.height = '100%';
});
}
};
window.addEventListener('resize', () => {
setupFilmCarousel();
equalizeCardHeights();
});
setupFilmCarousel();
equalizeCardHeights();
}
const chatInput = chatWindow.querySelector('input[type="text"]');
const sendButton = chatWindow.querySelector('button.bg-yellow-500');
const chatMessagesContainer = chatWindow.querySelector('.p-4.h-64.overflow-y-auto');
let sessionId = null;
function generateUUID() { // Public Domain/MIT
var d = new Date().getTime();//Timestamp
var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16;//random number between 0 and 16
if(d > 0){//Use timestamp until depleted
r = (d + r)%16 | 0;
d = Math.floor(d/16);
} else {//Use microseconds since page-load if supported
r = (d2 + r)%16 | 0;
d2 = Math.floor(d2/16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
chatButton.addEventListener('click', () => {
chatWindow.classList.toggle('hidden');
if (!sessionId) {
sessionId = generateUUID();
console.log('Chat session started with ID:', sessionId);
}
});
closeChat.addEventListener('click', () => {
chatWindow.classList.add('hidden');
});
sendButton.addEventListener('click', () => {
sendMessage();
});
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
function sendMessage() {
const messageText = chatInput.value.trim();
if (messageText === '') return;
// Display sent message
const sentMessageDiv = document.createElement('div');
sentMessageDiv.className = 'mb-4 text-right';
sentMessageDiv.innerHTML = `<p class="chat-message-sent">${messageText}</p>`;
chatMessagesContainer.appendChild(sentMessageDiv);
chatInput.value = ''; // Clear input
// Show typing indicator
const typingIndicator = document.createElement('div');
typingIndicator.className = 'mb-4 text-left typing-indicator';
typingIndicator.innerHTML = '<span></span><span></span><span></span>';
chatMessagesContainer.appendChild(typingIndicator);
// Scroll to bottom
chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
// Send message to webhook
fetch('https://bot.kediritechnopark.com/webhook/chat-berlayar-sinema', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: messageText, sessionId: sessionId }),
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Chat message sent successfully:', data);
// Remove typing indicator
chatMessagesContainer.removeChild(typingIndicator);
// Display response from the webhook
if (data && data.output) {
const receivedMessageDiv = document.createElement('div');
receivedMessageDiv.className = 'mb-4 text-left';
receivedMessageDiv.innerHTML = `<p class="chat-message-received">${data.output}</p>`;
chatMessagesContainer.appendChild(receivedMessageDiv);
chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
}
})
.catch(error => {
console.error('Chat message failed to send:', error);
// Remove typing indicator
chatMessagesContainer.removeChild(typingIndicator);
// Optionally, display an error message in the chat
const errorMessageDiv = document.createElement('div');
errorMessageDiv.className = 'mb-4 text-left';
errorMessageDiv.innerHTML = `<p class="chat-message-received text-red-500">Error: Could not send message.</p>`;
chatMessagesContainer.appendChild(errorMessageDiv);
chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
});
}
}
// Observer to disable chat button when image popup is active
if (imagePopup && chatButton) {
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
if (!imagePopup.classList.contains('hidden')) {
chatButton.classList.add('opacity-50', 'cursor-not-allowed');
chatButton.disabled = true;
} else {
chatButton.classList.remove('opacity-50', 'cursor-not-allowed');
chatButton.disabled = false;
}
}
}
});
observer.observe(imagePopup, { attributes: true });
// Initial check
if (!imagePopup.classList.contains('hidden')) {
chatButton.classList.add('opacity-50', 'cursor-not-allowed');
chatButton.disabled = true;
}
}
});