This commit is contained in:
pika 2025-01-04 02:40:01 +01:00
parent 071a4d8a97
commit 4d3ca1c554
39 changed files with 3386 additions and 0 deletions

1760
static/css/style.css Normal file

File diff suppressed because it is too large Load diff

5
static/icons/de.svg Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
static/images/pizza.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

38
static/js/header.js Normal file
View 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
View 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
View 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
View 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');
}
});
});
});
});

View 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
View 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');
}
});