.
1760
static/css/style.css
Normal file
5
static/icons/de.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 3">
|
||||
<rect width="5" height="3" y="0" x="0" fill="#000"/>
|
||||
<rect width="5" height="2" y="1" x="0" fill="#D00"/>
|
||||
<rect width="5" height="1" y="2" x="0" fill="#FFCE00"/>
|
||||
</svg>
|
After Width: | Height: | Size: 234 B |
11
static/icons/gb.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30">
|
||||
<clipPath id="a"><path d="M0 0v30h60V0z"/></clipPath>
|
||||
<clipPath id="b"><path d="M30 15h30v15zv15H0zH0V0zV0h30z"/></clipPath>
|
||||
<g clip-path="url(#a)">
|
||||
<path d="M0 0v30h60V0z" fill="#012169"/>
|
||||
<path d="M0 0l60 30m0-30L0 30" stroke="#fff" stroke-width="6"/>
|
||||
<path d="M0 0l60 30m0-30L0 30" clip-path="url(#b)" stroke="#C8102E" stroke-width="4"/>
|
||||
<path d="M30 0v30M0 15h60" stroke="#fff" stroke-width="10"/>
|
||||
<path d="M30 0v30M0 15h60" stroke="#C8102E" stroke-width="6"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 566 B |
BIN
static/images/logo.webp
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
static/images/pizza-aussen.webp
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
static/images/pizza-innen.webp
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
static/images/pizza.webp
Normal file
After Width: | Height: | Size: 576 KiB |
BIN
static/images/team/Daisy.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/images/team/Luigi.webp
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
static/images/team/Wario.webp
Normal file
After Width: | Height: | Size: 101 KiB |
38
static/js/header.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const header = document.querySelector('.header');
|
||||
const navToggle = document.getElementById('nav-toggle');
|
||||
const navMenu = document.getElementById('nav-menu');
|
||||
|
||||
// Scroll behavior
|
||||
let lastScroll = 0;
|
||||
window.addEventListener('scroll', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
|
||||
if (currentScroll <= 0) {
|
||||
header.classList.remove('scrolled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentScroll > lastScroll && !header.contains(document.activeElement)) {
|
||||
header.style.transform = 'translateY(-100%)';
|
||||
} else {
|
||||
header.style.transform = 'translateY(0)';
|
||||
header.classList.add('scrolled');
|
||||
}
|
||||
lastScroll = currentScroll;
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!header.contains(e.target)) {
|
||||
navToggle.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu when window is resized to desktop
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth >= 1024) {
|
||||
navToggle.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
23
static/js/language.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Only run on the home page to avoid redirect loops
|
||||
if (window.location.pathname === '/') {
|
||||
const userLang = navigator.language || navigator.userLanguage;
|
||||
const currentLang = document.documentElement.lang;
|
||||
|
||||
// Simple language mapping
|
||||
const supportedLanguages = {
|
||||
'de': '/de/',
|
||||
'de-DE': '/de/',
|
||||
'de-AT': '/de/',
|
||||
'de-CH': '/de/',
|
||||
'en': '/en/',
|
||||
'en-US': '/en/',
|
||||
'en-GB': '/en/'
|
||||
};
|
||||
|
||||
// Check if user's language is different from current and is supported
|
||||
if (supportedLanguages[userLang] && currentLang !== userLang.substring(0,2)) {
|
||||
window.location.href = supportedLanguages[userLang];
|
||||
}
|
||||
}
|
||||
});
|
274
static/js/main.js
Normal file
|
@ -0,0 +1,274 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Mobile menu toggle
|
||||
const mobileMenuButton = document.querySelector('.mobile-menu-button');
|
||||
const mobileMenu = document.querySelector('.mobile-menu');
|
||||
|
||||
mobileMenuButton.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
|
||||
// Update button state
|
||||
mobileMenuButton.setAttribute('aria-expanded', !isExpanded);
|
||||
|
||||
// Toggle icons
|
||||
menuIconOpen.classList.toggle('hidden');
|
||||
menuIconClosed.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!mobileMenuButton.contains(e.target) && !mobileMenu.contains(e.target)) {
|
||||
mobileMenu.classList.add('hidden');
|
||||
mobileMenuButton.setAttribute('aria-expanded', 'false');
|
||||
menuIconOpen.classList.remove('hidden');
|
||||
menuIconClosed.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Theme toggle functionality
|
||||
function updateTheme(isDark) {
|
||||
const html = document.documentElement;
|
||||
if (isDark) {
|
||||
html.classList.add('dark');
|
||||
} else {
|
||||
html.classList.remove('dark');
|
||||
}
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
// Desktop theme toggle
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
darkModeToggle.addEventListener('click', () => {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
updateTheme(!isDark);
|
||||
});
|
||||
|
||||
// Mobile theme toggle
|
||||
const darkModeToggleMobile = document.getElementById('darkModeToggleMobile');
|
||||
darkModeToggleMobile.addEventListener('click', () => {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
updateTheme(!isDark);
|
||||
});
|
||||
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
updateTheme(savedTheme === 'dark');
|
||||
} else {
|
||||
// Default to dark theme
|
||||
updateTheme(true);
|
||||
}
|
||||
|
||||
// Header scroll behavior
|
||||
const header = document.querySelector('header');
|
||||
let lastScroll = 0;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
|
||||
if (currentScroll <= 0) {
|
||||
header.classList.remove('shadow-lg', 'bg-white/95', 'dark:bg-gray-900/95');
|
||||
header.style.transform = 'translateY(0)';
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentScroll > lastScroll && !header.contains(document.activeElement)) {
|
||||
// Scrolling down
|
||||
header.style.transform = 'translateY(-100%)';
|
||||
} else {
|
||||
// Scrolling up
|
||||
header.style.transform = 'translateY(0)';
|
||||
header.classList.add('shadow-lg', 'bg-white/95', 'dark:bg-gray-900/95');
|
||||
}
|
||||
|
||||
lastScroll = currentScroll;
|
||||
});
|
||||
|
||||
// Menu tab functionality
|
||||
const tabButtons = document.querySelectorAll('.tab-button');
|
||||
const menuContents = document.querySelectorAll('.menu-content');
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
// Remove active state from all buttons
|
||||
tabButtons.forEach(btn => {
|
||||
btn.classList.remove('bg-pizza-red', 'text-white');
|
||||
btn.classList.add('bg-gray-200', 'dark:bg-pizza-darker', 'text-gray-700', 'dark:text-white');
|
||||
});
|
||||
|
||||
// Add active state to clicked button
|
||||
button.classList.remove('bg-gray-200', 'dark:bg-pizza-darker', 'text-gray-700', 'dark:text-white');
|
||||
button.classList.add('bg-pizza-red', 'text-white');
|
||||
|
||||
// Hide all content sections
|
||||
menuContents.forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Show selected content
|
||||
const targetContent = document.getElementById(`${button.dataset.tab}-content`);
|
||||
targetContent.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Price group toggles
|
||||
const priceGroups = document.querySelectorAll('.price-group-header');
|
||||
|
||||
priceGroups.forEach(group => {
|
||||
group.addEventListener('click', () => {
|
||||
const content = group.nextElementSibling;
|
||||
const arrow = group.querySelector('svg');
|
||||
|
||||
// Toggle content visibility
|
||||
content.classList.toggle('hidden');
|
||||
|
||||
// Rotate arrow
|
||||
if (content.classList.contains('hidden')) {
|
||||
arrow.style.transform = 'rotate(0deg)';
|
||||
} else {
|
||||
arrow.style.transform = 'rotate(180deg)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-expand first price group
|
||||
const firstPriceGroup = document.querySelector('.price-group-header');
|
||||
if (firstPriceGroup) {
|
||||
firstPriceGroup.click();
|
||||
}
|
||||
|
||||
// Legend hover effect
|
||||
const legendContainer = document.querySelector('.legend-container');
|
||||
const legend = legendContainer.querySelector('.price-legend');
|
||||
|
||||
legendContainer.addEventListener('mouseenter', () => {
|
||||
legend.style.opacity = '0';
|
||||
});
|
||||
|
||||
legendContainer.addEventListener('mouseleave', () => {
|
||||
legend.style.opacity = '1';
|
||||
});
|
||||
|
||||
// Smooth scroll for menu links
|
||||
const menuLinks = document.querySelectorAll('a[href*="#menu-section"]');
|
||||
|
||||
menuLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
console.log('Menu link clicked'); // Debug log
|
||||
|
||||
const menuSection = document.getElementById('menu-section');
|
||||
if (menuSection) {
|
||||
const headerHeight = document.querySelector('header').offsetHeight;
|
||||
const menuPosition = menuSection.offsetTop - headerHeight;
|
||||
|
||||
window.scrollTo({
|
||||
top: menuPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
} else {
|
||||
console.error('Menu section not found'); // Debug log
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Language dropdown functionality
|
||||
const languageButton = document.getElementById('languageButton');
|
||||
const languageDropdown = document.getElementById('languageDropdown');
|
||||
|
||||
if (languageButton && languageDropdown) {
|
||||
// Toggle dropdown
|
||||
languageButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
languageDropdown.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!languageButton.contains(e.target) && !languageDropdown.contains(e.target)) {
|
||||
languageDropdown.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown when clicking a language option
|
||||
languageDropdown.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
languageDropdown.classList.add('hidden');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-detect browser language
|
||||
function detectLanguage() {
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
const supportedLangs = ['de', 'en']; // Add your supported languages here
|
||||
|
||||
if (!localStorage.getItem('userLanguage')) {
|
||||
const detectedLang = supportedLangs.includes(browserLang) ? browserLang : 'de';
|
||||
localStorage.setItem('userLanguage', detectedLang);
|
||||
|
||||
// Redirect if needed
|
||||
const currentLang = document.documentElement.lang;
|
||||
if (currentLang !== detectedLang) {
|
||||
const newPath = window.location.pathname.replace(`/${currentLang}/`, `/${detectedLang}/`);
|
||||
window.location.href = newPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detectLanguage();
|
||||
|
||||
function checkOpenStatus() {
|
||||
const now = new Date();
|
||||
const currentTime = now.toLocaleTimeString('de-DE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
const currentDay = now.toLocaleDateString('de-DE', { weekday: 'long' });
|
||||
|
||||
// Get opening hours from data attributes
|
||||
const hoursRows = document.querySelectorAll('.hours-row');
|
||||
let isOpen = false;
|
||||
|
||||
hoursRows.forEach(row => {
|
||||
const rowDay = row.dataset.day;
|
||||
const openTime = row.dataset.open;
|
||||
const closeTime = row.dataset.close;
|
||||
|
||||
// Convert times to comparable numbers (e.g., "14:30" -> 1430)
|
||||
const currentNum = parseInt(currentTime.replace(':', ''));
|
||||
const openNum = parseInt(openTime.replace(':', ''));
|
||||
const closeNum = parseInt(closeTime.replace(':', ''));
|
||||
|
||||
if (rowDay === currentDay && currentNum >= openNum && currentNum <= closeNum) {
|
||||
isOpen = true;
|
||||
row.classList.add('border-2', 'border-status-green');
|
||||
} else {
|
||||
row.classList.remove('border-2', 'border-status-green');
|
||||
}
|
||||
});
|
||||
|
||||
// Update status bar
|
||||
const statusBar = document.getElementById('status-bar');
|
||||
const statusText = document.getElementById('status-text');
|
||||
|
||||
if (statusBar && statusText) {
|
||||
if (isOpen) {
|
||||
statusBar.classList.remove('bg-status-red');
|
||||
statusBar.classList.add('bg-status-green');
|
||||
statusText.textContent = statusText.dataset.textOpen;
|
||||
} else {
|
||||
statusBar.classList.remove('bg-status-green');
|
||||
statusBar.classList.add('bg-status-red');
|
||||
statusText.textContent = statusText.dataset.textClosed;
|
||||
}
|
||||
}
|
||||
|
||||
return { isOpen, currentDay, currentTime };
|
||||
}
|
||||
|
||||
// Update status every minute
|
||||
setInterval(checkOpenStatus, 60000);
|
||||
// Initial check
|
||||
checkOpenStatus();
|
||||
});
|
29
static/js/menu.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Menu category handling
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Handle tab switching
|
||||
const allTabButtons = document.querySelectorAll('[data-tab]');
|
||||
allTabButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const tabId = this.getAttribute('data-tab');
|
||||
|
||||
// Hide all content
|
||||
document.querySelectorAll('.menu-content').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Show selected content
|
||||
document.getElementById(tabId + '-content').classList.remove('hidden');
|
||||
|
||||
// Update active states for all buttons with same data-tab
|
||||
allTabButtons.forEach(btn => {
|
||||
if (btn.getAttribute('data-tab') === tabId) {
|
||||
btn.classList.add('bg-pizza-red', 'text-white');
|
||||
btn.classList.remove('bg-gray-200', 'dark:bg-pizza-darker', 'text-gray-700');
|
||||
} else {
|
||||
btn.classList.remove('bg-pizza-red', 'text-white');
|
||||
btn.classList.add('bg-gray-200', 'dark:bg-pizza-darker', 'text-gray-700');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
49
static/js/opening-status.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
function updateTime() {
|
||||
const now = new Date();
|
||||
const currentTime = now.toLocaleTimeString('de-DE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
|
||||
// Update displayed time
|
||||
const timeDisplay = document.getElementById('current-time');
|
||||
if (timeDisplay) {
|
||||
timeDisplay.textContent = currentTime;
|
||||
}
|
||||
|
||||
// Check opening status
|
||||
fetch('/api/check-open-status')
|
||||
.then(response => response.json())
|
||||
.then(status => {
|
||||
// Update status bar
|
||||
const statusBar = document.getElementById('status-bar');
|
||||
const statusText = document.getElementById('status-text');
|
||||
|
||||
if (statusBar) {
|
||||
statusBar.className = status.isOpen
|
||||
? 'bg-status-green transition-colors duration-300'
|
||||
: 'bg-status-red transition-colors duration-300';
|
||||
}
|
||||
|
||||
if (statusText) {
|
||||
const textOpen = statusText.dataset.textOpen;
|
||||
const textClosed = statusText.dataset.textClosed;
|
||||
statusText.textContent = status.isOpen ? textOpen : textClosed;
|
||||
}
|
||||
|
||||
// Update opening hours rows
|
||||
document.querySelectorAll('.hours-row').forEach(row => {
|
||||
const open = row.dataset.open;
|
||||
const close = row.dataset.close;
|
||||
const isCurrentTimeSlot = currentTime >= open && currentTime <= close;
|
||||
|
||||
row.classList.toggle('border-2', isCurrentTimeSlot && status.isOpen);
|
||||
row.classList.toggle('border-status-green', isCurrentTimeSlot && status.isOpen);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update immediately and then every minute
|
||||
updateTime();
|
||||
setInterval(updateTime, 60000);
|
42
static/js/theme.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Theme handling
|
||||
function getPreferredTheme() {
|
||||
if (localStorage.getItem('theme') === 'dark' ||
|
||||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
return 'dark';
|
||||
}
|
||||
return 'light';
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
localStorage.setItem('theme', theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
setTheme(isDark ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
// Initialize theme
|
||||
if (getPreferredTheme() === 'dark') {
|
||||
setTheme('dark');
|
||||
}
|
||||
|
||||
// Add click event listener
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
if (darkModeToggle) {
|
||||
darkModeToggle.addEventListener('click', toggleTheme);
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for system theme changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||
if (!localStorage.getItem('theme')) {
|
||||
setTheme(e.matches ? 'dark' : 'light');
|
||||
}
|
||||
});
|