.
61
assets/css/main.css
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--primary-color: theme('colors.primary.DEFAULT');
|
||||||
|
--accent-color: theme('colors.accent.DEFAULT');
|
||||||
|
--text-color: theme('colors.gray.800');
|
||||||
|
--background-color: theme('colors.white');
|
||||||
|
--card-background: theme('colors.white');
|
||||||
|
--header-background: theme('colors.white');
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--primary-color: theme('colors.primary.400');
|
||||||
|
--accent-color: theme('colors.accent.400');
|
||||||
|
--text-color: theme('colors.gray.200');
|
||||||
|
--background-color: theme('colors.gray.900');
|
||||||
|
--card-background: theme('colors.gray.800');
|
||||||
|
--header-background: theme('colors.gray.900');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-in-left {
|
||||||
|
animation: slideInLeft 1s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-in-right {
|
||||||
|
animation: slideInRight 1s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
44
assets/js/menu.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Menu category switching
|
||||||
|
const tabButtons = document.querySelectorAll('[data-tab]');
|
||||||
|
const menuContents = document.querySelectorAll('.menu-content');
|
||||||
|
|
||||||
|
tabButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const targetId = `${button.dataset.tab}-content`;
|
||||||
|
|
||||||
|
// Update button styles
|
||||||
|
tabButtons.forEach(btn => {
|
||||||
|
const isActive = btn === button;
|
||||||
|
btn.classList.toggle('bg-pizza-red', isActive);
|
||||||
|
btn.classList.toggle('text-white', isActive);
|
||||||
|
btn.classList.toggle('bg-gray-200', !isActive);
|
||||||
|
btn.classList.toggle('dark:bg-pizza-darker', !isActive);
|
||||||
|
btn.classList.toggle('text-gray-700', !isActive);
|
||||||
|
btn.classList.toggle('dark:text-white', !isActive);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show/hide content
|
||||||
|
menuContents.forEach(content => {
|
||||||
|
content.classList.toggle('hidden', content.id !== targetId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pizza price group toggles
|
||||||
|
const priceGroupHeaders = document.querySelectorAll('.price-group-header');
|
||||||
|
priceGroupHeaders.forEach(header => {
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
const content = header.nextElementSibling;
|
||||||
|
const arrow = header.querySelector('svg');
|
||||||
|
|
||||||
|
// Toggle content visibility
|
||||||
|
content.classList.toggle('hidden');
|
||||||
|
|
||||||
|
// Rotate arrow
|
||||||
|
arrow.style.transform = content.classList.contains('hidden')
|
||||||
|
? 'rotate(0deg)'
|
||||||
|
: 'rotate(180deg)';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
95
assets/js/opening-status.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
function updateTimeAndStatus() {
|
||||||
|
const now = new Date();
|
||||||
|
const timeString = now.toLocaleTimeString('de-DE', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update time display
|
||||||
|
const timeDisplay = document.getElementById('current-time');
|
||||||
|
if (timeDisplay) {
|
||||||
|
timeDisplay.textContent = timeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/api/check-open-status')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(status => {
|
||||||
|
const statusBar = document.getElementById('status-bar');
|
||||||
|
const statusText = document.getElementById('status-text');
|
||||||
|
const deliveryText = document.querySelector('.delivery-text');
|
||||||
|
|
||||||
|
if (statusBar && statusText) {
|
||||||
|
statusBar.className = status.isOpen
|
||||||
|
? 'bg-status-green transition-colors duration-300'
|
||||||
|
: 'bg-status-red transition-colors duration-300';
|
||||||
|
|
||||||
|
const textOpen = statusText.dataset.textOpen;
|
||||||
|
const textClosed = statusText.dataset.textClosed;
|
||||||
|
statusText.textContent = status.isOpen ? textOpen : textClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug information
|
||||||
|
const debugInfo = document.getElementById('debug-info');
|
||||||
|
if (debugInfo && !status.isOpen) {
|
||||||
|
const nextOpen = document.getElementById('next-open');
|
||||||
|
const currentDay = document.getElementById('current-day');
|
||||||
|
|
||||||
|
if (currentDay) {
|
||||||
|
currentDay.textContent = now.toLocaleDateString('de-DE', { weekday: 'long' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextOpen) {
|
||||||
|
const hoursRows = document.querySelectorAll('.hours-row');
|
||||||
|
let nextOpenTime = null;
|
||||||
|
let foundToday = false;
|
||||||
|
|
||||||
|
// First check today's remaining times
|
||||||
|
hoursRows.forEach(row => {
|
||||||
|
if (row.dataset.day === status.currentDay) {
|
||||||
|
const openTime = row.dataset.open;
|
||||||
|
if (openTime > timeString) {
|
||||||
|
nextOpenTime = `Today at ${openTime}`;
|
||||||
|
foundToday = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no times found today, find next day's opening
|
||||||
|
if (!foundToday) {
|
||||||
|
hoursRows.forEach(row => {
|
||||||
|
const dayIndex = parseInt(row.dataset.dayIndex || 0);
|
||||||
|
const currentDayIndex = now.getDay();
|
||||||
|
|
||||||
|
if (dayIndex > currentDayIndex || (dayIndex === 0 && currentDayIndex !== 0)) {
|
||||||
|
const openTime = row.dataset.open;
|
||||||
|
const dayName = row.dataset.dayName;
|
||||||
|
if (!nextOpenTime) {
|
||||||
|
nextOpenTime = `${dayName} at ${openTime}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextOpen.textContent = nextOpenTime || 'Check opening hours';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update other elements
|
||||||
|
if (deliveryText) {
|
||||||
|
deliveryText.className = status.isOpen
|
||||||
|
? 'delivery-text text-status-green'
|
||||||
|
: 'delivery-text text-status-red';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.hours-row').forEach(row => {
|
||||||
|
const isCurrentDay = row.dataset.day === status.currentDay;
|
||||||
|
row.classList.toggle('border-2', isCurrentDay && status.isOpen);
|
||||||
|
row.classList.toggle('border-status-green', isCurrentDay && status.isOpen);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update immediately and then every minute
|
||||||
|
updateTimeAndStatus();
|
||||||
|
setInterval(updateTimeAndStatus, 60000);
|
60
layouts/404.html
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-white dark:bg-pizza-dark p-4">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-9xl font-bold tracking-widest text-gray-900 dark:text-white">
|
||||||
|
4
|
||||||
|
<span class="inline-block animate-spin-slow origin-center">
|
||||||
|
<svg class="w-32 h-32 text-pizza-red" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="45" fill="currentColor"/>
|
||||||
|
<!-- Pepperoni -->
|
||||||
|
<circle cx="30" cy="35" r="8" fill="#8B0000"/>
|
||||||
|
<circle cx="60" cy="40" r="8" fill="#8B0000"/>
|
||||||
|
<circle cx="45" cy="65" r="8" fill="#8B0000"/>
|
||||||
|
<!-- Cheese drips -->
|
||||||
|
<path d="M20,50 Q30,70 40,50" fill="none" stroke="#FFD700" stroke-width="3"/>
|
||||||
|
<path d="M60,50 Q70,75 80,50" fill="none" stroke="#FFD700" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
4
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="mt-8 space-y-4">
|
||||||
|
<p class="text-2xl font-medium text-gray-600 dark:text-gray-300">
|
||||||
|
{{ i18n "404_oops" | default "Oops! Looks like this slice is missing!" }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">
|
||||||
|
{{ i18n "404_text" | default "The page you're looking for has been eaten or never existed." }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Animated delivery guy -->
|
||||||
|
<div class="mt-12 relative">
|
||||||
|
<div class="animate-drive-by">
|
||||||
|
<svg class="w-24 h-24 mx-auto text-pizza-red" viewBox="0 0 100 100">
|
||||||
|
<!-- Scooter -->
|
||||||
|
<path d="M10,70 Q30,65 50,70 Q70,75 90,70" fill="none" stroke="currentColor" stroke-width="3"/>
|
||||||
|
<circle cx="25" cy="80" r="10" fill="currentColor"/>
|
||||||
|
<circle cx="75" cy="80" r="10" fill="currentColor"/>
|
||||||
|
<!-- Driver -->
|
||||||
|
<circle cx="50" cy="40" r="15" fill="currentColor"/>
|
||||||
|
<path d="M40,55 L60,55 L50,70 Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12">
|
||||||
|
<a href="/"
|
||||||
|
class="inline-flex items-center px-6 py-3 text-lg font-medium text-white
|
||||||
|
bg-pizza-red rounded-full hover:bg-red-600
|
||||||
|
transform hover:scale-105 transition-all duration-200
|
||||||
|
animate-bounce-subtle">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||||
|
</svg>
|
||||||
|
{{ i18n "404_back_home" | default "Back to Home" }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
26
layouts/_default/baseof.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ .Site.Language.Lang }}" class="scroll-smooth dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} - {{ .Site.Title }}{{ end }}</title>
|
||||||
|
<link rel="stylesheet" href="{{ "css/style.css" | relURL }}">
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen bg-white text-gray-900 dark:bg-pizza-dark dark:text-white">
|
||||||
|
{{ partial "header.html" . }}
|
||||||
|
<main class="pt-24">
|
||||||
|
{{ block "main" . }}{{ end }}
|
||||||
|
</main>
|
||||||
|
{{ partial "footer.html" . }}
|
||||||
|
|
||||||
|
<!-- JavaScript -->
|
||||||
|
<script src="{{ "js/main.js" | relURL }}"></script>
|
||||||
|
<script src="{{ "js/theme.js" | relURL }}" defer></script>
|
||||||
|
<script src="/js/menu.js"></script>
|
||||||
|
{{ with resources.Get "js/opening-status.js" }}
|
||||||
|
{{ with . | resources.Minify }}
|
||||||
|
<script src="{{ .RelPermalink }}"></script>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</body>
|
||||||
|
</html>
|
5
layouts/_default/check-open-status.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"isOpen": {{ $status := partial "check-open-status.html" . }}{{ $status.isOpen }},
|
||||||
|
"currentDay": "{{ now.Format "Monday" }}",
|
||||||
|
"currentTime": "{{ now.Format "15:04" }}"
|
||||||
|
}
|
12
layouts/_default/list.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<h1>{{ .Title }}</h1>
|
||||||
|
<div class="posts-list">
|
||||||
|
{{ range .Pages }}
|
||||||
|
<article class="post-preview">
|
||||||
|
<h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
|
||||||
|
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||||
|
<p>{{ .Summary }}</p>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
9
layouts/_default/single.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<article>
|
||||||
|
<h1>{{ .Title }}</h1>
|
||||||
|
<time>{{ .Date.Format "January 2, 2006" }}</time>
|
||||||
|
<div class="content">
|
||||||
|
{{ .Content }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
5
layouts/_default/single.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"layout": "{{ .Layout }}",
|
||||||
|
"title": "{{ .Title }}",
|
||||||
|
"type": "{{ .Type }}"
|
||||||
|
}
|
105
layouts/about/about.html
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="min-h-screen">
|
||||||
|
<!-- Hero Section with Background -->
|
||||||
|
<div class="relative h-[40vh] mb-16">
|
||||||
|
<div class="absolute inset-0 bg-[url('/images/pizza-hero.jpg')] bg-cover bg-center">
|
||||||
|
<div class="absolute inset-0 bg-black/50"></div>
|
||||||
|
</div>
|
||||||
|
<div class="relative container mx-auto h-full flex items-center justify-center px-4">
|
||||||
|
<div class="text-center text-white space-y-4">
|
||||||
|
<h1 class="text-5xl md:text-6xl font-bold">{{ .Site.Data.about.hero.title }}</h1>
|
||||||
|
<p class="text-xl md:text-2xl max-w-2xl mx-auto">{{ .Site.Data.about.hero.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Story Section -->
|
||||||
|
<div class="container mx-auto px-4 py-12">
|
||||||
|
<div class="max-w-4xl mx-auto space-y-16">
|
||||||
|
<!-- Our Story -->
|
||||||
|
<div class="grid md:grid-cols-2 gap-8 items-center">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 dark:text-white">{{ .Site.Data.about.story.title }}</h2>
|
||||||
|
<div class="prose dark:prose-invert">
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-300">
|
||||||
|
{{ .Site.Data.about.story.content }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-3">
|
||||||
|
{{ range .Site.Data.about.story.features }}
|
||||||
|
<li class="flex items-center space-x-3 text-gray-600 dark:text-gray-300">
|
||||||
|
<svg class="w-5 h-5 text-pizza-red" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/>
|
||||||
|
</svg>
|
||||||
|
<span>{{ . }}</span>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="relative h-80 rounded-xl overflow-hidden">
|
||||||
|
<img src="/images/pizza-innen.webp" alt="Restaurant Interior"
|
||||||
|
class="absolute inset-0 w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Team Section -->
|
||||||
|
<div class="space-y-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">{{ .Site.Data.about.team.title }}</h2>
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
|
||||||
|
{{ .Site.Data.about.team.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{{ range .Site.Data.about.team.members }}
|
||||||
|
<div class="group">
|
||||||
|
<div class="relative overflow-hidden rounded-xl aspect-square mb-4">
|
||||||
|
<img src="/images/{{ .image }}" alt="{{ .name }}"
|
||||||
|
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ .name }}</h3>
|
||||||
|
<p class="text-pizza-red">{{ .role }}</p>
|
||||||
|
<p class="mt-2 text-gray-600 dark:text-gray-300">
|
||||||
|
{{ .description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Section -->
|
||||||
|
<div class="bg-white/5 dark:bg-pizza-darker/5 backdrop-blur-sm rounded-xl p-8 shadow-lg">
|
||||||
|
<div class="grid md:grid-cols-2 gap-8">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ .Site.Data.about.contact.title }}</h3>
|
||||||
|
<div class="space-y-2 text-gray-600 dark:text-gray-300">
|
||||||
|
<p class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 text-pizza-red" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||||
|
</svg>
|
||||||
|
{{ .Site.Data.about.contact.address }}
|
||||||
|
</p>
|
||||||
|
<p class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 text-pizza-red" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
|
||||||
|
</svg>
|
||||||
|
{{ .Site.Params.contact.phone }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-64 rounded-xl overflow-hidden">
|
||||||
|
<img src="/images/pizza-aussen.webp" alt="Restaurant Exterior"
|
||||||
|
class="w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
105
layouts/en/about/about.html
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="min-h-screen">
|
||||||
|
<!-- Hero Section with Background -->
|
||||||
|
<div class="relative h-[40vh] mb-16">
|
||||||
|
<div class="absolute inset-0 bg-[url('/images/pizza-hero.jpg')] bg-cover bg-center">
|
||||||
|
<div class="absolute inset-0 bg-black/50"></div>
|
||||||
|
</div>
|
||||||
|
<div class="relative container mx-auto h-full flex items-center justify-center px-4">
|
||||||
|
<div class="text-center text-white space-y-4">
|
||||||
|
<h1 class="text-5xl md:text-6xl font-bold">{{ .Site.Data.en.about.hero.title }}</h1>
|
||||||
|
<p class="text-xl md:text-2xl max-w-2xl mx-auto">{{ .Site.Data.en.about.hero.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Story Section -->
|
||||||
|
<div class="container mx-auto px-4 py-12">
|
||||||
|
<div class="max-w-4xl mx-auto space-y-16">
|
||||||
|
<!-- Our Story -->
|
||||||
|
<div class="grid md:grid-cols-2 gap-8 items-center">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 dark:text-white">{{ .Site.Data.en.about.story.title }}</h2>
|
||||||
|
<div class="prose dark:prose-invert">
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-300">
|
||||||
|
{{ .Site.Data.en.about.story.content }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-3">
|
||||||
|
{{ range .Site.Data.en.about.story.features }}
|
||||||
|
<li class="flex items-center space-x-3 text-gray-600 dark:text-gray-300">
|
||||||
|
<svg class="w-5 h-5 text-pizza-red" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/>
|
||||||
|
</svg>
|
||||||
|
<span>{{ . }}</span>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="relative h-80 rounded-xl overflow-hidden">
|
||||||
|
<img src="/images/pizza-innen.webp" alt="Restaurant Interior"
|
||||||
|
class="absolute inset-0 w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Team Section -->
|
||||||
|
<div class="space-y-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">{{ .Site.Data.en.about.team.title }}</h2>
|
||||||
|
<p class="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
|
||||||
|
{{ .Site.Data.en.about.team.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{{ range .Site.Data.en.about.team.members }}
|
||||||
|
<div class="group">
|
||||||
|
<div class="relative overflow-hidden rounded-xl aspect-square mb-4">
|
||||||
|
<img src="/images/{{ .image }}" alt="{{ .name }}"
|
||||||
|
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ .name }}</h3>
|
||||||
|
<p class="text-pizza-red">{{ .role }}</p>
|
||||||
|
<p class="mt-2 text-gray-600 dark:text-gray-300">
|
||||||
|
{{ .description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Section -->
|
||||||
|
<div class="bg-white/5 dark:bg-pizza-darker/5 backdrop-blur-sm rounded-xl p-8 shadow-lg">
|
||||||
|
<div class="grid md:grid-cols-2 gap-8">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ .Site.Data.en.about.contact.title }}</h3>
|
||||||
|
<div class="space-y-2 text-gray-600 dark:text-gray-300">
|
||||||
|
<p class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 text-pizza-red" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||||
|
</svg>
|
||||||
|
{{ .Site.Data.en.about.contact.address }}
|
||||||
|
</p>
|
||||||
|
<p class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 text-pizza-red" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
|
||||||
|
</svg>
|
||||||
|
{{ .Site.Params.contact.phone }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-64 rounded-xl overflow-hidden">
|
||||||
|
<img src="/images/pizza-aussen.webp" alt="Restaurant Exterior"
|
||||||
|
class="w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
25
layouts/en/index.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="min-h-screen bg-white dark:bg-pizza-dark text-gray-900 dark:text-white">
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
|
||||||
|
<!-- Opening Hours Card -->
|
||||||
|
<div class="bg-white dark:bg-pizza-darker rounded-xl p-6 shadow-lg lg:-ml-16 lg:-mt-8 relative z-10 slide-in-left">
|
||||||
|
{{ partial "opening-hours.html" . }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pizza Image -->
|
||||||
|
<div class="rounded-xl overflow-hidden shadow-lg slide-in-right">
|
||||||
|
<img src="/images/pizza.webp" alt="Delicious Pizza" class="w-full h-full object-cover transform hover:scale-105 transition-transform duration-700">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Section -->
|
||||||
|
{{ partial "menu-card.html" . }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
{{ $menu := resources.Get "js/menu.js" | resources.Minify | resources.Fingerprint }}
|
||||||
|
<script src="{{ $menu.RelPermalink }}" integrity="{{ $menu.Data.Integrity }}"></script>
|
||||||
|
{{ end }}
|
26
layouts/index.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="min-h-screen bg-white dark:bg-pizza-dark text-gray-900 dark:text-white">
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
|
||||||
|
<!-- Opening Hours Card -->
|
||||||
|
<div class="bg-white dark:bg-pizza-darker rounded-xl p-6 shadow-lg lg:-ml-16 lg:-mt-8 relative z-10 slide-in-left">
|
||||||
|
{{ partial "opening-hours.html" . }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pizza Image -->
|
||||||
|
<div class="rounded-xl overflow-hidden shadow-lg slide-in-right">
|
||||||
|
<img src="/images/pizza.webp" alt="Delicious Pizza" class="w-full h-full object-cover transform hover:scale-105 transition-transform duration-700">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Section -->
|
||||||
|
{{ partial "menu-card.html" . }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
{{ $menu := resources.Get "js/menu.js" | resources.Minify | resources.Fingerprint }}
|
||||||
|
<script src="{{ $menu.RelPermalink }}" integrity="{{ $menu.Data.Integrity }}"></script>
|
||||||
|
{{ end }}
|
32
layouts/partials/check-open-status.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{{ $currentTime := now.Format "15:04" }}
|
||||||
|
{{ $currentDay := now.Format "Monday" }}
|
||||||
|
{{ $isOpen := false }}
|
||||||
|
{{ $holidayInfo := dict }}
|
||||||
|
{{ $isHoliday := false }}
|
||||||
|
|
||||||
|
<!-- Check if today is a holiday -->
|
||||||
|
{{ range .Site.Data.hours.holidays }}
|
||||||
|
{{ $holidayDate := time .date }}
|
||||||
|
{{ if eq (now.Format "2006-01-02") ($holidayDate.Format "2006-01-02") }}
|
||||||
|
{{ $isHoliday = true }}
|
||||||
|
{{ $holidayInfo = . }}
|
||||||
|
{{ if not .closed }}
|
||||||
|
{{ if and (ge $currentTime .open) (le $currentTime .close) }}
|
||||||
|
{{ $isOpen = true }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<!-- If not a holiday, check regular hours -->
|
||||||
|
{{ if not $isHoliday }}
|
||||||
|
{{ range .Site.Data.hours.regular_hours }}
|
||||||
|
{{ if in .days $currentDay }}
|
||||||
|
{{ if and (ge $currentTime .open) (le $currentTime .close) }}
|
||||||
|
{{ $isOpen = true }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ return dict "isOpen" $isOpen "isHoliday" $isHoliday "holidayInfo" $holidayInfo "currentDay" $currentDay "currentTime" $currentTime }}
|
13
layouts/partials/components/language-switcher.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="{{ .Class }}">
|
||||||
|
{{ if eq .Site.Language.Lang "en" }}
|
||||||
|
<a href="/de/" class="lang-btn">
|
||||||
|
<img src="/icons/de.svg" alt="Deutsch" class="flag-icon">
|
||||||
|
<span>DE</span>
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="/en/" class="lang-btn">
|
||||||
|
<img src="/icons/gb.svg" alt="English" class="flag-icon">
|
||||||
|
<span>EN</span>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
70
layouts/partials/components/theme-switcher.html
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<!-- theme switcher -->
|
||||||
|
{{ $class := .Class }}
|
||||||
|
{{ if site.Params.theme_switcher }}
|
||||||
|
<div class="theme-switcher {{ $class }} hidden">
|
||||||
|
<input id="theme-switcher" data-theme-switcher type="checkbox" />
|
||||||
|
<label for="theme-switcher">
|
||||||
|
<span class="sr-only">theme switcher</span>
|
||||||
|
<span>
|
||||||
|
<!-- sun -->
|
||||||
|
<svg
|
||||||
|
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10 opacity-100 dark:opacity-0"
|
||||||
|
viewBox="0 0 56 56"
|
||||||
|
fill="#fff"
|
||||||
|
height="16"
|
||||||
|
width="16">
|
||||||
|
<path
|
||||||
|
d="M30 4.6c0-1-.9-2-2-2a2 2 0 0 0-2 2v5c0 1 .9 2 2 2s2-1 2-2Zm9.6 9a2 2 0 0 0 0 2.8c.8.8 2 .8 2.9 0L46 13a2 2 0 0 0 0-2.9 2 2 0 0 0-3 0Zm-26 2.8c.7.8 2 .8 2.8 0 .8-.7.8-2 0-2.9L13 10c-.7-.7-2-.8-2.9 0-.7.8-.7 2.1 0 3ZM28 16a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12Zm23.3 14c1.1 0 2-.9 2-2s-.9-2-2-2h-4.9a2 2 0 0 0-2 2c0 1.1 1 2 2 2ZM4.7 26a2 2 0 0 0-2 2c0 1.1.9 2 2 2h4.9c1 0 2-.9 2-2s-1-2-2-2Zm37.8 13.6a2 2 0 0 0-3 0 2 2 0 0 0 0 2.9l3.6 3.5a2 2 0 0 0 2.9 0c.8-.8.8-2.1 0-3ZM10 43.1a2 2 0 0 0 0 2.9c.8.7 2.1.8 3 0l3.4-3.5c.8-.8.8-2.1 0-2.9-.8-.8-2-.8-2.9 0Zm20 3.4c0-1.1-.9-2-2-2a2 2 0 0 0-2 2v4.9c0 1 .9 2 2 2s2-1 2-2Z" />
|
||||||
|
</svg>
|
||||||
|
<!-- moon -->
|
||||||
|
<svg
|
||||||
|
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10 opacity-0 dark:opacity-100"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
height="16"
|
||||||
|
width="16">
|
||||||
|
<path
|
||||||
|
fill="#000"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M8.2 2.2c1-.4 2 .6 1.6 1.5-1 3-.4 6.4 1.8 8.7a8.4 8.4 0 0 0 8.7 1.8c1-.3 2 .5 1.5 1.5v.1a10.3 10.3 0 0 1-9.4 6.2A10.3 10.3 0 0 1 3.2 6.7c1-2 2.9-3.5 4.9-4.4Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- theme switcher -->
|
||||||
|
<script>
|
||||||
|
var darkMode = {{if eq site.Params.theme_default "dark"}}true{{else}}false{{end}};
|
||||||
|
|
||||||
|
{{ if eq site.Params.theme_default "system" }}
|
||||||
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches){darkMode = true}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
if (localStorage.getItem("theme") === "dark"){darkMode = true}
|
||||||
|
else if (localStorage.getItem("theme") === "light"){darkMode = false}
|
||||||
|
|
||||||
|
if (darkMode){document.documentElement.classList.add("dark")}
|
||||||
|
else {document.documentElement.classList.remove("dark")}
|
||||||
|
|
||||||
|
// Show theme switcher after applying theme
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
var themeSwitch = document.querySelectorAll("[data-theme-switcher]");
|
||||||
|
var themeSwitcherContainer = document.querySelector('.theme-switcher');
|
||||||
|
|
||||||
|
[].forEach.call(themeSwitch, function (ts) {
|
||||||
|
ts.checked = darkMode;
|
||||||
|
ts.addEventListener("click", () => {
|
||||||
|
document.documentElement.classList.toggle("dark");
|
||||||
|
localStorage.setItem(
|
||||||
|
"theme",
|
||||||
|
document.documentElement.classList.contains("dark") ? "dark" : "light"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now make the switcher visible
|
||||||
|
themeSwitcherContainer.classList.remove('hidden');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
27
layouts/partials/footer.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<footer class="bg-white dark:bg-pizza-dark mt-auto">
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- Contact -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-4">{{ i18n "contact" }}</h3>
|
||||||
|
{{ with .Site.Params.contact }}
|
||||||
|
{{ if .phone }}
|
||||||
|
<a href="tel:{{ .phone }}" class="flex items-center text-status-green dark:text-gray-400 hover:text-status-green dark:hover:text-status-green mb-2">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
|
||||||
|
</svg>
|
||||||
|
{{ i18n "call_now" }}: {{ .phone }}
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<p class="text-center text-gray-600 dark:text-gray-400">
|
||||||
|
© {{ now.Format "2006" }} {{ .Site.Title }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
1
layouts/partials/head.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
161
layouts/partials/header.html
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
<header class="fixed top-0 left-0 right-0 z-50 transition-all duration-300">
|
||||||
|
<!-- Status Bar -->
|
||||||
|
{{ $status := partial "check-open-status.html" . }}
|
||||||
|
<div id="status-bar" class="{{ if $status.isOpen }}bg-status-green{{ else }}bg-status-red{{ end }} transition-colors duration-300">
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<div class="flex justify-center items-center h-8 sm:h-10">
|
||||||
|
<div id="status-text" class="text-xs sm:text-sm text-white font-medium">
|
||||||
|
{{ if $status.isOpen }}
|
||||||
|
{{ i18n "open" }}
|
||||||
|
{{ else }}
|
||||||
|
{{ i18n "closed" }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="ml-2 text-xs sm:text-sm text-white">
|
||||||
|
(<span id="current-time">{{ now.Format "15:04" }}</span> {{ i18n "time_suffix" }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="bg-white dark:bg-pizza-darker shadow-md">
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<div class="flex justify-between items-center h-16 sm:h-20">
|
||||||
|
<!-- Logo -->
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<a href="{{ .Site.Home.RelPermalink }}" class="flex items-center">
|
||||||
|
<img src="/images/logo.webp"
|
||||||
|
alt="{{ .Site.Title }}"
|
||||||
|
class="h-12 sm:h-16 w-auto transition-transform duration-300 hover:scale-105">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Navigation Links -->
|
||||||
|
<div class="hidden md:flex items-center space-x-8">
|
||||||
|
{{ range .Site.Menus.main }}
|
||||||
|
<a href="{{ .URL | relLangURL }}"
|
||||||
|
class="text-gray-700 dark:text-gray-100 hover:text-pizza-red transition-colors duration-300">
|
||||||
|
{{ .Name }}
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Language Switcher - Desktop -->
|
||||||
|
<div class="hidden md:flex items-center space-x-4">
|
||||||
|
{{ if eq .Site.Language.Lang "de" }}
|
||||||
|
<a href="{{ relLangURL "en" }}"
|
||||||
|
class="text-gray-700 dark:text-gray-100 hover:text-pizza-red transition-colors duration-300">
|
||||||
|
English
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="/"
|
||||||
|
class="text-gray-700 dark:text-gray-100 hover:text-pizza-red transition-colors duration-300">
|
||||||
|
Deutsch
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dark mode toggle - Desktop -->
|
||||||
|
<div class="hidden md:flex items-center">
|
||||||
|
<button id="darkModeToggle"
|
||||||
|
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none">
|
||||||
|
<!-- Sun icon -->
|
||||||
|
<svg class="h-6 w-6 text-gray-700 dark:text-gray-200 hidden dark:block"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
<!-- Moon icon -->
|
||||||
|
<svg class="h-6 w-6 text-gray-700 dark:text-gray-200 block dark:hidden"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Controls -->
|
||||||
|
<div class="md:hidden flex items-center space-x-3">
|
||||||
|
<!-- Dark mode toggle - Mobile -->
|
||||||
|
<button id="darkModeToggle"
|
||||||
|
onclick="toggleTheme()"
|
||||||
|
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none">
|
||||||
|
<!-- Sun icon -->
|
||||||
|
<svg class="h-6 w-6 text-gray-700 dark:text-gray-200 hidden dark:block"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
<!-- Moon icon -->
|
||||||
|
<svg class="h-6 w-6 text-gray-700 dark:text-gray-200 block dark:hidden"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Button -->
|
||||||
|
<button type="button"
|
||||||
|
id="mobile-menu-button"
|
||||||
|
class="text-gray-700 dark:text-gray-100 hover:text-pizza-red p-2">
|
||||||
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu -->
|
||||||
|
<div id="mobile-menu" class="md:hidden hidden">
|
||||||
|
<div class="px-2 pt-2 pb-3 space-y-1 bg-white dark:bg-pizza-darker border-t border-gray-200 dark:border-gray-700">
|
||||||
|
{{ range .Site.Menus.main }}
|
||||||
|
<a href="{{ .URL | relLangURL }}"
|
||||||
|
class="block px-3 py-2.5 text-base font-medium text-gray-700 dark:text-gray-100 hover:text-pizza-red transition-colors duration-300">
|
||||||
|
{{ .Name }}
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<!-- Language Options in Mobile Menu -->
|
||||||
|
<div class="border-t border-gray-200 dark:border-gray-700 mt-2 pt-2">
|
||||||
|
{{ if eq .Site.Language.Lang "de" }}
|
||||||
|
<a href="{{ relLangURL "en" }}"
|
||||||
|
class="block px-3 py-2.5 text-base font-medium text-gray-700 dark:text-gray-100 hover:text-pizza-red transition-colors duration-300">
|
||||||
|
English
|
||||||
|
</a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="/"
|
||||||
|
class="block px-3 py-2.5 text-base font-medium text-gray-700 dark:text-gray-100 hover:text-pizza-red transition-colors duration-300">
|
||||||
|
Deutsch
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Mobile Menu JavaScript -->
|
||||||
|
<script>
|
||||||
|
document.getElementById('mobile-menu-button').addEventListener('click', function() {
|
||||||
|
const mobileMenu = document.getElementById('mobile-menu');
|
||||||
|
mobileMenu.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
</script>
|
5
layouts/partials/logo.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{ if .Site.Params.logo }}
|
||||||
|
<img src="{{ .Site.Params.logo | relURL }}" alt="{{ .Site.Title }}" class="logo">
|
||||||
|
{{ else }}
|
||||||
|
<span class="logo-text">{{ .Site.Title }}</span>
|
||||||
|
{{ end }}
|
180
layouts/partials/menu-card.html
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
<!-- Menu Section -->
|
||||||
|
<div id="menu-section" class="mt-8 sm:mt-16">
|
||||||
|
<h2 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-8">{{ i18n "menu" }}</h2>
|
||||||
|
|
||||||
|
<!-- Mobile Horizontal Scroll -->
|
||||||
|
<div class="md:hidden overflow-x-auto whitespace-nowrap pb-2 mb-4 scrollbar-hide">
|
||||||
|
<div class="inline-flex gap-2">
|
||||||
|
{{ $categories := .Site.Data.menu.categories }}
|
||||||
|
{{ $sortedCategories := sort $categories "sort_order" "asc" }}
|
||||||
|
{{ range $sortedCategories }}
|
||||||
|
<button
|
||||||
|
data-tab="{{ .id }}"
|
||||||
|
class="tab-button inline-block px-3 py-1.5 text-sm rounded-full transition-colors duration-200 whitespace-nowrap
|
||||||
|
{{ if eq .id "pizzas" }}
|
||||||
|
bg-pizza-red text-white
|
||||||
|
{{ else }}
|
||||||
|
bg-gray-200 dark:bg-pizza-darker text-gray-700 dark:text-white hover:bg-gray-300 dark:hover:bg-pizza-dark
|
||||||
|
{{ end }}">
|
||||||
|
{{ .name }}
|
||||||
|
</button>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Menu Tabs -->
|
||||||
|
<div class="hidden md:flex flex-wrap gap-4 mb-8">
|
||||||
|
{{ $categories := .Site.Data.menu.categories }}
|
||||||
|
{{ $sortedCategories := sort $categories "sort_order" "asc" }}
|
||||||
|
{{ range $sortedCategories }}
|
||||||
|
<button
|
||||||
|
data-tab="{{ .id }}"
|
||||||
|
class="tab-button px-8 py-2 text-base rounded-full transition-colors duration-200
|
||||||
|
{{ if eq .id "pizzas" }}
|
||||||
|
bg-pizza-red text-white
|
||||||
|
{{ else }}
|
||||||
|
bg-gray-200 dark:bg-pizza-darker text-gray-700 dark:text-white hover:bg-gray-300 dark:hover:bg-pizza-dark
|
||||||
|
{{ end }}">
|
||||||
|
{{ .name }}
|
||||||
|
</button>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Price Legend -->
|
||||||
|
<div class="legend-container fixed bottom-4 right-4 z-50 group">
|
||||||
|
<!-- The actual legend -->
|
||||||
|
<div class="price-legend bg-white/90 dark:bg-pizza-dark/90 backdrop-blur-sm shadow-lg rounded-lg p-4
|
||||||
|
transition-all duration-300 group-hover:opacity-0 group-hover:translate-y-1">
|
||||||
|
<div class="flex flex-col space-y-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="w-3 h-3 bg-status-green rounded-full mr-2 animate-pulse"></span>
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Abholung</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="w-3 h-3 bg-status-red rounded-full mr-2 animate-pulse"></span>
|
||||||
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Lieferung</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info icon -->
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100
|
||||||
|
transition-all duration-300 group-hover:-translate-y-1";
|
||||||
|
title="Zeigt die Preise für Abholung und Lieferung">
|
||||||
|
<svg class="w-6 h-6 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Content -->
|
||||||
|
{{ $categories := .Site.Data.menu.categories }}
|
||||||
|
{{ $sortedCategories := sort $categories "sort_order" "asc" }}
|
||||||
|
{{ range $sortedCategories }}
|
||||||
|
<div id="{{ .id }}-content" class="menu-content {{ if ne .id "pizzas" }}hidden{{ end }} space-y-4 sm:space-y-6">
|
||||||
|
{{ if eq .id "pizzas" }}
|
||||||
|
{{ range .price_groups }}
|
||||||
|
<div class="bg-gray-100 dark:bg-pizza-darker rounded-xl p-6">
|
||||||
|
<div class="flex justify-between items-center mb-8 cursor-pointer price-group-header">
|
||||||
|
<h3 class="text-xl font-bold">{{ .name }}</h3>
|
||||||
|
<svg class="w-6 h-6 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="price-group-content hidden">
|
||||||
|
<!-- Size Options -->
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||||
|
{{ range .prices }}
|
||||||
|
<div class="text-center">
|
||||||
|
<h4 class="font-medium mb-2">{{ .size }}</h4>
|
||||||
|
<div class="flex justify-center space-x-4">
|
||||||
|
<span class="text-emerald-400">{{ printf "%.2f" .pickup }}€</span>
|
||||||
|
<span class="text-pizza-red">/</span>
|
||||||
|
<span class="text-pizza-red">{{ printf "%.2f" .delivery }}€</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Items -->
|
||||||
|
<div class="space-y-4 mt-6 border-t border-gray-200 dark:border-pizza-dark pt-6">
|
||||||
|
{{ range .items }}
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 class="font-medium">{{ .name }}</h4>
|
||||||
|
<p class="text-gray-600 dark:text-pizza-gray text-sm">{{ .description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ else if eq .id "menus" }}
|
||||||
|
{{ range .price_groups }}
|
||||||
|
<div class="bg-gray-100 dark:bg-pizza-darker rounded-xl p-6">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-xl font-bold">{{ .name }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Description -->
|
||||||
|
<p class="text-gray-600 dark:text-pizza-gray mb-4">{{ .description }}</p>
|
||||||
|
|
||||||
|
<!-- Included Items -->
|
||||||
|
<ul class="list-disc list-inside text-gray-600 dark:text-pizza-gray mb-6 space-y-2">
|
||||||
|
{{ range .included }}
|
||||||
|
<li>{{ . }}</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Price Options -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4 border-t border-gray-200 dark:border-pizza-dark pt-4">
|
||||||
|
{{ range .prices }}
|
||||||
|
<div class="flex justify-between items-center py-2 border-b border-gray-200 dark:border-pizza-dark last:border-0">
|
||||||
|
<span class="font-medium">{{ .base }}</span>
|
||||||
|
<div class="font-medium">
|
||||||
|
{{ if and (isset . "pickup") (isset . "delivery") }}
|
||||||
|
<span class="text-status-green">{{ printf "%.2f" .pickup }}€</span>
|
||||||
|
<span class="text-gray-400 mx-1">/</span>
|
||||||
|
<span class="text-status-red">{{ printf "%.2f" .delivery }}€</span>
|
||||||
|
{{ else }}
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">{{ printf "%.2f" .price }}€</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
<div class="bg-gray-100 dark:bg-pizza-darker rounded-xl p-6">
|
||||||
|
<div class="space-y-4">
|
||||||
|
{{ range .items }}
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h4 class="font-medium">{{ .name }}</h4>
|
||||||
|
<p class="text-gray-600 dark:text-pizza-gray text-sm">{{ .description }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="font-medium">
|
||||||
|
{{ if and (isset . "pickup") (isset . "delivery") }}
|
||||||
|
<span class="text-status-green">{{ printf "%.2f" .pickup }}€</span>
|
||||||
|
<span class="text-gray-400 mx-1">/</span>
|
||||||
|
<span class="text-status-red">{{ printf "%.2f" .delivery }}€</span>
|
||||||
|
{{ else if and (isset . "price_small") (isset . "price_large") }}
|
||||||
|
<span class="text-gray-600 dark:text-gray-400 text-sm">{{ i18n "portion_small"}}: {{ printf "%.2f" .price_small }}€</span>
|
||||||
|
<span class="text-gray-600 mx-1">/</span>
|
||||||
|
<span class="text-gray-600 dark:text-gray-400 text-base">{{ i18n "portion_large"}}: {{ printf "%.2f" .price_large }}€</span>
|
||||||
|
{{ else }}
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">{{ printf "%.2f" .price }}€</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
83
layouts/partials/opening-hours.html
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
{{ $now := now }}
|
||||||
|
{{ $currentDay := $now.Format "Monday" }}
|
||||||
|
{{ $currentTime := $now.Format "15:04" }}
|
||||||
|
|
||||||
|
<div class="opening-hours bg-white/5 dark:bg-pizza-darker/5 backdrop-blur-sm rounded-xl p-4 sm:p-6 mx-0 sm:mx-0">
|
||||||
|
<h3 class="text-2xl font-bold mb-6 sm:mb-8 text-gray-900 dark:text-white">{{ i18n "opening_hours" }}</h3>
|
||||||
|
|
||||||
|
<!-- Regular Hours -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
{{ range .Site.Data.hours.regular_hours }}
|
||||||
|
{{ $isOpen := and (in .days $currentDay) (ge $currentTime .open) (le $currentTime .close) }}
|
||||||
|
<div class="hours-row {{ if $isOpen }}border-2 border-status-green{{ end }}
|
||||||
|
bg-white dark:bg-pizza-darker rounded-xl p-4 transition-all duration-300"
|
||||||
|
data-day="{{ $currentDay }}"
|
||||||
|
data-open="{{ .open }}"
|
||||||
|
data-close="{{ .close }}"
|
||||||
|
data-delivery-until="{{ .delivery_until }}">
|
||||||
|
<div class="flex justify-around items-center">
|
||||||
|
<div class="days-group">
|
||||||
|
{{ $numDays := len .days }}
|
||||||
|
{{ if eq $numDays 1 }}
|
||||||
|
<span class="text-lg font-medium text-gray-800 dark:text-gray-200">{{ index .days 0 }}</span>
|
||||||
|
{{ else if eq $numDays 2 }}
|
||||||
|
<span class="text-lg font-medium text-gray-800 dark:text-gray-200">{{ i18n (index .days 0) }} & {{ i18n (index .days 1) }}</span>
|
||||||
|
{{ else }}
|
||||||
|
{{ $lastIndex := sub $numDays 1 }}
|
||||||
|
{{ range $i, $day := .days }}
|
||||||
|
{{ if eq $i 0 }}
|
||||||
|
<span class="text-lg font-medium text-gray-800 dark:text-gray-200">{{ i18n $day }}</span>
|
||||||
|
{{ else if eq $i $lastIndex }}
|
||||||
|
<span class="text-lg font-medium text-gray-800 dark:text-gray-200"> - {{ i18n $day }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<div class="text-lg font-medium {{ if $isOpen }}text-status-green{{ else }}text-gray-700 dark:text-gray-300{{ end }}">
|
||||||
|
{{ .open }} - {{ .close }}
|
||||||
|
</div>
|
||||||
|
<div class="delivery-text {{ if $isOpen }}text-status-green{{ else }}text-status-red{{ end }}">
|
||||||
|
{{ i18n "delivery_until" }} {{ .close }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Phone number - only shown when this time slot is open -->
|
||||||
|
{{ if $isOpen }}
|
||||||
|
<div class="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700">
|
||||||
|
<a href="tel:{{ $.Site.Params.contact.phone }}"
|
||||||
|
class="flex items-center text-status-green hover:text-status-green/80 transition-colors duration-300">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium"> {{ i18n "call_now" }}: {{ $.Site.Params.contact.phone }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Holiday Notice -->
|
||||||
|
{{ range .Site.Data.hours.holidays }}
|
||||||
|
{{ $holidayDate := time .date }}
|
||||||
|
{{ if eq ($now.Format "2006-01-02") ($holidayDate.Format "2006-01-02") }}
|
||||||
|
<div class="mt-6 p-4 bg-pizza-red/10 dark:bg-pizza-red/20 rounded-xl border border-pizza-red/20">
|
||||||
|
<div class="font-medium text-pizza-red">
|
||||||
|
{{ .name }}:
|
||||||
|
{{ if .closed }}
|
||||||
|
<span class="ml-2">{{ i18n "closed" }}</span>
|
||||||
|
{{ else }}
|
||||||
|
<span class="ml-2">{{ .open }} - {{ .close }} Uhr</span>
|
||||||
|
<div class="text-sm">
|
||||||
|
{{ i18n "delivery_until" }} {{ .delivery_until }} Uhr
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
</div>
|
5
layouts/partials/price-table.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="price-header">
|
||||||
|
<span>{{ i18n "size" }}</span>
|
||||||
|
<span>{{ i18n "pickup" }}</span>
|
||||||
|
<span>{{ i18n "delivery" }}</span>
|
||||||
|
</div>
|
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');
|
||||||
|
}
|
||||||
|
});
|