Files
Hellas-Wawi/index.html

580 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Hellas Shop</title>
<style>
:root {
--bg: #0a2036;
--card: #0f2236;
--muted: #9fb1c8;
--text: #f1f4f8;
--line: rgba(255,255,255,.12);
--accent: #f3d52a;
--teal: #2a8a8a;
--ok: #7bd58d;
--bad: #ff6b7d;
--warn: #f0c04a;
--shadow: 0 12px 28px rgba(0,0,0,.28);
--radius: 14px;
--surface: #0f2236;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Liberation Sans", sans-serif;
background:
radial-gradient(900px 520px at 50% -10%, rgba(255,255,255,.10), transparent 60%),
radial-gradient(900px 600px at 50% 20%, rgba(16,44,70,.85), transparent 65%),
radial-gradient(1200px 700px at 50% 80%, rgba(7,26,44,.95), transparent 70%),
linear-gradient(180deg, #0a2036 0%, #0b243b 45%, #092135 100%);
color: var(--text);
line-height: 1.35;
}
header {
position: sticky;
top: 0;
z-index: 10;
background: rgba(9,24,40,.9);
border-bottom: 1px solid var(--line);
box-shadow: 0 8px 22px rgba(0,0,0,.35);
}
header::before {
content: "";
display: block;
height: 3px;
background: linear-gradient(90deg, var(--teal), rgba(42,138,138,.0));
}
.wrap {
max-width: 1100px;
margin: 0 auto;
padding: 18px 16px;
}
.top {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand img {
height: 42px;
width: auto;
display: block;
filter: drop-shadow(0 2px 6px rgba(0,0,0,.4));
}
h1 {
font-family: "Arial Narrow", "Helvetica Neue Condensed", Impact, "Franklin Gothic Medium", "Arial Black", sans-serif;
font-size: 24px;
margin: 0;
letter-spacing: 1px;
text-transform: uppercase;
}
.meta { color: var(--muted); font-size: 12px; letter-spacing: .3px; }
.controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.search {
min-width: 240px; max-width: 460px; flex: 1;
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-radius: 999px;
background: rgba(255,255,255,.06);
border: 1px solid var(--line);
box-shadow: 0 1px 0 rgba(255,255,255,.06) inset;
}
.search input {
width: 100%; border: 0; outline: 0; background: transparent;
color: var(--text); font-size: 14px;
}
.pill {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 12px; border-radius: 999px;
background: rgba(255,255,255,.06);
border: 1px solid var(--line);
user-select: none; cursor: pointer;
font-size: 13px; color: var(--text);
box-shadow: 0 2px 0 rgba(0,0,0,.18);
}
.pill input { accent-color: var(--accent); }
main .wrap { padding-top: 14px; padding-bottom: 28px; }
.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.card-tile {
border: 1px solid rgba(255,255,255,.08);
border-radius: 16px;
background: rgba(12,31,51,.92);
box-shadow: 0 10px 22px rgba(0,0,0,.28);
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform .12s ease, box-shadow .12s ease;
}
.card-tile:hover { transform: translateY(-1px); box-shadow: 0 14px 26px rgba(0,0,0,.32); }
.card-media {
width: 100%;
aspect-ratio: 1 / 1;
background: rgba(0,0,0,.2);
display: flex;
align-items: center;
justify-content: center;
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: .6px;
}
.card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-body { padding: 12px 12px 10px; display: grid; gap: 6px; }
.card-title {
font-family: "Arial Narrow", "Helvetica Neue Condensed", Impact, "Franklin Gothic Medium", "Arial Black", sans-serif;
font-size: 16px; font-weight: 600; letter-spacing: .6px;
}
.card-price { color: var(--accent); font-weight: 800; font-size: 14px; }
.card-sub { color: var(--muted); font-size: 12px; }
.card-actions { padding: 0 12px 12px; display: flex; justify-content: flex-end; }
.detail-btn { width: 100%; }
.size-list { display: grid; gap: 8px; padding: 10px 2px 2px; }
.size-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; padding: 8px 10px; border-radius: 10px; }
.size-row:nth-child(odd) { background: rgba(255,255,255,.04); }
.size-meta { font-size: 13px; color: var(--text); }
.size-meta strong { font-weight: 700; letter-spacing: .2px; }
.art { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.art .name {
font-family: "Arial Narrow", "Helvetica Neue Condensed", Impact, "Franklin Gothic Medium", "Arial Black", sans-serif;
font-size: 18px; font-weight: 600; letter-spacing: .6px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.art .sub { font-size: 12px; color: var(--muted); }
.badges { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.badge {
font-size: 12px; padding: 6px 10px; border-radius: 10px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.05);
color: var(--text);
white-space: nowrap;
font-weight: 700;
letter-spacing: .2px;
}
.thumb {
width: 64px;
height: 64px;
border-radius: 12px;
background:
linear-gradient(135deg, rgba(255,255,255,.08), rgba(0,0,0,.2)),
radial-gradient(circle at 30% 30%, rgba(255,255,255,.25), transparent 40%);
border: 1px solid rgba(255,255,255,.12);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .6px;
flex: 0 0 auto;
}
.price {
margin-top: 4px;
font-size: 12px;
color: var(--accent);
font-weight: 700;
letter-spacing: .2px;
}
.content {
border-top: 1px solid rgba(255,255,255,.08);
padding: 10px 12px 12px 12px;
background: rgba(6,16,28,.3);
}
.table-wrap { overflow-x: auto; }
.table-wrap table { min-width: 520px; }
table { width: 100%; border-collapse: collapse; font-size: 13px; overflow: hidden; }
th, td { padding: 10px 8px; border-bottom: 1px solid rgba(255,255,255,.06); text-align: right; vertical-align: middle; font-variant-numeric: tabular-nums; }
th:first-child, td:first-child { text-align: left; }
th { color: var(--muted); font-weight: 600; }
tr:last-child td { border-bottom: 0; }
thead th { background: rgba(0,0,0,.12); }
tbody tr:nth-child(odd) { background: rgba(255,255,255,.02); }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.footer {
margin-top: 14px; color: var(--muted); font-size: 12px;
display: flex; gap: 10px; flex-wrap: wrap; justify-content: space-between;
border-top: 1px dashed rgba(255,255,255,.12);
padding-top: 12px;
}
.empty, .error {
border: 1px dashed rgba(255,255,255,.2);
border-radius: var(--radius);
padding: 16px;
color: var(--muted);
text-align: center;
}
.error { color: #ffd6de; border-color: rgba(255,107,125,.4); }
.order-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border-radius: 10px;
border: 1px solid var(--line);
background: rgba(255,255,255,.06);
color: var(--text);
cursor: pointer;
font-size: 12px;
}
.modal {
position: fixed;
inset: 0;
background: rgba(5,12,20,.6);
display: none;
align-items: flex-start;
justify-content: center;
padding: 84px 16px 24px;
z-index: 50;
opacity: 0;
transition: opacity .18s ease;
}
.modal.open { display: flex; opacity: 1; }
.modal-card {
width: 100%;
max-width: 520px;
border: 1px solid var(--line);
border-radius: 14px;
background: linear-gradient(180deg, rgba(18,45,70,.92), rgba(12,31,51,.98));
box-shadow: 0 16px 28px rgba(0,0,0,.35);
padding: 14px;
max-height: calc(100vh - 80px);
overflow: auto;
transform: scale(.98);
transition: transform .18s ease;
}
.modal.open .modal-card { transform: scale(1); }
.modal-card h3 { margin: 0 0 10px; }
.detail-header { display: grid; gap: 8px; }
.detail-title { font-size: 20px; font-weight: 700; letter-spacing: .4px; }
.detail-price { color: var(--accent); font-weight: 800; }
.detail-media {
width: 100%;
aspect-ratio: 4 / 3;
border-radius: 12px;
overflow: hidden;
background: rgba(0,0,0,.2);
}
.detail-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.modal-card { position: relative; }
.close-x {
position: absolute;
top: 8px;
right: 8px;
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid rgba(0,0,0,.35);
background: rgba(8,16,28,.85);
color: var(--text);
font-size: 16px;
line-height: 1;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0,0,0,.35);
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.form-grid label { display: grid; gap: 6px; font-size: 12px; color: var(--muted); }
.form-grid input, .form-grid textarea {
background: rgba(255,255,255,.06);
border: 1px solid var(--line);
color: var(--text);
padding: 8px 10px;
border-radius: 10px;
font-size: 13px;
}
.form-actions { margin-top: 12px; display: flex; gap: 10px; justify-content: flex-end; }
.btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 12px; border-radius: 999px;
border: 1px solid var(--line);
background: rgba(255,255,255,.06);
color: var(--text); cursor: pointer;
}
.btn.accent { background: var(--accent); color: #071320; border-color: transparent; font-weight: 700; }
.img-modal .modal-card { max-width: 720px; padding: 10px; }
.img-modal img { width: 100%; height: auto; border-radius: 12px; display: block; }
.order-modal { z-index: 60; }
@media (max-width: 700px) {
.top { align-items: flex-start; }
.brand { width: 100%; }
.controls { width: 100%; }
.search { min-width: 100%; }
.card-media { aspect-ratio: 4 / 3; }
.order-btn { width: 100%; justify-content: center; }
}
@media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<header>
<div class="wrap">
<div class="top">
<div class="brand">
<img src="logo.png" alt="Hellas 1899 Logo" />
<div>
<h1>Shop</h1>
<div class="meta" id="meta">Stand: </div>
</div>
</div>
<div class="controls">
<div class="search" role="search">
<span aria-hidden="true">🔎</span>
<input id="q" type="search" placeholder="Artikel suchen… (z.B. Badehose, Hoodie, Kappe)" />
</div>
</div>
</div>
</div>
</header>
<main>
<div class="wrap">
<div id="grid" class="grid" aria-live="polite"></div>
<div class="footer">
<div>Tip: Auf einen Artikel klicken, um die GrößenTabelle zu öffnen.</div>
</div>
</div>
</main>
<div id="orderModal" class="modal order-modal" role="dialog" aria-modal="true">
<div class="modal-card">
<button class="close-x" id="orderCloseX" aria-label="Schließen">×</button>
<h3>Bestellung</h3>
<form id="orderForm">
<div class="form-grid">
<label>Artikel
<input id="fArtikel" name="artikel" readonly />
</label>
<label>Größe
<input id="fGroesse" name="groesse" readonly />
</label>
<label>Menge
<input id="fMenge" name="menge" type="number" min="1" value="1" required />
</label>
<label>Name
<input id="fName" name="name" required />
</label>
<label>Handy
<input id="fHandy" name="handy" required />
</label>
<label>Mannschaft
<input id="fMannschaft" name="mannschaft" placeholder="z. B. U12" required />
</label>
<label style="grid-column: 1 / -1;">Notiz
<textarea id="fNotiz" name="notiz" rows="3"></textarea>
</label>
</div>
<div class="form-actions">
<button type="button" class="btn" id="orderCancel">Abbrechen</button>
<button type="submit" class="btn accent">Bestellen</button>
</div>
</form>
</div>
</div>
<div id="imgModal" class="modal img-modal" role="dialog" aria-modal="true">
<div class="modal-card">
<img id="imgPreview" src="" alt="Artikelbild" />
</div>
</div>
<div id="detailModal" class="modal" role="dialog" aria-modal="true">
<div class="modal-card">
<button class="close-x" id="detailCloseX" aria-label="Schließen">×</button>
<div class="detail-header">
<div id="detailMedia" class="detail-media"></div>
<div class="detail-title" id="detailTitle"></div>
<div class="detail-price" id="detailPrice"></div>
<div class="card-sub" id="detailSizes"></div>
</div>
<div class="size-list" id="detailSizeList"></div>
<div class="form-actions">
<button type="button" class="btn" id="detailClose">Schließen</button>
</div>
</div>
</div>
<script>
// Proxy der WaWiApp (kein APIKey im Browser nötig).
const API_URL = "/wawi/proxy/bestand";
let DATA = [];
const DATA_MAP = new Map();
function fmt(v) {
if (v === null || v === undefined || Number.isNaN(v)) return "";
return String(v);
}
function badge(label, value, cls="") {
if (value === null || value === undefined) return "";
return `<span class="badge ${cls}">${label}: <strong>${fmt(value)}</strong></span>`;
}
function render(items) {
const grid = document.getElementById("grid");
if (!items.length) {
grid.innerHTML = `<div class="empty">Keine Treffer. (Suchbegriff anpassen)</div>`;
return;
}
grid.innerHTML = items.map((item, idx) => {
const t = item.totals || {};
const diff = t.abweichung ?? 0;
const fb = t.fehlbestand ?? null;
let statusCls = "ok";
if ((diff ?? 0) !== 0) statusCls = "bad";
else if ((fb ?? 0) !== 0) statusCls = "warn";
const statusText = ((diff ?? 0) === 0 && (fb ?? 0) === 0) ? "OK" : "Prüfen";
const price = Number(item.preis) || 0;
const priceText = price > 0 ? `${price.toFixed(2)}` : "Preis auf Anfrage";
const img = (item.bild_url || "").trim();
return `
<div class="card-tile">
<div class="card-media">
${img ? `<button class="thumb-btn" data-img="${img}" aria-label="Bild vergrößern" title="Bild vergrößern" style="width:100%;height:100%;background-image:url('${img}');background-size:cover;background-position:center;border:0;cursor:pointer;"></button>` : "Bild"}
</div>
<div class="card-body">
<div class="card-title">${item.artikel}</div>
<div class="card-price">${priceText}</div>
<div class="card-sub">${item.rows.length} Größen</div>
</div>
<div class="card-actions">
<button class="order-btn detail-btn" data-artikel="${item.artikel}" data-preis="${priceText}" data-img="${img}">Bestellen / Details</button>
</div>
</div>
`;
}).join("");
}
function applyFilters() {
const q = (document.getElementById("q").value || "").trim().toLowerCase();
let items = DATA.slice();
if (q) {
items = items.filter(it => (it.artikel || "").toLowerCase().includes(q));
}
render(items);
}
async function loadData() {
const res = await fetch(API_URL);
if (!res.ok) {
const grid = document.getElementById("grid");
grid.innerHTML = `<div class="error">API Fehler (${res.status}). Bitte APIKey prüfen.</div>`;
return;
}
DATA = await res.json();
DATA_MAP.clear();
DATA.forEach(item => DATA_MAP.set(item.artikel, item));
document.getElementById("meta").textContent = `Stand: ${new Date().toLocaleString("de-DE")}`;
applyFilters();
}
document.getElementById("q").addEventListener("input", applyFilters);
const modal = document.getElementById("orderModal");
const form = document.getElementById("orderForm");
const setField = (id, v) => document.getElementById(id).value = v || "";
const ORDER_KEY = "";
document.addEventListener("click", (e) => {
const imgBtn = e.target.closest(".thumb-btn");
if (imgBtn) {
document.getElementById("imgPreview").src = imgBtn.dataset.img;
document.getElementById("imgModal").classList.add("open");
return;
}
const detailBtn = e.target.closest(".detail-btn");
if (detailBtn) {
const artikel = detailBtn.dataset.artikel;
const item = DATA_MAP.get(artikel);
if (!item) return;
const img = (detailBtn.dataset.img || "").trim();
const media = document.getElementById("detailMedia");
media.innerHTML = img ? `<img src="${img}" alt="${artikel}">` : "";
document.getElementById("detailTitle").textContent = artikel;
document.getElementById("detailPrice").textContent = detailBtn.dataset.preis || "Preis auf Anfrage";
document.getElementById("detailSizes").textContent = `${item.rows.length} Größen`;
const list = item.rows.map(r => `
<div class="size-row">
<div class="size-meta"><strong>${fmt(r.groesse)}</strong> · Bestand: ${fmt(r.gezaehlt)}</div>
${(Number(r.gezaehlt) || 0) > 0 ? `<button class="order-btn" data-artikel="${item.artikel}" data-groesse="${fmt(r.groesse)}">Bestellen</button>` : ""}
</div>
`).join("");
document.getElementById("detailSizeList").innerHTML = list;
document.getElementById("detailModal").classList.add("open");
return;
}
const btn = e.target.closest(".order-btn");
if (!btn) return;
setField("fArtikel", btn.dataset.artikel);
setField("fGroesse", btn.dataset.groesse);
setField("fMenge", 1);
modal.classList.add("open");
});
document.getElementById("orderCancel").addEventListener("click", () => {
modal.classList.remove("open");
if (document.getElementById("detailModal").classList.contains("open")) {
document.getElementById("detailModal").querySelector(".size-row")?.scrollIntoView({ block: "nearest" });
}
});
document.getElementById("orderCloseX").addEventListener("click", () => {
modal.classList.remove("open");
});
modal.addEventListener("click", (e) => {
if (e.target === modal) modal.classList.remove("open");
});
document.getElementById("imgModal").addEventListener("click", (e) => {
if (e.target.id === "imgModal") e.currentTarget.classList.remove("open");
});
document.getElementById("detailClose").addEventListener("click", () => {
document.getElementById("detailModal").classList.remove("open");
});
document.getElementById("detailCloseX").addEventListener("click", () => {
document.getElementById("detailModal").classList.remove("open");
});
document.getElementById("detailModal").addEventListener("click", (e) => {
if (e.target.id === "detailModal") e.currentTarget.classList.remove("open");
});
form.addEventListener("submit", async (e) => {
e.preventDefault();
const payload = Object.fromEntries(new FormData(form).entries());
const key = ORDER_KEY || "";
const headers = { "Content-Type": "application/json" };
if (key) headers["X-Order-Key"] = key;
const res = await fetch("/wawi/order", {
method: "POST",
headers,
body: JSON.stringify(payload)
});
if (res.ok) {
alert("Bestellung gesendet.");
modal.classList.remove("open");
document.getElementById("detailModal").classList.remove("open");
form.reset();
setField("fMenge", 1);
} else {
alert("Fehler beim Senden der Bestellung.");
}
});
loadData();
</script>
</body>
</html>