Compare commits
1 Commits
801dbcea97
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e13f35a9e0 |
@@ -1,2 +0,0 @@
|
||||
TMDB_API_TOKEN=yourapitoken
|
||||
TMDB_API_KEY=yourapikey
|
||||
4
.gitattributes
vendored
@@ -1,2 +1,2 @@
|
||||
public/img/* filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
img/ filter=lfs diff=lfs merge=lfs -text
|
||||
img/** filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
25
.gitignore
vendored
@@ -1,25 +0,0 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
.claude/
|
||||
4
.vscode/extensions.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
.vscode/launch.json
vendored
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
43
README.md
@@ -1,43 +0,0 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
@@ -1,6 +1,4 @@
|
||||
import type { User } from "./interfaces.js";
|
||||
|
||||
function readStorageJson(key: string, fallbackValue: any) {
|
||||
function readStorageJson(key, fallbackValue) {
|
||||
const raw = localStorage.getItem(key);
|
||||
|
||||
if (!raw || raw === "undefined" || raw === "null") {
|
||||
@@ -15,18 +13,22 @@ function readStorageJson(key: string, fallbackValue: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeUser(user: User): User {
|
||||
function normalizeUser(user) {
|
||||
if (!user || typeof user !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
firstName: user.firstName || "",
|
||||
lastName: user.lastName || "",
|
||||
email: user.email || "",
|
||||
hashedPassword: user.hashedPassword || "",
|
||||
password: user.password || "",
|
||||
orders: Array.isArray(user.orders) ? user.orders : [],
|
||||
paymentMethods: Array.isArray(user.paymentMethods) ? user.paymentMethods : []
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(value: string) {
|
||||
function escapeHtml(value) {
|
||||
return String(value || "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
@@ -35,7 +37,7 @@ function escapeHtml(value: string) {
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function formatEuro(value: string) {
|
||||
function formatEuro(value) {
|
||||
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
|
||||
}
|
||||
|
||||
@@ -51,21 +53,15 @@ function persistCurrentUser() {
|
||||
}
|
||||
}
|
||||
|
||||
export let users = readStorageJson("eagleUsers", []);
|
||||
let users = readStorageJson("eagleUsers", []);
|
||||
if (!Array.isArray(users)) {
|
||||
users = [];
|
||||
}
|
||||
users = users.map(normalizeUser).filter(Boolean);
|
||||
|
||||
const rawCurrentUser = readStorageJson("currentUser", null);
|
||||
|
||||
export var currentUser: User | null = rawCurrentUser ? normalizeUser(rawCurrentUser) : null;
|
||||
|
||||
let currentUser = normalizeUser(readStorageJson("currentUser", null));
|
||||
if (currentUser && currentUser.email) {
|
||||
const currentEmail = currentUser.email;
|
||||
const storedMatch = users.find((user: { email: string; }) => {
|
||||
return user.email === currentEmail;
|
||||
});
|
||||
const storedMatch = users.find((user) => user.email === currentUser.email);
|
||||
if (storedMatch) {
|
||||
currentUser = storedMatch;
|
||||
} else {
|
||||
@@ -74,23 +70,11 @@ if (currentUser && currentUser.email) {
|
||||
}
|
||||
}
|
||||
|
||||
async function hashMessage(message: string) {
|
||||
const msgBuffer = new TextEncoder().encode(message); // Encode as UTF-8
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); // Hash
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); // Convert to bytes
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // Hex string
|
||||
}
|
||||
|
||||
function getInputValue(id: string): string {
|
||||
const el = document.getElementById(id) as HTMLInputElement | null;
|
||||
return el?.value.trim() ?? "";
|
||||
}
|
||||
|
||||
export async function registerUser() {
|
||||
const firstName = getInputValue("reg-firstname");
|
||||
const lastName = getInputValue("reg-lastname");
|
||||
const email = getInputValue("reg-email").toLowerCase();
|
||||
const password = document.querySelector<HTMLInputElement>("#reg-password")?.value ?? "";
|
||||
function registerUser() {
|
||||
const firstName = document.getElementById("reg-firstname")?.value.trim() || "";
|
||||
const lastName = document.getElementById("reg-lastname")?.value.trim() || "";
|
||||
const email = (document.getElementById("reg-email")?.value.trim() || "").toLowerCase();
|
||||
const password = document.getElementById("reg-password")?.value || "";
|
||||
|
||||
if (!firstName || !lastName || !email || !password) {
|
||||
alert("Bitte fuelle alle Felder aus.");
|
||||
@@ -102,19 +86,17 @@ const password = document.querySelector<HTMLInputElement>("#reg-password")?.valu
|
||||
return;
|
||||
}
|
||||
|
||||
const existingUser = users.find((user: User) => user.email.toLowerCase() === email);
|
||||
const existingUser = users.find((user) => user.email.toLowerCase() === email);
|
||||
if (existingUser) {
|
||||
alert("E-Mail bereits registriert");
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedPassword = await hashMessage(password);
|
||||
|
||||
const newUser = {
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
hashedPassword,
|
||||
password,
|
||||
orders: [],
|
||||
paymentMethods: []
|
||||
};
|
||||
@@ -131,13 +113,12 @@ const password = document.querySelector<HTMLInputElement>("#reg-password")?.valu
|
||||
openAccountDashboard();
|
||||
}
|
||||
|
||||
export async function loginUser() {
|
||||
const email = (document.querySelector<HTMLInputElement>("#login-email")?.value.trim() || "").toLowerCase();
|
||||
const password = document.querySelector<HTMLInputElement>("#login-password")?.value || "";
|
||||
const hashedPassword = await hashMessage(password);
|
||||
function loginUser() {
|
||||
const email = (document.getElementById("login-email")?.value.trim() || "").toLowerCase();
|
||||
const password = document.getElementById("login-password")?.value || "";
|
||||
|
||||
const user = users.find(
|
||||
(entry: User) => entry.email.toLowerCase() === email && entry.hashedPassword === hashedPassword
|
||||
(entry) => entry.email.toLowerCase() === email && entry.password === password
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
@@ -150,7 +131,7 @@ export async function loginUser() {
|
||||
openAccountDashboard();
|
||||
}
|
||||
|
||||
export function openAccountDashboard() {
|
||||
function openAccountDashboard() {
|
||||
const accountView = document.getElementById("account-view");
|
||||
if (!accountView) {
|
||||
return;
|
||||
@@ -161,7 +142,7 @@ export function openAccountDashboard() {
|
||||
return;
|
||||
}
|
||||
|
||||
accountView.innerHTML = /*html*/`
|
||||
accountView.innerHTML = `
|
||||
<div class="account-panel">
|
||||
<div class="account-panel-header">
|
||||
<h2>Mein Konto</h2>
|
||||
@@ -217,7 +198,7 @@ function renderOrders() {
|
||||
const orderHtml = orders
|
||||
.map((order, index) => {
|
||||
const movieItems = Array.isArray(order.items)
|
||||
? order.items.filter((item: any) => item.category === "movie")
|
||||
? order.items.filter((item) => item.category === "movie")
|
||||
: [];
|
||||
const previewItem = movieItems[0] || (Array.isArray(order.items) ? order.items[0] : null);
|
||||
const previewTitle = previewItem?.title || "Bestellung";
|
||||
@@ -246,20 +227,20 @@ function renderOrders() {
|
||||
`;
|
||||
|
||||
const detailTarget = document.getElementById("order-ticket-details");
|
||||
const orderButtons = Array.from(target.querySelectorAll<HTMLButtonElement>(".order-item-btn"));
|
||||
const orderButtons = Array.from(target.querySelectorAll(".order-item-btn"));
|
||||
|
||||
const renderOrderTicket = (orderIndex: number) => {
|
||||
const renderOrderTicket = (orderIndex) => {
|
||||
const order = orders[orderIndex];
|
||||
if (!order || !detailTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const movieItems = Array.isArray(order.items)
|
||||
? order.items.filter((item: any) => item.category === "movie")
|
||||
? order.items.filter((item) => item.category === "movie")
|
||||
: [];
|
||||
const primaryMovie = movieItems[0] || (Array.isArray(order.items) ? order.items[0] : null);
|
||||
const poster = primaryMovie?.img || "";
|
||||
const seats = movieItems.map((item: any) => item.seatId).filter(Boolean).join(", ") || "-";
|
||||
const seats = movieItems.map((item) => item.seatId).filter(Boolean).join(", ") || "-";
|
||||
const ticketCount = movieItems.length || (Array.isArray(order.items) ? order.items.length : 0);
|
||||
const hall = primaryMovie?.hall || "-";
|
||||
const time = primaryMovie?.time ? `${primaryMovie.time} Uhr` : "-";
|
||||
@@ -309,7 +290,7 @@ function renderPayments() {
|
||||
return;
|
||||
}
|
||||
|
||||
target.innerHTML = /*html*/`
|
||||
target.innerHTML = `
|
||||
<div class="account-card">
|
||||
<h3>Zahlungsmethoden</h3>
|
||||
<p class="account-payments-note">Platzhalter zum Hinterlegen deiner Logos oder Anbieter-Informationen.</p>
|
||||
@@ -463,12 +444,7 @@ function renderPayments() {
|
||||
}
|
||||
|
||||
function logoutUser() {
|
||||
currentUser = null;
|
||||
persistCurrentUser();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
(window as any).logoutUser = logoutUser;
|
||||
(window as any).renderPersonalInfo = renderPersonalInfo;
|
||||
(window as any).renderOrders = renderOrders;
|
||||
(window as any).renderPayments = renderPayments;
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// @ts-check
|
||||
import { defineConfig, envField } from 'astro/config';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [react({
|
||||
include: ['**/react/*']
|
||||
})],
|
||||
vite: {
|
||||
// @ts-ignore
|
||||
plugins: [tailwindcss({optimize:false})]
|
||||
},
|
||||
env: {
|
||||
schema: {
|
||||
TMDB_API_TOKEN: envField.string({ context: 'server', access: 'secret'}),
|
||||
TMDB_API_KEY: envField.string({context: "server", access: "secret"})
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,36 +1,32 @@
|
||||
import { seatLayouts, occupiedSeatsData, prices, cart } from "./main.js"
|
||||
import { renderCart, saveCart } from "./cart.js";
|
||||
import { renderCheckout } from "./checkout.js";
|
||||
let currentBookingContext = null;
|
||||
let currentHallLayout = null;
|
||||
|
||||
let currentBookingContext: any = null;
|
||||
let currentHallLayout: any = null;
|
||||
function openBooking(movie, hall, time) {
|
||||
const titleEl = document.getElementById("modal-movie-title");
|
||||
const infoEl = document.getElementById("modal-info-text");
|
||||
|
||||
export function openBooking(movie: string, hall: string, time: any) {
|
||||
const titleEl = document.getElementById("modal-movie-title");
|
||||
const infoEl = document.getElementById("modal-info-text");
|
||||
if (titleEl) {
|
||||
titleEl.innerText = movie;
|
||||
}
|
||||
|
||||
if (titleEl) {
|
||||
titleEl.innerText = movie;
|
||||
}
|
||||
if (infoEl) {
|
||||
infoEl.innerText = `${hall} • ${time} Uhr`;
|
||||
}
|
||||
|
||||
if (infoEl) {
|
||||
infoEl.innerText = `${hall} • ${time} Uhr`;
|
||||
}
|
||||
currentBookingContext = { movie, hall, time };
|
||||
|
||||
currentBookingContext = { movie, hall, time };
|
||||
createSeats(hall, time);
|
||||
renderBookingLegend();
|
||||
updateBookingSummary();
|
||||
|
||||
createSeats(hall, time);
|
||||
renderBookingLegend();
|
||||
updateBookingSummary();
|
||||
document.getElementById("booking-modal")?.classList.remove("hidden");
|
||||
}
|
||||
|
||||
document.getElementById("booking-modal")?.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function getRowLabel(rowIndex: number) {
|
||||
function getRowLabel(rowIndex) {
|
||||
return String(rowIndex + 1);
|
||||
}
|
||||
|
||||
function buildHallLayout(hallName: string, baseConfig:any) {
|
||||
function buildHallLayout(hallName, baseConfig) {
|
||||
const rows = Number(baseConfig.rows || 0);
|
||||
const totalCols = Number(baseConfig.left || 0) + Number(baseConfig.right || 0);
|
||||
const isDeluxe = /deluxe/i.test(hallName);
|
||||
@@ -43,7 +39,7 @@ function buildHallLayout(hallName: string, baseConfig:any) {
|
||||
const vipRows = rows > 0 ? [rows] : [];
|
||||
|
||||
const dboxMap = new Set();
|
||||
const markDboxRange = (rowNumber: number, startCol: number, width: number) => {
|
||||
const markDboxRange = (rowNumber, startCol, width) => {
|
||||
if (!rowNumber || width <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -56,7 +52,7 @@ function buildHallLayout(hallName: string, baseConfig:any) {
|
||||
|
||||
if (isDeluxe) {
|
||||
const configuredDboxSeats = Array.isArray(baseConfig.dbox)
|
||||
? baseConfig.dbox.reduce((sum: number, section: any) => sum + Number(section.w || 0), 0)
|
||||
? baseConfig.dbox.reduce((sum, section) => sum + Number(section.w || 0), 0)
|
||||
: 0;
|
||||
|
||||
const totalDboxSeats = Math.max(4, configuredDboxSeats || 0);
|
||||
@@ -81,7 +77,7 @@ function buildHallLayout(hallName: string, baseConfig:any) {
|
||||
markDboxRange(rowNumber, startCol, seatsForRow);
|
||||
});
|
||||
} else if (Array.isArray(baseConfig.dbox)) {
|
||||
baseConfig.dbox.forEach((section: any) => {
|
||||
baseConfig.dbox.forEach((section) => {
|
||||
const rowNumber = Number(section.r || 0);
|
||||
const width = Number(section.w || 0);
|
||||
const startCol = Number(section.c || 0);
|
||||
@@ -100,7 +96,7 @@ function buildHallLayout(hallName: string, baseConfig:any) {
|
||||
};
|
||||
}
|
||||
|
||||
function getSeatType(layout: any, rowNumber: number, colNumber: number) {
|
||||
function getSeatType(layout, rowNumber, colNumber) {
|
||||
if (layout.dboxMap.has(`${rowNumber}-${colNumber}`)) {
|
||||
return "dbox";
|
||||
}
|
||||
@@ -116,7 +112,7 @@ function getSeatType(layout: any, rowNumber: number, colNumber: number) {
|
||||
return "normal";
|
||||
}
|
||||
|
||||
function createSeatElement({seatId, seatType, occupiedSeats }:any) {
|
||||
function createSeatElement({ seatId, seatType, occupiedSeats }) {
|
||||
const seat = document.createElement("button");
|
||||
seat.type = "button";
|
||||
seat.classList.add("seat", seatType);
|
||||
@@ -140,7 +136,7 @@ function createSeatElement({seatId, seatType, occupiedSeats }:any) {
|
||||
return seat;
|
||||
}
|
||||
|
||||
function createSeats(hallName: string, time: any) {
|
||||
function createSeats(hallName, time) {
|
||||
const seatGrid = document.getElementById("seat-grid");
|
||||
if (!seatGrid) {
|
||||
return;
|
||||
@@ -148,8 +144,7 @@ function createSeats(hallName: string, time: any) {
|
||||
|
||||
seatGrid.innerHTML = "";
|
||||
|
||||
const arrIndex = hallName as keyof typeof seatLayouts;
|
||||
const baseConfig: any = seatLayouts[arrIndex];
|
||||
const baseConfig = seatLayouts[hallName];
|
||||
if (!baseConfig) {
|
||||
currentHallLayout = null;
|
||||
return;
|
||||
@@ -240,7 +235,7 @@ function renderBookingLegend() {
|
||||
}
|
||||
|
||||
function updateBookingSummary() {
|
||||
const selectedSeats = Array.from(document.querySelectorAll("#seat-grid .seat.selected")) as HTMLElement[];;
|
||||
const selectedSeats = Array.from(document.querySelectorAll("#seat-grid .seat.selected"));
|
||||
const summaryPanel = document.getElementById("booking-summary");
|
||||
const summaryItems = document.getElementById("summary-items");
|
||||
const totalEl = document.getElementById("total-price");
|
||||
@@ -250,7 +245,7 @@ function updateBookingSummary() {
|
||||
if (summaryItems) {
|
||||
summaryItems.innerHTML = selectedSeats
|
||||
.map((seat) => {
|
||||
const type = (seat.dataset.type || "normal") as keyof typeof prices;
|
||||
const type = seat.dataset.type || "normal";
|
||||
const seatPrice = Number(prices?.[type] ?? prices?.normal ?? 11);
|
||||
total += seatPrice;
|
||||
|
||||
@@ -277,13 +272,12 @@ function updateBookingSummary() {
|
||||
summaryPanel?.classList.toggle("hidden", selectedSeats.length === 0);
|
||||
}
|
||||
|
||||
function findMoviePoster(movieTitle: string) {
|
||||
function findMoviePoster(movieTitle) {
|
||||
const cards = Array.from(document.querySelectorAll(".movie-card, .detailed-card"));
|
||||
const normalizedTarget = String(movieTitle || "").trim().toLowerCase();
|
||||
|
||||
for (const card of cards) {
|
||||
const currentCard = card.querySelector("h2, h3") as HTMLElement;
|
||||
const title = currentCard.innerText?.trim().toLowerCase();
|
||||
const title = card.querySelector("h2, h3")?.innerText?.trim().toLowerCase();
|
||||
if (title === normalizedTarget) {
|
||||
const imageSrc = card.querySelector("img")?.src;
|
||||
if (imageSrc) {
|
||||
@@ -296,7 +290,7 @@ function findMoviePoster(movieTitle: string) {
|
||||
}
|
||||
|
||||
function confirmSelectedSeats() {
|
||||
const selectedSeats = Array.from(document.querySelectorAll("#seat-grid .seat.selected")) as HTMLElement[];
|
||||
const selectedSeats = Array.from(document.querySelectorAll("#seat-grid .seat.selected"));
|
||||
|
||||
if (!currentBookingContext || selectedSeats.length === 0) {
|
||||
alert("Bitte waehle mindestens einen Platz aus.");
|
||||
@@ -310,7 +304,7 @@ function confirmSelectedSeats() {
|
||||
const seatId = seat.dataset.seatId;
|
||||
const seatType = seat.dataset.type || "normal";
|
||||
|
||||
const alreadyInCart = cart.some((item: any) =>
|
||||
const alreadyInCart = cart.some((item) =>
|
||||
item.category === "movie" &&
|
||||
item.title === currentBookingContext.movie &&
|
||||
item.hall === currentBookingContext.hall &&
|
||||
@@ -1,10 +1,8 @@
|
||||
import { cart } from "./main.js";
|
||||
|
||||
function formatEuro(value: number) {
|
||||
function formatEuro(value) {
|
||||
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
|
||||
}
|
||||
|
||||
function escapeHtml(value: any) {
|
||||
function escapeHtml(value) {
|
||||
return String(value || "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
@@ -13,14 +11,14 @@ function escapeHtml(value: any) {
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function buildCartKey(item: { category: string; seatId: any; hall: any; time: any; title: any; }) {
|
||||
function buildCartKey(item) {
|
||||
const infoText = item.category === "movie"
|
||||
? `Sitz: ${item.seatId} (${item.hall})`
|
||||
: item.time;
|
||||
return `${item.title}-${item.hall}-${infoText}`;
|
||||
}
|
||||
|
||||
function isDrinkItem(item: { category: string; title: any; hall: any; }) {
|
||||
function isDrinkItem(item) {
|
||||
if (item.category !== "snack") {
|
||||
return false;
|
||||
}
|
||||
@@ -41,7 +39,7 @@ function isDrinkItem(item: { category: string; title: any; hall: any; }) {
|
||||
return drinkKeywords.some((word) => title.includes(word)) || size.includes("l");
|
||||
}
|
||||
|
||||
function buildItemInfo(item: { category: any; seatId?: any; hall: any; time?: any; title: any; }) {
|
||||
function buildItemInfo(item) {
|
||||
if (item.category === "movie") {
|
||||
return `
|
||||
<div>Sitzplatz: ${escapeHtml(item.seatId || "-")}</div>
|
||||
@@ -67,7 +65,7 @@ function buildItemInfo(item: { category: any; seatId?: any; hall: any; time?: an
|
||||
function groupCartItems() {
|
||||
const groups = new Map();
|
||||
|
||||
cart.forEach((item: { price?: any; category: string; seatId: any; hall: any; time: any; title: any; }) => {
|
||||
cart.forEach((item) => {
|
||||
const key = buildCartKey(item);
|
||||
|
||||
if (!groups.has(key)) {
|
||||
@@ -87,12 +85,12 @@ function groupCartItems() {
|
||||
return Array.from(groups.values());
|
||||
}
|
||||
|
||||
export function saveCart() {
|
||||
function saveCart() {
|
||||
localStorage.setItem("eagleCart", JSON.stringify(cart));
|
||||
updateCartBadge();
|
||||
}
|
||||
|
||||
export function updateCartBadge() {
|
||||
function updateCartBadge() {
|
||||
const cartBadge = document.getElementById("cart-badge");
|
||||
|
||||
if (!cartBadge) {
|
||||
@@ -103,7 +101,7 @@ export function updateCartBadge() {
|
||||
cartBadge.classList.toggle("hidden", cart.length === 0);
|
||||
}
|
||||
|
||||
export function renderCart() {
|
||||
function renderCart() {
|
||||
const cartList = document.getElementById("cart-items-list");
|
||||
const totalEl = document.getElementById("cart-total-right");
|
||||
const vatEl = document.getElementById("cart-vat-right");
|
||||
@@ -121,7 +119,7 @@ export function renderCart() {
|
||||
|
||||
const groupedItems = groupCartItems();
|
||||
|
||||
const header = /*html*/`
|
||||
const header = `
|
||||
<div class="cart-header-row">
|
||||
<div class="col-amount">MENGE</div>
|
||||
<div class="col-img">VORSCHAU</div>
|
||||
@@ -135,11 +133,11 @@ export function renderCart() {
|
||||
const rows = groupedItems
|
||||
.map((group) => {
|
||||
const imageHtml = group.item.img
|
||||
? /*html*/`<img class="cart-img-small" src="${escapeHtml(group.item.img)}" alt="${escapeHtml(group.item.title)}">`
|
||||
: /*html*/`<div class="cart-img-fallback">Kein Bild</div>`;
|
||||
? `<img class="cart-img-small" src="${escapeHtml(group.item.img)}" alt="${escapeHtml(group.item.title)}">`
|
||||
: `<div class="cart-img-fallback">Kein Bild</div>`;
|
||||
const quantityHtml = group.item.category === "movie"
|
||||
? /*html*/`<div class="qty-static" aria-label="Feste Ticketanzahl">${group.quantity}x</div>`
|
||||
: /*html*/`
|
||||
? `<div class="qty-static" aria-label="Feste Ticketanzahl">${group.quantity}x</div>`
|
||||
: `
|
||||
<div class="qty-stepper">
|
||||
<button class="btn-qty" data-action="minus" data-key="${escapeHtml(group.key)}">-</button>
|
||||
<span>${group.quantity}</span>
|
||||
@@ -147,7 +145,7 @@ export function renderCart() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
return /*html*/`
|
||||
return `
|
||||
<div class="cart-item-row">
|
||||
<div class="col-amount">
|
||||
${quantityHtml}
|
||||
@@ -174,23 +172,22 @@ export function renderCart() {
|
||||
|
||||
saveCart();
|
||||
}
|
||||
//@ts-ignore
|
||||
window.removeItem = function removeItem(id: any) {
|
||||
var localCart = cart.filter((item: { id: any; }) => item.id !== id);
|
||||
|
||||
window.removeItem = function removeItem(id) {
|
||||
cart = cart.filter((item) => item.id !== id);
|
||||
saveCart();
|
||||
renderCart();
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
window.changeQty = function changeQty(title, delta): void {
|
||||
window.changeQty = function changeQty(title, delta) {
|
||||
if (delta > 0) {
|
||||
const item = cart.find((entry: { title: any; }) => entry.title === title);
|
||||
const item = cart.find((entry) => entry.title === title);
|
||||
if (item) {
|
||||
cart.push({ ...item, id: Date.now() + Math.random() });
|
||||
}
|
||||
} else {
|
||||
const index = cart
|
||||
.map((entry: { title: any; }) => entry.title)
|
||||
.map((entry) => entry.title)
|
||||
.lastIndexOf(title);
|
||||
if (index !== -1) {
|
||||
cart.splice(index, 1);
|
||||
@@ -200,4 +197,3 @@ window.changeQty = function changeQty(title, delta): void {
|
||||
saveCart();
|
||||
renderCart();
|
||||
};
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { currentUser, users } from "./account.js";
|
||||
import { renderCart, saveCart } from "./cart.js";
|
||||
import { cart, emptyCart, occupiedSeatsData } from "./main.js";
|
||||
|
||||
function formatCheckoutEuro(value: number) {
|
||||
function formatCheckoutEuro(value) {
|
||||
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
|
||||
}
|
||||
|
||||
let selectedPaymentMethod = "";
|
||||
let checkoutEventsBound = false;
|
||||
|
||||
function setCheckoutStep(step: number) {
|
||||
function setCheckoutStep(step) {
|
||||
const step1 = document.getElementById("checkout-step-1");
|
||||
const step2 = document.getElementById("checkout-step-2");
|
||||
const step3 = document.getElementById("checkout-step-3");
|
||||
@@ -31,7 +27,7 @@ function setCheckoutStep(step: number) {
|
||||
line2?.classList.toggle("active", step >= 3);
|
||||
}
|
||||
|
||||
export function renderCheckout() {
|
||||
function renderCheckout() {
|
||||
const summaryList = document.getElementById("checkout-summary-list");
|
||||
const totalDisplay = document.getElementById("checkout-total-display");
|
||||
const vatDisplay = document.getElementById("checkout-vat-display");
|
||||
@@ -97,7 +93,7 @@ function generateTicket() {
|
||||
const qrData = encodeURIComponent(`EAGLE-IMAX|${mainMovie.title}|${mainMovie.hall}|${matchingMovieSeats}`);
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${qrData}&bgcolor=ffffff`;
|
||||
|
||||
ticketContainer.innerHTML = /*html*/`
|
||||
ticketContainer.innerHTML = `
|
||||
<div class="luxury-ticket">
|
||||
<div class="ticket-left">
|
||||
<img src="${mainMovie.img}" class="ticket-poster" alt="${mainMovie.title}">
|
||||
@@ -119,7 +115,7 @@ function generateTicket() {
|
||||
`;
|
||||
}
|
||||
|
||||
function saveOrderForCurrentUser(orderItems: any[], orderTotal: any) {
|
||||
function saveOrderForCurrentUser(orderItems, orderTotal) {
|
||||
if (typeof currentUser === "undefined" || !currentUser) {
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +131,6 @@ function saveOrderForCurrentUser(orderItems: any[], orderTotal: any) {
|
||||
paymentMethod: selectedPaymentMethod || "-"
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
const userIndex = users.findIndex((entry) => entry.email === currentUser.email);
|
||||
if (userIndex === -1) {
|
||||
return;
|
||||
@@ -149,7 +144,7 @@ function saveOrderForCurrentUser(orderItems: any[], orderTotal: any) {
|
||||
localStorage.setItem("eagleUsers", JSON.stringify(users));
|
||||
}
|
||||
|
||||
function reserveSeatsAfterPayment(orderItems: any[]) {
|
||||
function reserveSeatsAfterPayment(orderItems) {
|
||||
const movieItems = orderItems.filter((item) => item.category === "movie");
|
||||
|
||||
movieItems.forEach((item) => {
|
||||
@@ -171,7 +166,7 @@ function completeCheckout() {
|
||||
saveOrderForCurrentUser(orderItems, orderTotal);
|
||||
reserveSeatsAfterPayment(orderItems);
|
||||
|
||||
emptyCart?.()
|
||||
cart = [];
|
||||
saveCart?.();
|
||||
renderCart?.();
|
||||
}
|
||||
@@ -185,7 +180,7 @@ function bindCheckoutEvents() {
|
||||
|
||||
const nextButton = document.getElementById("btn-next-step-2");
|
||||
const backButton = document.getElementById("btn-back-to-step1");
|
||||
const payNowButton = document.getElementById("btn-pay-now") as HTMLButtonElement;
|
||||
const payNowButton = document.getElementById("btn-pay-now");
|
||||
|
||||
document.querySelectorAll(".payment-method").forEach((method) => {
|
||||
method.addEventListener("click", () => {
|
||||
@@ -194,7 +189,6 @@ function bindCheckoutEvents() {
|
||||
});
|
||||
|
||||
method.classList.add("selected");
|
||||
//@ts-ignore
|
||||
selectedPaymentMethod = method.dataset.method || "";
|
||||
nextButton?.classList.remove("hidden");
|
||||
});
|
||||
674
index.html
@@ -267,15 +267,208 @@
|
||||
<h1 class="list-title">Snacks & Getränke</h1>
|
||||
|
||||
<div class="category-tabs">
|
||||
<button class="tab-btn active" data-target="cat-getraenke">Getränke</button>
|
||||
<button class="tab-btn active" data-target="cat-limited">Limitierte Specials</button>
|
||||
<button class="tab-btn" data-target="cat-getraenke">Getränke</button>
|
||||
<button class="tab-btn" data-target="cat-popcorn">Popcorn</button>
|
||||
<button class="tab-btn" data-target="cat-nachos">Nachos</button>
|
||||
<button class="tab-btn" data-target="cat-snacks">Snacks</button>
|
||||
<button class="tab-btn" data-target="cat-kombi">Kombi</button>
|
||||
<button class="tab-btn" data-target="cat-eis">Eis</button>
|
||||
</div>
|
||||
|
||||
<div id="cat-getraenke" class="snack-category active">
|
||||
<div id="cat-limited" class="snack-category active limited-specials-category">
|
||||
<div class="limited-specials-hero">
|
||||
<div>
|
||||
<span class="limited-kicker">Nur für kurze Zeit</span>
|
||||
<h2>Limitierte Specials</h2>
|
||||
<p>Filmbecher, Sammler-Eimer und Kids-Menüs als kleine Vitrine für besondere Aktionen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="limited-special-block">
|
||||
<div class="special-film-heading">
|
||||
<img src="img/Zoomania-2.jpg" alt="Zoomania 2 Logo">
|
||||
<div>
|
||||
<span>Zoomania 2</span>
|
||||
<h2>City Edition</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="limited-special-grid">
|
||||
<div class="snack-card limited-special-card">
|
||||
<div class="snack-img"><img src="img/zoomania-popcorn.jpg" alt="Zoomania 2 Metallbecher"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Metallbecher</span>
|
||||
<h3>Limitierter Metallbecher - Zoomania 2</h3>
|
||||
<p class="snack-card-note">Sammlerbecher mit Popcornfüllung, wahlweise süß oder salzig.</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Special <span>12,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card limited-special-card wide-special">
|
||||
<div class="snack-img"><img src="img/zoomaniakidsmenu.jpg" alt="Zoomania Kids Menu"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Kids Special</span>
|
||||
<h3>Zoomania Kids Menü</h3>
|
||||
<p class="snack-card-note">0,5L Getränk im Zoomania Becher + Zoomania Popcorn Schale + Figur zum Aussuchen.</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>10,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="limited-special-block">
|
||||
<div class="special-film-heading">
|
||||
<img src="img/screamvii.jpg" alt="Scream VII Logo">
|
||||
<div>
|
||||
<span>Scream VII</span>
|
||||
<h2>Horror Collectors</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="limited-special-grid compact-specials">
|
||||
<div class="snack-card limited-special-card">
|
||||
<div class="snack-img"><img src="img/screamdoorpopcorn.jpg" alt="Scream VII Sammelbecher"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Collector</span>
|
||||
<h3>Limitierter Sammelbecher - Scream VII</h3>
|
||||
<p class="snack-card-note">Dunkler Sammlerbecher passend zum Horrorabend.</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Special <span>29,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="limited-special-block">
|
||||
<div class="special-film-heading">
|
||||
<img src="img/derAustronaut.jpg" alt="Der Austronaut Logo">
|
||||
<div>
|
||||
<span>Project Hail Mary</span>
|
||||
<h2>Space Collection</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="limited-special-grid compact-specials">
|
||||
<div class="snack-card limited-special-card">
|
||||
<div class="snack-img"><img src="img/astronautpopcorn.jpg" alt="Der Austronaut Sammelbecher"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Space Cup</span>
|
||||
<h3>Limitierter Sammelbecher - Der Austronaut</h3>
|
||||
<p class="snack-card-note">Großer Sammlerbecher im Sci-Fi-Look für deine Mission ins Kino.</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Special <span>34,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="limited-special-block">
|
||||
<div class="special-film-heading">
|
||||
<img src="img/hoppers.jpg" alt="Hoppers Logo">
|
||||
<div>
|
||||
<span>Hoppers</span>
|
||||
<h2>Family Specials</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="limited-special-grid">
|
||||
<div class="snack-card limited-special-card">
|
||||
<div class="snack-img"><img src="img/hopperspopcornmetall.jpg" alt="Hoppers Metallbecher"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Metallbecher</span>
|
||||
<h3>Limitierter Metallbecher - Hoppers</h3>
|
||||
<p class="snack-card-note">Stabiler Becher mit Popcornfüllung.</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Special <span>12,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card limited-special-card">
|
||||
<div class="snack-img"><img src="img/hopperspopcornwood.png" alt="Hoppers Sammelbecher"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Collector</span>
|
||||
<h3>Limitierter Sammelbecher - Hoppers</h3>
|
||||
<p class="snack-card-note">Sammlerbecher mit warmem Popcornmoment.</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Special <span>21,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card limited-special-card wide-special">
|
||||
<div class="snack-img"><img src="img/hopperskidsmenu.jpg" alt="Hoppers Kids Menu"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Kids Special</span>
|
||||
<h3>Hoppers Kids Menü</h3>
|
||||
<p class="snack-card-note">0,5L Getränk im Hoppers Becher + Hoppers Popcorn Schale + Hoppers Figur.</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>10,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="limited-special-block">
|
||||
<div class="special-film-heading">
|
||||
<img src="img/mariogalaxy.jpg" alt="Mario Galaxy Logo">
|
||||
<div>
|
||||
<span>Super Mario Galaxy</span>
|
||||
<h2>Galaxy Specials</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="limited-special-grid">
|
||||
<div class="snack-card limited-special-card">
|
||||
<div class="snack-img"><img src="img/marioyoshipopcorn.png" alt="Yoshi Sammelbecher"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Yoshi Cup</span>
|
||||
<h3>Limitierter Sammelbecher - Yoshi Becher</h3>
|
||||
<p class="snack-card-note">Verspielter Sammlerbecher für Mario-Fans.</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Special <span>35,90€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card limited-special-card wide-special">
|
||||
<div class="snack-img"><img src="img/mariokidsmenu.png" alt="Mario Kids Menu"></div>
|
||||
<div class="snack-info">
|
||||
<span class="badge">Kids Special</span>
|
||||
<h3>Mario Galaxy Kids Menü</h3>
|
||||
<p class="snack-card-note">0,5L Getränk im Mario Galaxy Becher + Mario Galaxy Popcorn Schale.</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>10,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cat-getraenke" class="snack-category hidden">
|
||||
<div class="snack-grid">
|
||||
|
||||
<div class="snack-card">
|
||||
@@ -392,229 +585,209 @@
|
||||
</div> </div>
|
||||
|
||||
<div id="cat-popcorn" class="snack-category hidden">
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/popcorn-klein.png" alt="Popcorn klein"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Popcorn klein</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
<div class="snack-subsection">
|
||||
<div class="snack-section-heading">
|
||||
<span>Frisch gepoppt</span>
|
||||
<h2>Einzelprodukte</h2>
|
||||
</div>
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/popcorn-klein.png" alt="Popcorn klein"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Popcorn klein</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Klein <span>3,50€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">3,50€</button>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/popcorn-mittel.png" alt="Popcorn mittel"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Popcorn Mittel</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Mittel <span>4,50€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/popcorn-big.png" alt="Popcorn groß"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Popcorn Groß</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Groß <span>6,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="snack-subsection">
|
||||
<div class="snack-section-heading popcorn-combo-heading">
|
||||
<span>Für Filmabende</span>
|
||||
<h2>Kombi Menü</h2>
|
||||
</div>
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<h3>Kleines Menü</h3>
|
||||
<p class="snack-card-note">0,33L Getränk + Popcorn klein</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>5,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<h3>Mittleres Menü</h3>
|
||||
<p class="snack-card-note">0,5L Getränk + Popcorn mittel</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>6,50€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<span class="badge">Bestseller</span>
|
||||
<h3>Großes Menü</h3>
|
||||
<p class="snack-card-note">1L Getränk + Popcorn groß</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>8,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/popcorn-mittel.png" alt="Popcorn mittel"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Popcorn Mittel</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">4,50€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/popcorn-big.png" alt="Popcorn groß"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Popcorn Groß</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">6,00€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/zoomania-popcorn.jpg" alt="Popcorn limited - zoomania 2"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Limitierter Metallbecher - Zoomania 2</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">12,00€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/screamdoorpopcorn.jpg" alt="Popcorn limited - Scream VII"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Limitierter Sammelbecher - Scream VII</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">29,00€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/astronautpopcorn.jpg" alt="Popcorn limited - Der Austronaut"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Limitierter Sammelbecher - Der Austronaut</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">34,00€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/hopperspopcornmetall.jpg" alt="Popcorn limited - Hoppers"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Limitierter Metallbecher - Hoppers</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">12,00€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/hopperspopcornwood.png" alt="Popcorn limited - Hoppers"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Limitierter Sammelbecher - Hoppers</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">21,00€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/marioyoshipopcorn.png" alt="Popcorn limited - Yoshi"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Limitierter Sammelbecher - Yoshi Becher</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Süß</button>
|
||||
<button class="opt-btn">Salzig</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">35,90€</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cat-nachos" class="snack-category hidden">
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachosnormal.png" alt="Nachos"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nachos Klein</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Käse-Dip</button>
|
||||
<button class="opt-btn">Salsa-Dip</button>
|
||||
<div class="snack-subsection">
|
||||
<div class="snack-section-heading">
|
||||
<span>Nachos</span>
|
||||
<h2>Einzelprodukte</h2>
|
||||
</div>
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachosnormal.png" alt="Nachos"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nachos Klein</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Käse-Dip</button>
|
||||
<button class="opt-btn">Salsa-Dip</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Klein <span>5,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Klein<span>5,00€</span></button>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachosnormal.png" alt="Nachos"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nachos Normal</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Käse-Dip</button>
|
||||
<button class="opt-btn">Salsa-Dip</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Mittel <span>6,50€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachos.jpg" alt="Nachos"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nachos Groß</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Käse-Dip</button>
|
||||
<button class="opt-btn">Sourcreme-Dip</button>
|
||||
<button class="opt-btn">Salsa-Dip</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Groß <span>8,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachosnormal.png" alt="Nachos"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nachos Normal</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Käse-Dip</button>
|
||||
<button class="opt-btn">Salsa-Dip</button>
|
||||
</div>
|
||||
|
||||
<div class="snack-subsection">
|
||||
<div class="snack-section-heading">
|
||||
<span>Extra dazu</span>
|
||||
<h2>Dips</h2>
|
||||
</div>
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/käsedip.png" alt="Käse-Dip"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Käse-Dip<br>(warm)</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Normal</button>
|
||||
<button class="opt-btn">Scharf</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Schale <span>2,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Mittel<span>6,50€</span></button>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/sourdip.png" alt="Sourcreme-Dip"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Sourcreme-Dip</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Normal</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Schale <span>2,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/salsadip.png" alt="Salsa-Dip"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Salsa-Dip</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Normal</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Schale <span>2,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachos.jpg" alt="Nachos"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nachos Groß</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Käse-Dip</button>
|
||||
<button class="opt-btn">Sourcreme-Dip</button>
|
||||
<button class="opt-btn">Salsa-Dip</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Groß <span>8,00€</span></button>
|
||||
</div>
|
||||
|
||||
<div class="snack-subsection">
|
||||
<div class="snack-section-heading">
|
||||
<span>Alles drin</span>
|
||||
<h2>Kombi Menü</h2>
|
||||
</div>
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-img"><img src="img/nachokombiklein.png" alt="Nacho Kombi Klein"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nacho Menü Klein</h3>
|
||||
<p class="snack-card-note">Nachos klein + 1 Dip + 1x 0,33L Getränk</p>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Klein</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Kombi <span>6,90€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="list-title"> </h2>
|
||||
<br>
|
||||
<h2 class="list-title">Dips</h2>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/käsedip.png" alt="Käse-Dip"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Käse-Dip<br>(warm)</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Normal</button>
|
||||
<button class="opt-btn">Scharf</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Schale<span>2,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/sourdip.png" alt="Sourcreme-Dip"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Sourcreme-Dip</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Normal</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Schale<span>2,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/salsadip.png" alt="Salsa-Dip"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Salsa-Dip</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Normal</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Schale<span>2,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="list-title">Nacho Kombi-Menüs</h2>
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card">
|
||||
<div class="snack-img"><img src="img/nachokombiklein.png" alt="Nacho Kombi Klein"></div>
|
||||
<div class="snack-info">
|
||||
<h3>Nacho Menü Klein - Nachos klein + 1 Dip + 1 0,33L Getränk</h3>
|
||||
<div class="option-group">
|
||||
<button class="opt-btn active">Klein</button>
|
||||
</div>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Kombi<span>6,90€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cat-snacks" class="snack-category hidden">
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card">
|
||||
@@ -647,75 +820,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cat-kombi" class="snack-category hidden">
|
||||
<div class="snack-grid">
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<h3>Kleines Menü</h3>
|
||||
<p style="font-size: 0.8rem; color: #86868b; margin-bottom: 10px;">0,33L Getränk + Popcorn Klein</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>5,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<h3>Mittleres Menü</h3>
|
||||
<p style="font-size: 0.8rem; color: #86868b; margin-bottom: 10px;">0,5L Getränk + Popcorn Mittel</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>6,50€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<span class="badge">Bestseller</span>
|
||||
<h3>Großes Menü</h3>
|
||||
<p style="font-size: 0.8rem; color: #86868b; margin-bottom: 10px;">1L Getränk + Popcorn Groß</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>8,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<div class="snack-img"><img src="img/hopperskidsmenu.jpg" alt="Hoppers Kids Menu"></div>
|
||||
<span class="badge">SPECIAL</span>
|
||||
<h3>Limitiertes Menü</h3>
|
||||
<p style="font-size: 0.8rem; color: #86868b; margin-bottom: 10px;">0,5L Getränk im HOPPERS Becher + HOPPERS Popcorn Schale<br>+HOPPERS Figur</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>10,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<div class="snack-img"><img src="img/mariokidsmenu.png" alt="Mario Kids Menu"></div>
|
||||
<br>
|
||||
<span class="badge">SPECIAL</span>
|
||||
<h3>Limitiertes Menü</h3>
|
||||
<p style="font-size: 0.8rem; color: #86868b; margin-bottom: 10px;">0,5L Getränk im MARIO GALXY Becher + MARIO GALAXY Popcorn Schale</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>10,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snack-card highlight">
|
||||
<div class="snack-info">
|
||||
<div class="snack-img"><img src="img/zoomaniakidsmenu.jpg" alt="Zoomania Kids Menu"></div>
|
||||
<br>
|
||||
<span class="badge">SPECIAL</span>
|
||||
<h3>Limitiertes Menü</h3>
|
||||
<p style="font-size: 0.8rem; color: #86868b; margin-bottom: 10px;">0,5L Getränk im ZOOMANIA Becher + ZOOMANIA Popcorn Schale<br>+ Figur zum aussuchen</p>
|
||||
<div class="size-selector">
|
||||
<button class="size-chip">Menü-Preis <span>10,00€</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cat-eis" class="snack-category hidden">
|
||||
<div class="coming-soon-banner">
|
||||
<h2>Eiscreme & Shakes</h2>
|
||||
@@ -735,8 +839,8 @@
|
||||
<div class="header-sub-info">
|
||||
<p id="modal-info-text">Saal • Zeit</p>
|
||||
<div id="tech-badges" class="tech-badges-container hidden">
|
||||
<img src="img/Dolby.png" alt="Dolby" class="tech-badge">
|
||||
<img src="img/dbox.jpg" alt="D-Box" class="tech-badge">
|
||||
<img src="img/dolby.png" alt="Dolby" class="tech-badge">
|
||||
<img src="img/dbox.png" alt="D-Box" class="tech-badge">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -948,11 +1052,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="dist/main.js"></script>
|
||||
<script type="module" src="dist/cart.js"></script>
|
||||
<script type="module" src="dist/booking.js"></script>
|
||||
<script type="module" src="dist/checkout.js"></script>
|
||||
<script type="module" src="dist/account.js"></script>
|
||||
<script src="account.js"></script>
|
||||
<script src="cart.js"></script>
|
||||
<script src="booking.js"></script>
|
||||
<script src="checkout.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,34 +1,13 @@
|
||||
import { currentUser, loginUser, openAccountDashboard, registerUser } from "./account.js";
|
||||
import { openBooking } from "./booking.js";
|
||||
import { renderCart, saveCart, updateCartBadge } from "./cart.js";
|
||||
import { renderCheckout } from "./checkout.js";
|
||||
import type { MovieCatalog as MovieCatalogInterface } from "./interfaces.js";
|
||||
|
||||
export const movieCatalog: MovieCatalogInterface[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "oh hell nah",
|
||||
genre: "n",
|
||||
duration: 3,
|
||||
fsk: "jfd",
|
||||
description: "jsss",
|
||||
poster: "g",
|
||||
backdrop: "f",
|
||||
rating: 1,
|
||||
year: "d",
|
||||
}
|
||||
]
|
||||
|
||||
// Shared app state for legacy script files (account.js, booking.js, cart.js, checkout.js)
|
||||
export const prices: Record<string, number> = { normal: 11.0, imax: 15.0, vip: 12.0, dbox: 16.0 };
|
||||
export var seatLayouts = {
|
||||
// Shared app state for legacy script files (account.js, booking.js, cart.js, checkout.js)
|
||||
var prices = { normal: 11.0, imax: 15.0, vip: 12.0, dbox: 16.0 };
|
||||
var seatLayouts = {
|
||||
"Kino 1": { rows: 6, left: 3, right: 7, vipRows: [5], dbox: [] },
|
||||
"Kino 2": { rows: 7, left: 5, right: 5, vipRows: [6], dbox: [] },
|
||||
"Deluxe 1": { rows: 10, left: 7, right: 8, vipRows: [9], dbox: [{ r: 4, c: 5, w: 4 }] },
|
||||
IMAX: { rows: 15, left: 10, right: 10, vipRows: [], dbox: [], isImax: true }
|
||||
};
|
||||
export var cart: any[] = JSON.parse(localStorage.getItem("eagleCart") || '[]');
|
||||
export var occupiedSeatsData = JSON.parse(localStorage.getItem("eagleOccupied") || '{}');
|
||||
var cart = JSON.parse(localStorage.getItem("eagleCart")) || [];
|
||||
var occupiedSeatsData = JSON.parse(localStorage.getItem("eagleOccupied")) || {};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const views = {
|
||||
@@ -76,6 +55,269 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
three: document.getElementById("checkout-step-3")
|
||||
};
|
||||
|
||||
const movieCatalog = [
|
||||
{
|
||||
title: "Zoomania 2",
|
||||
genre: "Animation",
|
||||
duration: 148,
|
||||
fsk: "6",
|
||||
description: "In Walt Disney Animation Studios’ \"Zoomania 2\" geraten die tierischen Detektive Judy Hopps und Nick Wilde auf die rätselhafte Spur eines geheimnisvollen Reptils, das in Zoomania auftaucht und die Metropole völlig auf den Kopf stellt: Gary De’Snake! ",
|
||||
poster: "img/zoomania-2.jpg",
|
||||
backdrop: "img/zoomania-2.jpg"
|
||||
},
|
||||
{
|
||||
title: "Shelter",
|
||||
genre: "Action, Abenteuer",
|
||||
duration: 147,
|
||||
fsk: "16",
|
||||
description: "Michael Mason, ein untergetauchter Elite-Agent, lebt auf einer abgelegenen Insel in Schottland. Als er in einem schweren Sturm ein Mädchen vor dem Ertrinken rettet, setzt er damit eine Kette von Ereignissen in Gang, die sein Versteck enttarnen und ihn zurück in die Welt zwingen. Dort muss er nicht nur um das Überleben der Teenagerin kämpfen, sondern sich auch den Dämonen seiner Vergangenheit stellen…",
|
||||
poster: "img/shelter.jpg",
|
||||
backdrop: "img/shelter.jpg"
|
||||
},
|
||||
{
|
||||
title: "Mutiny",
|
||||
genre: "Action, Abenteuer",
|
||||
duration: "tba",
|
||||
fsk: "?",
|
||||
description: "Als sein milliardenschwerer Boss vor seinen Augen einem Mordkomplott zum Opfer fällt, wird Cole Reed (Jason Statham) für das Verbrechen verantwortlich gemacht. Im Bestreben dessen Tod zu rächen und die Täter zu überführen, gelangt Reed an Bord eines Frachters und stößt dabei auf eine internationale Verschwörung. Im Alleingang startet Cole eine gnadenlose Racheaktion auf hoher See…",
|
||||
poster: "img/mutiny.jpg",
|
||||
backdrop: "img/mutiny.jpg"
|
||||
},
|
||||
{
|
||||
title: "Der Austronaut - Project Hail Mary",
|
||||
genre: "Sci-Fi",
|
||||
duration: 156,
|
||||
fsk: "12",
|
||||
description: "Der Naturwissenschaftslehrer Ryland Grace wacht eines Tages auf einem Raumschiff auf - Lichtjahre von zu Hause entfernt und ohne Erinnerung daran, wer er ist oder wie er dorthin gekommen ist.",
|
||||
poster: "img/derAustronaut.jpg",
|
||||
backdrop: "img/derAustronaut.jpg"
|
||||
},
|
||||
{
|
||||
title: "Der Super Mario Galaxy Film",
|
||||
genre: "Animation",
|
||||
duration: 98,
|
||||
fsk: "6",
|
||||
description: "''Der Super Mario Galaxy Film'' ist der neue Animationsfilm, basierend auf der Welt von „Super Mario Bros.“!",
|
||||
poster: "img/mariogalaxy.jpg",
|
||||
backdrop: "img/mariogalaxy.jpg"
|
||||
},
|
||||
{
|
||||
title: "Hoppers",
|
||||
genre: "Animation",
|
||||
duration: 105,
|
||||
fsk: "6",
|
||||
description: "In Disney Pixars ''Hoppers'' hoppt Tierfreundin Mabel mithilfe neuester Technologie in einen Roboterbiber und macht die Tierwelt (un)sicher! ",
|
||||
poster: "img/hoppers.jpg",
|
||||
backdrop: "img/hoppers.jpg"
|
||||
},
|
||||
{
|
||||
title: "Solo Mio",
|
||||
genre: "Lovestory, Komödie",
|
||||
duration: 96,
|
||||
fsk: "12",
|
||||
description: "Nachdem er in Rom am Altar stehen gelassen wurde, beschließt ein Mann, allein in die Flitterwochen zu fahren.",
|
||||
poster: "img/solomio.png",
|
||||
backdrop: "img/solomio.png"
|
||||
},
|
||||
{
|
||||
title: "The Mandalorian and Grogu",
|
||||
genre: "Action, Abenteuer",
|
||||
duration: 'tba',
|
||||
fsk: "?",
|
||||
description: "Während die Neue Republik daran arbeitet, alles zu schützen, wofür die Rebellion gekämpft hat, sichern sie sich die Unterstützung des legendären mandalorianischen Kopfgeldjägers Din Djarin und seines jungen Lehrlings Grogu. ",
|
||||
poster: "img/mandalorian.jpeg",
|
||||
backdrop: "img/mandalorian.jpeg"
|
||||
},
|
||||
{
|
||||
title: "Glennkill - Ein Schafskrimi",
|
||||
genre: "Krimi, Komödie",
|
||||
duration: 'tba',
|
||||
fsk: "?",
|
||||
description: "Schäfer George liest seinen geliebten Schafen jeden Abend Krimis vor und nimmt an, dass sie kein Wort davon verstehen. Doch als ein mysteriöser Vorfall das friedliche Leben auf der Farm durchbricht, wird den Schafen bewusst, dass sie selbst nun die Ermittlungen übernehmen müssen. Also folgen sie der Spur der Beweise und machen menschliche Verdächtige ausfindig.",
|
||||
poster: "img/glennkill.jpg",
|
||||
backdrop: "img/glennkill.jpg"
|
||||
},
|
||||
{
|
||||
title: "Masters of the Universe",
|
||||
genre: "Action, Science-Fiction",
|
||||
duration: 'tba',
|
||||
fsk: "?",
|
||||
description: "Nach 15 Jahren der Trennung führt das Schwert der Macht Prinz Adam zurück nach Eternia und er entdeckt, dass seine Heimat unter der Herrschaft von Skeletor in Trümmern liegt.",
|
||||
poster: "img/masteruniverse.jpg",
|
||||
backdrop: "img/masteruniverse.jpg"
|
||||
},
|
||||
{
|
||||
title: "Minions and Monsters",
|
||||
genre: "Animation, Familie",
|
||||
duration: 'tba',
|
||||
fsk: "?",
|
||||
description: "MINIONS & MONSTER erzählt die abgefahrene, aberwitzige und natürlich absolut wahre Geschichte von den Minions und wie sie Hollywood erobern, Filmstars werden, alles verlieren, Monster auf die Welt loslassen und sich dann heldenhaft daranmachen, den Planeten vor genau dem Chaos zu retten, das sie selbst angerichtet haben. ",
|
||||
poster: "img/minionsmonsters.jpg",
|
||||
backdrop: "img/minionsmonsters.jpg"
|
||||
},
|
||||
{
|
||||
title: "Spider Man - Brand New Day",
|
||||
genre: "Action",
|
||||
duration: 'tba',
|
||||
fsk: "?",
|
||||
description: "Spider-Man: Brand New Day erzählt die Geschichte von Peter Parker, der sich mit den Herausforderungen eines neuen Lebens auseinandersetzt.",
|
||||
poster: "img/spidermannewday.jpg",
|
||||
backdrop: "img/spidermannewday.jpg"
|
||||
},
|
||||
{
|
||||
title: "Scream VII",
|
||||
genre: "Horror",
|
||||
duration: 'tba',
|
||||
fsk: "18",
|
||||
description: "Als in der ruhigen Stadt, in der Sidney Prescott sich ein neues Leben aufgebaut hat, ein neuer Ghostface-Killer auftaucht, werden ihre schlimmsten Befürchtungen wahr, denn ihre Tochter ist sein nächstes Ziel.",
|
||||
poster: "img/screamvii.jpg",
|
||||
backdrop: "img/screamvii.jpg"
|
||||
},
|
||||
{
|
||||
title: "Blade Runner 2049",
|
||||
genre: "Science-Fiction, Action",
|
||||
duration: "163",
|
||||
fsk: "16",
|
||||
description: "30 Jahre nach den Ereignissen des ersten Films fördert ein neuer Blade Runner, der LAPD Polizeibeamte K (Ryan Gosling), ein lange unter Verschluss gehaltenes Geheimnis zu Tage, welches das Potential hat, die noch vorhandenen gesellschaftlichen Strukturen in Chaos zu stürzen. Die Entdeckungen von K führen ihn auf die Suche nach Rick Deckard (Harrison Ford), einem seit 30 Jahren verschwundenen, ehemaligen LAPD Blade Runner.",
|
||||
poster: "img/bladerunner2049.jpg",
|
||||
backdrop: "img/bladerunner2049.jpg"
|
||||
},
|
||||
{
|
||||
title: "THE FALL GUY",
|
||||
genre: "Action, Thriller",
|
||||
duration: 126,
|
||||
fsk: "16",
|
||||
description: "Ein angeschlagener Stuntman, der seine besten Zeiten hinter sich hat, findet sich in einem Film mit dem Star wieder, für den er vor langer Zeit gedoubelt hat und der ihn ersetzt hat. Der Haken an der Sache ist jedoch, dass der Star verschwunden ist.",
|
||||
poster: "img/fallguy.jpg",
|
||||
backdrop: "img/fallguy.jpg"
|
||||
},
|
||||
{
|
||||
title: "THE MEG",
|
||||
genre: "Action, Science-Fiction",
|
||||
duration: 113,
|
||||
fsk: "12",
|
||||
description: "Jonas Taylor ist Tiefseetaucher im geheimen Auftrag der US Navy. Als er mit seiner Crew auf Erkundungstour im Marianengraben unterwegs ist, treffen sie auf einen urzeitlichen, überdimensionierten Hai, einen sogenannten Megalodon. Jonas überlebt die Begegnung als einziger und widmet sich von nun an dem Studium der Paläobiologie. Ein Freund aus der Vergangenheit, Masao Tanaka, bittet Jonas darum mit seiner Hilfe ein U-Boot im Marianengraben zu bergen. Jonas willigt ein, obwohl er noch immer um seine verstorbenen Teammitglieder trauert. Als sich Jonas und Masao dort befinden, sichten sie erneut den Megalodon.",
|
||||
poster: "img/meg.jpg",
|
||||
backdrop: "img/meg.jpg"
|
||||
},
|
||||
{
|
||||
title: "THE MEG 2: Die Tiefe",
|
||||
genre: "Action, Science-Fiction",
|
||||
duration: 116,
|
||||
fsk: "12",
|
||||
description: "Was ist besser als ein Riesenhai? Ganz einfach: viele Riesenhaie! In diesem spannenden Abenteuer tauchen die Zuschauer gemeinsam mit den weltbekannten Action-Ikonen Jason Statham und Wu Jing in unerforschte Gewässer ein. Als Leiter eines Forschungsteams machen sich die beiden Helden auf einen waghalsigen Erkundungstauchgang in die tiefsten Tiefen des Ozeans. Doch ihre Reise wird jäh unterbrochen, als ein skrupelloser Minenkonzern ihre Mission durchkreuzt und sie sich plötzlich in einem Kampf ums Überleben wiederfinden.",
|
||||
poster: "img/meg2.jpg",
|
||||
backdrop: "img/meg2.jpg"
|
||||
},
|
||||
{
|
||||
title: "Homefront",
|
||||
genre: "Action, Thriller",
|
||||
duration: 100,
|
||||
fsk: "16",
|
||||
description: "Ex-Drogencop Phil Broker will mit seiner Tochter Maddy in Louisiana ein neues Leben beginnen. Aber dann gerät Maddy auf dem Schulhof in eine Prügelei mit einem Jungen. Dessen Onkel ist ausgerechnet der Crystal-Meth-Dealer Gator Bodine. Als sich Broker nicht einschüchtern lässt und Bodine Hinweise auf seine wahre Identität entdeckt, zögert er nicht, die Feinde aus Brokers Vergangenheit auf seine Spur zu bringen.",
|
||||
poster: "img/homefront.jpg",
|
||||
backdrop: "img/homefront.jpg"
|
||||
},
|
||||
{
|
||||
title: "Cash Truck",
|
||||
genre: "Action",
|
||||
duration: 118,
|
||||
fsk: "16",
|
||||
description: ""
|
||||
},
|
||||
{
|
||||
title: "Die Gangster Gang",
|
||||
genre: "Animation, Komödie",
|
||||
duration: 100,
|
||||
fsk: "6",
|
||||
description: "Mastermind Mr. Wolf, Safeknacker Mr. Snake, Mr. Shark, der Meister der Verwandlung, Mr. Piranha, der Mann fürs Grobe, und die geniale Hackerin Ms. Tarantula sind die „Gangster Gang“, die meistgesuchten Verbrecher von allen. Als die fünf Bösewichte nach einem spektakulären Raub gefasst werden, geloben sie inständig Besserung, um dem Gefängnis zu entgehen, und merken bald, dass Gutes tun gar nicht so schlecht ist. Doch dann bedroht ein neuer Schurke die Stadt ...",
|
||||
poster: "img/gangstergang.jpg",
|
||||
backdrop: "img/gangstergang.jpg"
|
||||
},
|
||||
{
|
||||
title: "Die Gangster Gang 2",
|
||||
genre: "Animation, Komödie",
|
||||
duration: 104,
|
||||
fsk: "6",
|
||||
description: "Unsere Lieblings-Gangster sind zurück: Im brandneuen, actiongeladenen Abenteuer der gefeierten Antihelden von DreamWorks Animation steht die chaotische, nun geläuterte Gangster Gang endlich auf der guten Seite – wirklich, sie versuchen es! ",
|
||||
poster: "img/gangstergang2.png",
|
||||
backdrop: "img/gangstergang2.png"
|
||||
},
|
||||
{
|
||||
title: "GOAT - Bock auf große Sprünge",
|
||||
genre: "Animation",
|
||||
duration: 100,
|
||||
fsk: "6",
|
||||
description: "Will, eine kleine Ziege mit großen Träumen, bekommt die einmalige Chance, bei den Profis mitzumachen und Rarball zu spielen - ein hochintensiver, gemischter Vollkontaktsport, der von den schnellsten und wildesten Tieren der Welt dominiert wird. Wills neue Teamkollegen sind nicht begeistert, eine kleine Ziege in ihrem Team zu haben, aber Will ist entschlossen, den Sport zu revolutionieren und ein für alle Mal zu beweisen, dass \"Smalls can ball!\"",
|
||||
poster: "img/goat.jpg",
|
||||
backdrop: "img/goat.jpg"
|
||||
},
|
||||
{
|
||||
title: "TOY STORY",
|
||||
genre: "Animation",
|
||||
duration: 81,
|
||||
fsk: "6",
|
||||
description: "Als Andys Lieblingspuppe hat Woody im Kinderzimmer das Sagen. Kaum ist der Junge nicht da, erwacht die Cowboy-Figur zum Leben, und mit ihm auch all das andere Spielzeug um ihn herum. Aufgeregt debattiert man über Andys bevorstehenden Geburtstag. Es wird befürchtet, daß ihr Besitzer ein neues Geschenk bevorzugen wird. Und tatsächlich: Der Neuankömmling Buzz Lightyear, ein stolzer Space Ranger, avanciert zu Andys Favoriten. Diese Herabstufung will Woody nicht hinnehmen.",
|
||||
poster: "img/toystory1.jpg",
|
||||
backdrop: "img/toystory1.jpg"
|
||||
},
|
||||
{
|
||||
title: "TOY STORY 2",
|
||||
genre: "Animation",
|
||||
duration: 92,
|
||||
fsk: "6",
|
||||
description: "Als Woody von einem fiesen Sammler entführt wird macht sich die Puppen-Clique auf, ihren Freund zu befreien - aber schon auf die andere Straßenseite zu gelangen, gestaltet sich schwierig, ganz zu schweigen von den lauernden Gefahren im Spielzeugladen gegenüber: Doppelgänger stellen sich Buzz Lightyear in den Weg, durchgeknallte Barbie-Sirenen drohen die edlen Ritter vom rechten Weg abzubringen, und obendrein entkommt Zurg, der böse Imperator, aus seiner Plastikbox",
|
||||
poster: "img/toystory2.jpg",
|
||||
backdrop: "img/toystory2.jpg"
|
||||
},
|
||||
{
|
||||
title: "TOY STORY 3",
|
||||
genre: "Animation",
|
||||
duration: 103,
|
||||
fsk: "6",
|
||||
description: "Der Moment des Abschieds ist gekommen: Andy ist den Kinderschuhen entwachsen und macht sich bereit, aufs College zu gehen. Für sein stets getreues Spielzeug hat er keine Verwendung mehr. Andys Mutter spendet die Spielsachen der Kindertagesstätte Sunnyside. Doch dieses angebliche Paradies entpuppt sich nach zuerst freundlicher Aufnahme durch die alteingesessenen Spielzeuge bald zum Gefängnis für die Freunde. Ein ausgeklügelter Ausbruchsplan wird ausgearbeitet - die Zeit drängt, denn hier geht nicht alles mit rechten Dingen zu.",
|
||||
poster: "img/toystory3.jpg",
|
||||
backdrop: "img/toystory3.jpg"
|
||||
},
|
||||
{
|
||||
title: "TOY STORY 4 - Alles hört auf kein Kommando",
|
||||
genre: "Animation",
|
||||
duration: 99,
|
||||
fsk: "6",
|
||||
description: "Woody wusste immer um seinen Platz im (Spielzeug-)Universum, seine Priorität war es, sich um „sein“ Kind zu kümmern, sei es sein alter Kindheits-Freund Andy oder die süße Bonnie. Aber als Bonnie ein äußerst widerspenstiges neues Spielzeug in ihre Sammlung aufnimmt, hat Woodys ruhiger gewordenes Kinderzimmerleben ein Ende. Denn Forky hat eine schwere Identitätskrise und ist davon überzeugt, kein Spielzeug zu sein. Zusammen mit alten und neuen Freunden erleben Forky und Woody den Roadtrip ihres Lebens und lernen dabei, dass die Welt für ein Spielzeug viel größer ist als jemals gedacht.",
|
||||
poster: "img/toystory4.jpg",
|
||||
backdrop: "img/toystory4.jpg"
|
||||
|
||||
},
|
||||
{
|
||||
title: "TOY STORY 5",
|
||||
genre: "Animation",
|
||||
duration: 'tba',
|
||||
fsk: "6",
|
||||
description: "Die Spielzeuge sind zurück und dieses Mal werden die Aufgaben von Buzz Lightyear, Woody, Jessie und dem Rest der Bande herausgefordert, als sie mit Lilypad (Stimme von Greta Lee) konfrontiert werden, einem brandneuen Tablet-Gerät, das mit seinen eigenen störenden Ideen darüber kommt, was das Beste für ihr Kind Bonnie ist. Wird die Spielzeit jemals wieder dieselbe sein?",
|
||||
poster: "img/toystory5.png",
|
||||
backdrop: "img/toystory5.png"
|
||||
},
|
||||
{
|
||||
title: "Die Monster AG - Monsters Inc.",
|
||||
genre: "Animation, Familie",
|
||||
duration: 92,
|
||||
fsk: "6",
|
||||
description: "In der Monster-AG-Fabrik gehen die Bösewichte eifrig ihrer Arbeit nach: Über Schranktüren schleichen sie sich in Kinderzimmer ein und sammeln die Angstschreie ihrer Bewohner, die den Strom für Monstropolis liefern. Ungekrönter Star unter den einfallsreichen \"Schreckeinjagern\" ist Sully. Dem passiert eines Tages ein folgenschweres Missgeschick: Das kleine Mädchen Boo, dem er wie gewohnt einen kräftigen Schock versetzen will, verkrallt sich in sein Fell. Als er dann mit dem Kind in die Fabrik zurückkehrt, bricht das totale Chaos aus...",
|
||||
poster: "img/monsterag.png",
|
||||
backdrop: "img/monsterag.png"
|
||||
},
|
||||
{
|
||||
title: "Die Monster Uni - Monsters University",
|
||||
genre: "Animation, Familie",
|
||||
duration: 104,
|
||||
fsk: "6",
|
||||
description: "Auch Monster müssen lernen, wie man andere wirkungsvoll erschreckt. Genau hierfür gibt es die Monster-Universität, einen Ort an dem aus harmlosen kleinen Monstern die ganz großen Schrecken gemacht werden. Exakt davon träumt Mike schon die ganze Zeit, deshalb schreibt er sich direkt für die Scheckwissenschaften ein. Nun ist er ein Student und lernt viele neue Monster kennen, die wie er den Studiengang schaffen wollen. Doch das ist nicht so einfach wie anfangs erst gedacht, denn neben guten Noten geht es auch um das Ansehen an der Uni. So bricht zwischen Mike und seinem neuen Bekannten Sulley ein wahrer Kampf um die besseren Schrecker aus.",
|
||||
poster: "img/monsteruni.jpg",
|
||||
backdrop: "img/monsteruni.jpg"
|
||||
}
|
||||
];
|
||||
|
||||
const hallRotation = ["IMAX", "Deluxe 1", "Kino 1", "Kino 2"];
|
||||
const timePatterns = [
|
||||
["13:00", "15:20", "17:40", "20:00", "22:20"],
|
||||
@@ -84,10 +326,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
["13:00", "16:00", "18:20", "20:40"]
|
||||
];
|
||||
|
||||
let movieProgram: any = []; // TODO: Find type
|
||||
let heroItems: any = []; // TODO: find Type
|
||||
let movieProgram = [];
|
||||
let heroItems = [];
|
||||
let heroIndex = 0;
|
||||
let heroTimer: any = null; // TODO: find type
|
||||
let heroTimer = null;
|
||||
|
||||
const weekdayShort = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
||||
|
||||
@@ -106,7 +348,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
const showMovieList = (programIndexToFocus: number = NaN) => {
|
||||
const showMovieList = (programIndexToFocus = null) => {
|
||||
hideAllViews();
|
||||
views.list?.classList.remove("hidden");
|
||||
|
||||
@@ -123,7 +365,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
};
|
||||
|
||||
const showStaticView = (viewElement: HTMLElement) => {
|
||||
const showStaticView = (viewElement) => {
|
||||
if (!viewElement) {
|
||||
return;
|
||||
}
|
||||
@@ -152,20 +394,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
ui.bookingModal?.classList.add("hidden");
|
||||
};
|
||||
|
||||
const escapeHtml = (value: string) => String(value || "")
|
||||
const escapeHtml = (value) => String(value || "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
|
||||
const formatDateShort = (dateObj: any) => {
|
||||
const formatDateShort = (dateObj) => {
|
||||
const day = String(dateObj.getDate()).padStart(2, "0");
|
||||
const month = String(dateObj.getMonth() + 1).padStart(2, "0");
|
||||
return `${day}.${month}.`;
|
||||
};
|
||||
|
||||
const buildDayMeta = (offset: number) => {
|
||||
const buildDayMeta = (offset) => {
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
date.setDate(date.getDate() + offset);
|
||||
@@ -199,15 +441,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
};
|
||||
|
||||
const buildScheduleForMovie = (movieIndex: number) => {
|
||||
const buildScheduleForMovie = (movieIndex) => {
|
||||
return Array.from({ length: 7 }, (_, dayOffset) => {
|
||||
const dayMeta = buildDayMeta(dayOffset);
|
||||
const pattern = timePatterns[(movieIndex + dayOffset) % timePatterns.length] || "Error reading";
|
||||
const pattern = timePatterns[(movieIndex + dayOffset) % timePatterns.length];
|
||||
const desiredCount = 4 + ((movieIndex + dayOffset) % 2);
|
||||
const showCount = Math.min(pattern.length, desiredCount);
|
||||
|
||||
//@ts-ignore
|
||||
const showings = pattern.slice(0, showCount).map((time: any, slotIndex: number) => { // TODO: fix map issue
|
||||
const showings = pattern.slice(0, showCount).map((time, slotIndex) => {
|
||||
const hall = hallRotation[(movieIndex + dayOffset + slotIndex) % hallRotation.length];
|
||||
return { time, hall };
|
||||
});
|
||||
@@ -220,14 +461,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
|
||||
const buildMovieProgram = () => {
|
||||
movieProgram = movieCatalog.map((movie: any, movieIndex: number) => ({
|
||||
movieProgram = movieCatalog.map((movie, movieIndex) => ({
|
||||
...movie,
|
||||
schedule: buildScheduleForMovie(movieIndex)
|
||||
}));
|
||||
heroItems = movieProgram.slice(0, 5);
|
||||
};
|
||||
|
||||
const setHeroSlide = (index: number) => {
|
||||
const setHeroSlide = (index) => {
|
||||
if (!heroItems.length || !ui.heroSlider) {
|
||||
return;
|
||||
}
|
||||
@@ -256,18 +497,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
ui.heroSlider.innerHTML = heroItems.map((movie: any, index: number) => `
|
||||
ui.heroSlider.innerHTML = heroItems.map((movie, index) => `
|
||||
<div class="hero-slide ${index === 0 ? "active" : ""}" style="background-image: linear-gradient(118deg, rgba(0,0,0,0.34), rgba(0,0,0,0.04)), url('${escapeHtml(movie.backdrop || movie.poster)}');"></div>
|
||||
`).join("");
|
||||
|
||||
if (ui.heroDots) {
|
||||
ui.heroDots.innerHTML = heroItems.map((_: any, index: number) => `
|
||||
ui.heroDots.innerHTML = heroItems.map((_, index) => `
|
||||
<button type="button" class="hero-dot ${index === 0 ? "active" : ""}" data-hero-index="${index}"></button>
|
||||
`).join("");
|
||||
|
||||
ui.heroDots.addEventListener("click", (event: any) => {
|
||||
const dotTarget = event.target || 0;
|
||||
const dot = dotTarget.closest(".hero-dot");
|
||||
ui.heroDots.addEventListener("click", (event) => {
|
||||
const dot = event.target.closest(".hero-dot");
|
||||
if (!dot) {
|
||||
return;
|
||||
}
|
||||
@@ -298,8 +538,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: implement movie interface
|
||||
ui.nowRunningRow.innerHTML = movieProgram.map((movie: any, index: number) => /*html*/`
|
||||
ui.nowRunningRow.innerHTML = movieProgram.map((movie, index) => `
|
||||
<article class="running-poster">
|
||||
<img src="${escapeHtml(movie.poster)}" alt="${escapeHtml(movie.title)}">
|
||||
<div class="running-meta">
|
||||
@@ -311,7 +550,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
`).join("");
|
||||
};
|
||||
|
||||
const renderScheduleRows = (programIndex: number, dayIndex: number) => {
|
||||
const renderScheduleRows = (programIndex, dayIndex) => {
|
||||
const movie = movieProgram[programIndex];
|
||||
if (!movie) {
|
||||
return;
|
||||
@@ -323,7 +562,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
body.innerHTML = day.showings.map((showing: { hall: string; time: string; }) => /*html*/`
|
||||
body.innerHTML = day.showings.map((showing) => `
|
||||
<button class="schedule-row time-chip program-time-row" data-movie="${escapeHtml(movie.title)}" data-hall="${escapeHtml(showing.hall)}" data-time="${escapeHtml(showing.time)}">
|
||||
<span>${escapeHtml(day.long)}</span>
|
||||
<span class="hall-pill">${escapeHtml(showing.hall)}</span>
|
||||
@@ -337,15 +576,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
ui.movieProgramList.innerHTML = movieProgram.map((movie: { schedule: any[]; poster: string; title: string; fsk: string; duration: any; genre: string; description: string; }, programIndex: any) => {
|
||||
const dayTabs = movie.schedule.map((day, dayIndex) => /*html*/`
|
||||
ui.movieProgramList.innerHTML = movieProgram.map((movie, programIndex) => {
|
||||
const dayTabs = movie.schedule.map((day, dayIndex) => `
|
||||
<button type="button" class="program-day-tab ${dayIndex === 0 ? "active" : ""}" data-program-index="${programIndex}" data-day-index="${dayIndex}">
|
||||
<span>${escapeHtml(day.short)}</span>
|
||||
<small>${escapeHtml(formatDateShort(day.date))}</small>
|
||||
</button>
|
||||
`).join("");
|
||||
|
||||
return /*html*/`
|
||||
return `
|
||||
<article class="detailed-card program-card reveal-on-scroll" data-program-index="${programIndex}">
|
||||
<div class="card-left">
|
||||
<img src="${escapeHtml(movie.poster)}" alt="${escapeHtml(movie.title)}">
|
||||
@@ -371,7 +610,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
movieProgram.forEach((_: any, programIndex: number) => {
|
||||
movieProgram.forEach((_, programIndex) => {
|
||||
renderScheduleRows(programIndex, 0);
|
||||
});
|
||||
};
|
||||
@@ -419,16 +658,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
ui.linkSnacks?.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
if (views.snacks) {
|
||||
showStaticView(views.snacks);
|
||||
}
|
||||
showStaticView(views.snacks);
|
||||
});
|
||||
|
||||
ui.linkAbout?.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
if (views.about) {
|
||||
showStaticView(views.about);
|
||||
}
|
||||
showStaticView(views.about);
|
||||
});
|
||||
|
||||
ui.linkCart?.addEventListener("click", (event) => {
|
||||
@@ -458,17 +693,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
|
||||
const bindProgramActions = () => {
|
||||
views.moviesGrid?.addEventListener("click", (event: any) => {
|
||||
views.moviesGrid?.addEventListener("click", (event) => {
|
||||
const trigger = event.target.closest(".open-program-btn");
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
const programIndex = Number(trigger.dataset.programIndex) || 0;
|
||||
const programIndex = Number(trigger.dataset.programIndex || 0);
|
||||
showMovieList(programIndex);
|
||||
});
|
||||
|
||||
ui.movieProgramList?.addEventListener("click", (event: any) => {
|
||||
ui.movieProgramList?.addEventListener("click", (event) => {
|
||||
const dayButton = event.target.closest(".program-day-tab");
|
||||
if (!dayButton) {
|
||||
return;
|
||||
@@ -478,7 +713,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const dayIndex = Number(dayButton.dataset.dayIndex || 0);
|
||||
|
||||
const tabRow = dayButton.closest(".program-day-tabs");
|
||||
tabRow?.querySelectorAll(".program-day-tab").forEach((tab: { classList: { remove: (arg0: string) => any; }; }) => tab.classList.remove("active"));
|
||||
tabRow?.querySelectorAll(".program-day-tab").forEach((tab) => tab.classList.remove("active"));
|
||||
dayButton.classList.add("active");
|
||||
|
||||
renderScheduleRows(programIndex, dayIndex);
|
||||
@@ -504,7 +739,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
openButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const targetId = button.getAttribute("data-home-view-open") as keyof typeof targetMap;
|
||||
const targetId = button.getAttribute("data-home-view-open");
|
||||
const target = targetId ? targetMap[targetId] : null;
|
||||
if (target) {
|
||||
showStaticView(target);
|
||||
@@ -557,11 +792,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
const THEME_KEY = "eagleTheme";
|
||||
|
||||
const applyTheme = (theme: any) => {
|
||||
const applyTheme = (theme) => {
|
||||
const isLight = theme === "light";
|
||||
document.body.classList.toggle("theme-light", isLight);
|
||||
document.body.classList.toggle("theme-dark", !isLight);
|
||||
//@ts-ignore
|
||||
ui.themeToggle.classList.toggle("is-light", isLight);
|
||||
localStorage.setItem(THEME_KEY, isLight ? "light" : "dark");
|
||||
};
|
||||
@@ -578,14 +812,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const bindAccountActions = () => {
|
||||
const registerModal = document.getElementById("register-modal");
|
||||
const forgotModal = document.getElementById("forgot-modal");
|
||||
const forgotEmailInput = document.getElementById("forgot-email") as HTMLInputElement;
|
||||
const forgotEmailInput = document.getElementById("forgot-email");
|
||||
const resetMessage = document.getElementById("reset-message");
|
||||
const loginError = document.getElementById("login-error");
|
||||
const loginEmailInput = document.getElementById("login-email");
|
||||
const loginPasswordInput = document.getElementById("login-password");
|
||||
|
||||
const openModal = (modal: HTMLElement | null) => modal?.classList.remove("hidden");
|
||||
const closeModal = (modal: HTMLElement | null) => modal?.classList.add("hidden");
|
||||
const openModal = (modal) => modal?.classList.remove("hidden");
|
||||
const closeModal = (modal) => modal?.classList.add("hidden");
|
||||
const triggerLogin = () => {
|
||||
loginError?.classList.add("hidden");
|
||||
if (typeof loginUser === "function") {
|
||||
@@ -621,7 +855,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
document.getElementById("btn-forgot-password")?.addEventListener("click", () => {
|
||||
if (forgotEmailInput != null) {
|
||||
if (forgotEmailInput) {
|
||||
forgotEmailInput.value = "";
|
||||
}
|
||||
resetMessage?.classList.add("hidden");
|
||||
@@ -666,10 +900,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
|
||||
const bindGlobalDocumentClicks = () => {
|
||||
document.addEventListener("click", (event: any) => {
|
||||
document.addEventListener("click", (event) => {
|
||||
if (event.target.classList.contains("opt-btn")) {
|
||||
const optionGroup = event.target.parentElement;
|
||||
optionGroup?.querySelectorAll(".opt-btn").forEach((button: { classList: { remove: (arg0: string) => any; }; }) => button.classList.remove("active"));
|
||||
optionGroup?.querySelectorAll(".opt-btn").forEach((button) => button.classList.remove("active"));
|
||||
event.target.classList.add("active");
|
||||
}
|
||||
|
||||
@@ -678,17 +912,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const row = deleteBtn.closest(".cart-item-row");
|
||||
if (row) {
|
||||
row.classList.add("slide-out-left");
|
||||
row.querySelectorAll("button").forEach((button: { disabled: boolean; }) => {
|
||||
row.querySelectorAll("button").forEach((button) => {
|
||||
button.disabled = true;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
//@ts-ignore
|
||||
removeFromCartByKey(deleteBtn.dataset.key); //TODO: removeFromCartByKey doesnt exist
|
||||
removeFromCartByKey(deleteBtn.dataset.key);
|
||||
}, 380);
|
||||
} else {
|
||||
//@ts-ignore
|
||||
removeFromCartByKey(deleteBtn.dataset.key); //TODO: removeFromCartByKey doesnt exist
|
||||
removeFromCartByKey(deleteBtn.dataset.key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -717,7 +949,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const relatedItem = cart.find((item: { category: string; seatId: any; hall: any; time: any; title: any; }) => {
|
||||
const relatedItem = cart.find((item) => {
|
||||
const infoText = item.category === "movie"
|
||||
? `Sitz: ${item.seatId} (${item.hall})`
|
||||
: item.time;
|
||||
@@ -731,7 +963,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (action === "plus") {
|
||||
cart.push({ ...relatedItem, id: Date.now() + Math.random() });
|
||||
} else {
|
||||
const keyList = cart.map((item: { category: string; seatId: any; hall: any; time: any; title: any; }) => {
|
||||
const keyList = cart.map((item) => {
|
||||
const infoText = item.category === "movie"
|
||||
? `Sitz: ${item.seatId} (${item.hall})`
|
||||
: item.time;
|
||||
@@ -750,7 +982,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
|
||||
const bindSnacksActions = () => {
|
||||
ui.snacksView?.addEventListener("click", (event: any) => {
|
||||
ui.snacksView?.addEventListener("click", (event) => {
|
||||
const sizeChip = event.target.closest(".size-chip");
|
||||
if (!sizeChip) {
|
||||
return;
|
||||
@@ -794,7 +1026,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}, 800);
|
||||
});
|
||||
|
||||
document.querySelectorAll(".tab-btn").forEach((button: any) => {
|
||||
document.querySelectorAll(".tab-btn").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
document.querySelectorAll(".tab-btn").forEach((tab) => tab.classList.remove("active"));
|
||||
button.classList.add("active");
|
||||
@@ -831,9 +1063,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
// @ts-ignore
|
||||
window.removeFromCartByKey = function removeFromCartByKey(key: string) {
|
||||
cart = cart.filter((item: { category: string; seatId: any; hall: any; time: any; title: any; }) => {
|
||||
|
||||
window.removeFromCartByKey = function removeFromCartByKey(key) {
|
||||
cart = cart.filter((item) => {
|
||||
const infoText = item.category === "movie"
|
||||
? `Sitz: ${item.seatId} (${item.hall})`
|
||||
: item.time;
|
||||
@@ -858,8 +1090,3 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
updateCartBadge?.();
|
||||
renderCheckout?.();
|
||||
});
|
||||
|
||||
export function emptyCart() {
|
||||
cart = []
|
||||
return
|
||||
}
|
||||
6543
package-lock.json
generated
29
package.json
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "kino-astro",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^5.0.4",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"astro": "^6.2.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"vite": "^6.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.0"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 655 B |
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 878 KiB |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 362 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 233 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 307 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 910 KiB |
|
Before Width: | Height: | Size: 586 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 643 KiB |
|
Before Width: | Height: | Size: 481 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 530 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 524 KiB |
@@ -1,58 +0,0 @@
|
||||
<section id="about-view" class="hidden info-view">
|
||||
<div class="container info-view-shell">
|
||||
<div class="about-hero-block">
|
||||
<div class="about-hero-content">
|
||||
<h1>Über uns</h1>
|
||||
<p class="about-intro">
|
||||
EAGLE's IMAX vereint modernes Design, starke Technik und echtes Kinofeeling.
|
||||
Unser Anspruch: Jeder Besuch soll wie ein kleines Event wirken. Von entspannten Abenden
|
||||
bis zu großen Blockbuster-Premieren liefern wir Bild, Sound und Atmosphäre auf Top-Niveau.
|
||||
</p>
|
||||
<div class="about-pill-row">
|
||||
<span>4 Säle</span>
|
||||
<span>IMAX Experience</span>
|
||||
<span>D-BOX Motion Seats</span>
|
||||
<span>Premium Snacks</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about-hero-media" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<div class="about-stats-grid">
|
||||
<article>
|
||||
<h3>600+</h3>
|
||||
<p>Sitzplätze insgesamt</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>4K / Laser</h3>
|
||||
<p>Hochauflösende Projektion</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Dolby Atmos</h3>
|
||||
<p>Raumklang in ausgewählten Sälen</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>D-BOX</h3>
|
||||
<p>Bewegung synchron zum Film</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="about-cards">
|
||||
<article class="about-card about-card-halls">
|
||||
<h3>Säle</h3>
|
||||
<p>Vom klassischen Kinoraum bis zum IMAX-Erlebnis: Jeder Saal ist individuell abgestimmt auf Genre, Publikum und Stimmung.</p>
|
||||
<button type="button" class="story-more-btn" data-home-view-open="halls-view">Mehr erfahren</button>
|
||||
</article>
|
||||
<article class="about-card about-card-dbox">
|
||||
<h3>D-BOX Plätze</h3>
|
||||
<p>Synchronisierte Sitzbewegungen machen Action und Effekte physisch spürbar und verstärken die Immersion im Film.</p>
|
||||
<button type="button" class="story-more-btn" data-home-view-open="dbox-view">Mehr erfahren</button>
|
||||
</article>
|
||||
<article class="about-card about-card-tech">
|
||||
<h3>Technik</h3>
|
||||
<p>Leinwandgrößen, Projektoren, Soundsysteme und Kapazitäten in einem separaten Technikfenster zusammengefasst.</p>
|
||||
<button id="btn-open-tech-modal" type="button" class="story-more-btn" data-about-modal-open="about-tech-modal">Mehr erfahren</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,45 +0,0 @@
|
||||
<div id="account-view" class="hidden">
|
||||
<div class="account-login-box">
|
||||
<h2>Mein Konto</h2>
|
||||
|
||||
<input type="email" id="login-email" placeholder="E-Mail">
|
||||
<input type="password" id="login-password" placeholder="Passwort">
|
||||
<button id="btn-forgot-password" type="button">Passwort vergessen?</button>
|
||||
|
||||
<div id="login-error" class="hidden">
|
||||
Falsche E-Mail oder Passwort
|
||||
</div>
|
||||
|
||||
<button id="btn-login-account" type="button">Anmelden</button>
|
||||
<p id="account-register-hint">Noch kein Konto?</p>
|
||||
<button id="btn-open-register" type="button">Registrieren</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="register-modal" class="modal hidden">
|
||||
<div class="modal-content account-auth-modal">
|
||||
<button id="btn-close-register" class="modal-close-btn" type="button" aria-label="Registrierung schliessen">×</button>
|
||||
<h2>Registrieren</h2>
|
||||
<p class="auth-modal-subtitle">Erstelle dein Konto für schnellere Buchungen.</p>
|
||||
<input type="text" id="reg-firstname" placeholder="Vorname">
|
||||
<input type="text" id="reg-lastname" placeholder="Nachname">
|
||||
<input type="email" id="reg-email" placeholder="E-Mail">
|
||||
<input type="password" id="reg-password" placeholder="Passwort">
|
||||
<button id="btn-register-save" class="auth-submit-btn" type="button">Konto erstellen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="forgot-modal" class="modal hidden">
|
||||
<div class="modal-content account-auth-modal">
|
||||
<button id="btn-close-forgot" class="modal-close-btn" type="button" aria-label="Passwort-Dialog schliessen">×</button>
|
||||
<h2>Passwort vergessen</h2>
|
||||
<p class="auth-modal-subtitle">Gib deine E-Mail ein und wir senden dir einen Reset-Code.</p>
|
||||
|
||||
<input type="email" id="forgot-email" placeholder="E-Mail">
|
||||
<button id="btn-send-reset" class="auth-submit-btn" type="button">Code senden</button>
|
||||
|
||||
<div id="reset-message" class="hidden">
|
||||
Ein Code wurde simuliert versendet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,43 +0,0 @@
|
||||
<div id="booking-modal" class="modal hidden">
|
||||
<div class="modal-content modal-large">
|
||||
<div class="modal-header">
|
||||
<div class="header-text-container">
|
||||
<h2 id="modal-movie-title">Film Titel</h2>
|
||||
<div class="header-sub-info">
|
||||
<p id="modal-info-text">Saal • Zeit</p>
|
||||
<div id="tech-badges" class="tech-badges-container hidden">
|
||||
<img src="/img/Dolby.png" alt="Dolby" class="tech-badge">
|
||||
<img src="/img/dbox.jpg" alt="D-Box" class="tech-badge">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="close-btn">×</span>
|
||||
</div>
|
||||
|
||||
<div class="screen-container">
|
||||
<div class="screen"></div>
|
||||
<p class="screen-text">LEINWAND</p>
|
||||
</div>
|
||||
|
||||
<div class="booking-layout">
|
||||
<div class="seat-map-container">
|
||||
<div id="seat-grid" class="seat-grid-custom"></div>
|
||||
</div>
|
||||
<div id="booking-summary" class="summary-panel hidden">
|
||||
<h3>Deine Auswahl</h3>
|
||||
<div id="summary-items"></div>
|
||||
<div class="summary-total">
|
||||
<div class="divider"></div>
|
||||
<div class="total-row">
|
||||
<span>Gesamtbetrag:</span>
|
||||
<span id="total-price">0,00€</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="legend" id="dynamic-legend"></div>
|
||||
|
||||
<button id="btn-confirm-seats" class="btn-primary" style="margin-top:20px">Plätze bestätigen</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,37 +0,0 @@
|
||||
<section id="cart-view" class="cart-section hidden">
|
||||
<div class="container" style="padding: 120px 8% 50px 8%;">
|
||||
<h1 class="list-title">Dein Warenkorb</h1>
|
||||
|
||||
<div class="cart-container">
|
||||
<div class="cart-left">
|
||||
<div id="cart-items-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="cart-right">
|
||||
<div class="summary-box-black">
|
||||
<h2>Zusammenfassung</h2>
|
||||
<div class="summary-row-large">
|
||||
<span>Gesamtsumme:</span>
|
||||
<span id="cart-total-right">0,00€</span>
|
||||
</div>
|
||||
<p id="cart-vat-right" style="text-align:right; color:#86868b; font-size:0.8rem; margin-top:5px;">
|
||||
inkl. 19% MwSt: 0,00€
|
||||
</p>
|
||||
|
||||
<button id="btn-checkout-final" class="blue-button">Jetzt kostenpflichtig bestellen</button>
|
||||
|
||||
<div class="payment-methods" style="margin-top: 25px; text-align: center;">
|
||||
<p style="font-size: 0.75rem; color: #86868b; margin-bottom: 12px;">Sicher bezahlen mit</p>
|
||||
<div style="display: flex; justify-content: center; gap: 15px; opacity: 0.6;">
|
||||
<img src="/img/paypal.png" alt="PayPal" style="height: 20px;">
|
||||
<img src="/img/visa.png" alt="Visa" style="height: 20px;">
|
||||
<img src="/img/mastercard.png" alt="Mastercard" style="height: 20px;">
|
||||
<img src="/img/applepay.png" alt="Apple Pay" style="height: 20px;">
|
||||
<img src="/img/googlepay.png" alt="Google Pay" style="height: 20px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,52 +0,0 @@
|
||||
<section id="checkout-view" class="hidden" style="padding: 40px 20px;">
|
||||
<div class="checkout-container">
|
||||
<div class="progress-bar">
|
||||
<div class="step active" id="step-1-indicator">1</div>
|
||||
<div class="line" id="line-1"></div>
|
||||
<div class="step" id="step-2-indicator">2</div>
|
||||
<div class="line" id="line-2"></div>
|
||||
<div class="step" id="step-3-indicator">3</div>
|
||||
</div>
|
||||
|
||||
<div id="checkout-step-1" class="checkout-step">
|
||||
<h2 style="text-align: center; margin-bottom: 20px;">Zahlungsmethode wählen</h2>
|
||||
<div class="payment-grid">
|
||||
<div class="payment-method" data-method="Apple Pay">
|
||||
<img src="/img/applepay.png" alt="Apple Pay" style="height: 20px;">
|
||||
<span>Apple Pay</span>
|
||||
</div>
|
||||
<div class="payment-method" data-method="PayPal">
|
||||
<img src="/img/paypal.png" alt="PayPal" style="height: 20px;">
|
||||
<span>PayPal</span>
|
||||
</div>
|
||||
<div class="payment-method" data-method="Google Pay">
|
||||
<img src="/img/googlepay.png" alt="Google Pay" style="height: 20px;">
|
||||
<span>Google Pay</span>
|
||||
</div>
|
||||
<div class="payment-method" data-method="Visa">
|
||||
<img src="/img/visa.png" alt="Visa" style="height: 20px;">
|
||||
<span>Visa</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-next-step-2" class="hidden" style="margin-top: 25px; width: 100%; padding: 15px; background: #0071e3; color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer;">Weiter zur Übersicht</button>
|
||||
</div>
|
||||
|
||||
<div id="checkout-step-2" class="checkout-step hidden">
|
||||
<div style="position: relative;">
|
||||
<button id="btn-back-to-step1" style="position: absolute; top: -15px; left: 0; background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; opacity: 0.6; transition: 0.3s;">←</button>
|
||||
<h2 style="text-align: center; margin-bottom: 25px;">Persönliche Daten</h2>
|
||||
</div>
|
||||
<h2 style="text-align: center; margin-bottom: 20px;">Bestellübersicht</h2>
|
||||
<div id="checkout-summary-list" style="background: #222; padding: 15px; border-radius: 8px;"></div>
|
||||
<h3 id="checkout-total-display" style="text-align: right; margin-top: 15px;"></h3>
|
||||
<div id="checkout-vat-display" style="text-align: right; color: #86868b; font-size: 0.8rem;"></div>
|
||||
<button id="btn-pay-now" style="margin-top: 25px; width: 100%; padding: 15px; background: #4caf50; color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer;">Jetzt Bezahlen</button>
|
||||
</div>
|
||||
|
||||
<div id="checkout-step-3" class="checkout-step hidden">
|
||||
<h2 style="color: #4caf50; text-align: center;">Kauf erfolgreich!</h2>
|
||||
<div id="ticket-container" style="margin-top: 30px;"></div>
|
||||
<button id="btn-back-home" style="margin-top: 30px; width: 100%; padding: 15px; background: #333; color: white; border: none; border-radius: 8px; cursor: pointer;">Zurück zur Startseite</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,33 +0,0 @@
|
||||
<section id="collectors-view" class="hidden info-view">
|
||||
<div class="container info-view-shell">
|
||||
<button class="subpage-back-btn" data-go-home type="button">← Zur Startseite</button>
|
||||
<h1>Collectors Popcorn Specials</h1>
|
||||
<p class="info-view-subtitle">Filmbezogene Specials in einer links/rechts versetzten Darstellung.</p>
|
||||
<div class="collector-zigzag">
|
||||
<article class="collector-entry left">
|
||||
<div class="collector-film-logo">Filmlogo</div>
|
||||
<div class="collector-entry-content">
|
||||
<h4>Special Becher 01</h4>
|
||||
<p>Kurzbeschreibung des Artikels und Bezug zum Film.</p>
|
||||
<input type="text" placeholder="Bildpfad (z.B. img/becher1.jpg)">
|
||||
</div>
|
||||
</article>
|
||||
<article class="collector-entry right">
|
||||
<div class="collector-film-logo">Filmlogo</div>
|
||||
<div class="collector-entry-content">
|
||||
<h4>Special Eimer 02</h4>
|
||||
<p>Weitere Edition mit eigenem Motiv und Text.</p>
|
||||
<input type="text" placeholder="Bildpfad (z.B. img/eimer2.jpg)">
|
||||
</div>
|
||||
</article>
|
||||
<article class="collector-entry left">
|
||||
<div class="collector-film-logo">Filmlogo</div>
|
||||
<div class="collector-entry-content">
|
||||
<h4>Special Box 03</h4>
|
||||
<p>Noch ein Eintrag fuer limitierte Collectors.</p>
|
||||
<input type="text" placeholder="Bildpfad (z.B. img/collector3.jpg)">
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,31 +0,0 @@
|
||||
<section id="dbox-view" class="hidden info-view">
|
||||
<div class="container info-view-shell">
|
||||
<button class="subpage-back-btn" data-go-home type="button">← Zur Startseite</button>
|
||||
<h1>D-BOX & Technik</h1>
|
||||
<p class="info-view-subtitle">Inspiriert von Technikseiten moderner Kinos, aber in deinem Design.</p>
|
||||
<section class="dbox-modal-section">
|
||||
<h3>Wie D-BOX funktioniert</h3>
|
||||
<p>D-BOX Sitze reagieren synchron zum Film. Die Bewegungsintensität ist individuell steuerbar und kann bei Bedarf reduziert oder deaktiviert werden.</p>
|
||||
</section>
|
||||
<section class="dbox-modal-section">
|
||||
<h3>Technik im Saal</h3>
|
||||
<p>Ergänze hier deine Informationen zu Projektion, Soundsystem, Sitzkomfort und den Sälen mit D-BOX-Unterstützung.</p>
|
||||
<div class="dbox-image-grid">
|
||||
<div class="dbox-image-slot">Bildplatz Technik 1</div>
|
||||
<div class="dbox-image-slot">Bildplatz Technik 2</div>
|
||||
<div class="dbox-image-slot">Bildplatz Technik 3</div>
|
||||
<div class="dbox-image-slot">Bildplatz Technik 4</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="dbox-now-highlight modal-highlight">
|
||||
<h4>Filme in D-BOX</h4>
|
||||
<div class="dbox-mini-cards">
|
||||
<div>Zoomania 2</div>
|
||||
<div>Der Austronaut</div>
|
||||
<div>Spider Man</div>
|
||||
<div>Scream VII</div>
|
||||
<div>Gangster Gang 2</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,29 +0,0 @@
|
||||
<section id="halls-view" class="hidden info-view">
|
||||
<div class="container info-view-shell">
|
||||
<button class="subpage-back-btn" data-go-home type="button">← Zur Startseite</button>
|
||||
<h1>Unsere Säle</h1>
|
||||
<p class="info-view-subtitle">Pflegbare Infoseite zu allen Sälen inkl. Bild- und Textbereichen.</p>
|
||||
<div class="hall-modal-grid full-page-grid">
|
||||
<article class="hall-modal-item">
|
||||
<h4>Kino 1</h4>
|
||||
<textarea placeholder="Infos zu Kino 1 eintragen..."></textarea>
|
||||
<input type="text" placeholder="Bildpfad fuer den Hintergrund (z.B. img/saal1.jpg)">
|
||||
</article>
|
||||
<article class="hall-modal-item">
|
||||
<h4>Kino 2</h4>
|
||||
<textarea placeholder="Infos zu Kino 2 eintragen..."></textarea>
|
||||
<input type="text" placeholder="Bildpfad fuer den Hintergrund (z.B. img/saal2.jpg)">
|
||||
</article>
|
||||
<article class="hall-modal-item">
|
||||
<h4>Deluxe 1</h4>
|
||||
<textarea placeholder="Infos zu Deluxe 1 eintragen..."></textarea>
|
||||
<input type="text" placeholder="Bildpfad fuer den Hintergrund (z.B. img/deluxe.jpg)">
|
||||
</article>
|
||||
<article class="hall-modal-item">
|
||||
<h4>IMAX</h4>
|
||||
<textarea placeholder="Infos zu IMAX eintragen..."></textarea>
|
||||
<input type="text" placeholder="Bildpfad fuer den Hintergrund (z.B. img/imax.jpg)">
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,10 +0,0 @@
|
||||
<header class="hero">
|
||||
<div id="hero-slider" class="hero-slider"></div>
|
||||
<div class="hero-overlay">
|
||||
<span class="badge">Neu im Kino</span>
|
||||
<h1 id="hero-title">Jetzt im Kino</h1>
|
||||
<p id="hero-text">Erlebe die neuesten Highlights auf der großen Leinwand.</p>
|
||||
<button class="btn-primary" id="hero-booking-btn">Jetzt Tickets kaufen</button>
|
||||
<div id="hero-dots" class="hero-dots"></div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,58 +0,0 @@
|
||||
<section id="movies-grid-section" class="movie-section cinema-home">
|
||||
<div class="section-header reveal-on-scroll">
|
||||
<h2>Dein Kinoabend beginnt hier</h2>
|
||||
<p>Premieren, Lieblingsfilme und flexible Spielzeiten in allen Sälen.</p>
|
||||
</div>
|
||||
|
||||
<div class="home-poster-band reveal-on-scroll">
|
||||
<div class="home-band-header">
|
||||
<h3>Jetzt läuft</h3>
|
||||
<span>Heute im Fokus</span>
|
||||
</div>
|
||||
<div id="now-running-row" class="now-running-row"></div>
|
||||
</div>
|
||||
|
||||
<div class="home-inline-showcase reveal-on-scroll">
|
||||
<article class="inline-section inline-halls">
|
||||
<div class="inline-media"></div>
|
||||
<div class="inline-content">
|
||||
<h3>Unsere Säle im Überblick</h3>
|
||||
<p>Jeder Saal hat ein eigenes Profil: von klassischem Kinofeeling bis High-End-Erlebnis mit Premiumtechnik und mehr Komfort.</p>
|
||||
<div class="inline-tags">
|
||||
<span>IMAX</span>
|
||||
<span>Deluxe 1</span>
|
||||
<span>Kino 1</span>
|
||||
<span>Kino 2</span>
|
||||
</div>
|
||||
<button type="button" class="story-more-btn" data-home-view-open="halls-view">Mehr erfahren</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="inline-section inline-dbox">
|
||||
<div class="inline-media"></div>
|
||||
<div class="inline-content">
|
||||
<h3>D-BOX & Technik</h3>
|
||||
<p>Spüre Bewegungen synchron zum Film und kombiniere das Erlebnis mit modernem Raumklang und Premium-Bestuhlung.</p>
|
||||
<div class="dbox-now-highlight">
|
||||
<h4>Aktuell in D-BOX</h4>
|
||||
<div class="dbox-mini-cards">
|
||||
<div>Zoomania 2</div>
|
||||
<div>Shelter</div>
|
||||
<div>Hoppers</div>
|
||||
<div>Spider Man</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="story-more-btn" data-home-view-open="dbox-view">Mehr erfahren</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="inline-section inline-collectors">
|
||||
<div class="inline-media"></div>
|
||||
<div class="inline-content">
|
||||
<h3>Collectors Popcorn Specials</h3>
|
||||
<p>Präsentiere Sonderbecher und Eimer filmbezogen mit Bild, Logo und kurzem Text in einer lebendigen Timeline.</p>
|
||||
<button type="button" class="story-more-btn" data-home-view-open="collectors-view">Mehr erfahren</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
import { getTopMovies } from "../scripts/fetchMovies";
|
||||
import type { MovieCatalog } from "../scripts/interfaces";
|
||||
|
||||
const movieProgram = await getTopMovies();
|
||||
---
|
||||
|
||||
<div>
|
||||
{
|
||||
movieProgram.map((movie: MovieCatalog, programIndex: any) => {
|
||||
{<div>{movie.genre}</div>}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
import { getTopMovies } from "../scripts/fetchMovies"
|
||||
import Movie from "./Movie.astro"
|
||||
console.log(await getTopMovies())
|
||||
---
|
||||
|
||||
<section id="movie-list-view" class="hidden">
|
||||
<div class="container movie-list-shell">
|
||||
<h1 class="list-title">Aktuelle Filme & Spielzeiten</h1>
|
||||
<p class="list-subtitle">Alle Filme mit 7 Tagen Spielplan. Erste Vorstellung täglich ab 13:00 Uhr.</p>
|
||||
|
||||
<!-- Movie List. -->
|
||||
<!-- <div id="movie-program-list" class="movie-program-list"></div> -->
|
||||
<Movie />
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,22 +0,0 @@
|
||||
<nav class="navbar">
|
||||
<div class="logo" id="logo-home" style="cursor:pointer">EAGLE's IMAX</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#" id="link-filme">Aktuelle Filme</a></li>
|
||||
<li><a href="#" id="link-snacks">Snacks & Getränke</a></li>
|
||||
<li><a href="#" id="link-about">Über uns</a></li>
|
||||
<li><a href="#" id="link-account">Mein Konto</a></li>
|
||||
<li>
|
||||
<a href="#" id="link-cart" style="position: relative; display: flex; align-items: center; gap: 5px;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>
|
||||
Warenkorb
|
||||
<span id="cart-badge" class="badge">0</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="theme-toggle-item">
|
||||
<button id="theme-toggle" class="theme-toggle-btn" type="button" aria-label="Theme wechseln">
|
||||
<span class="theme-icon theme-icon-sun">☀</span>
|
||||
<span class="theme-icon theme-icon-moon">☾</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||