diff --git a/app.css b/app.css index 14fcceb..bc52bfe 100644 --- a/app.css +++ b/app.css @@ -3028,6 +3028,154 @@ footer a:hover { color: var(--accent-2); } .app-store-chip { background: #2a2a2e; border-color: rgba(255,255,255,.08); } } + +/* ── Per-type bespoke: Ticket — tear-off stub ── */ + +.card.ticket-stub { background: #fff; border-color: rgba(0,0,0,.12); overflow: visible; } +.card.ticket-stub .hd.hidden { display: none; } +.card.ticket-stub .bd { padding: 0; background: #fff; } +.card.ticket-stub .bd::before { display: none; } + +.ticket-layout { + display: flex; + border-radius: .75rem; + overflow: hidden; + background: #fff; + position: relative; +} + +/* Main body (left side) */ +.ticket-main { + flex: 1; + padding: 1.8rem 1.6rem; + min-width: 0; +} +.ticket-event { + font-size: 1.5rem; + font-weight: 800; + line-height: 1.2; + margin: 0 0 .6rem; + color: #111; +} +.ticket-desc { + font-size: .85rem; + line-height: 1.55; + color: #555; + margin: 0 0 1rem; +} + +.ticket-meta-grid { + display: flex; + gap: 1.4rem; + flex-wrap: wrap; +} +.ticket-meta-cell { + display: flex; + flex-direction: column; + gap: .15rem; +} +.ticket-meta-cell small { + font-size: .7rem; + text-transform: uppercase; + letter-spacing: .06em; + color: #999; +} +.ticket-meta-cell span { + font-size: .9rem; + font-weight: 600; + color: #222; +} + +/* Perforation divider */ +.ticket-perf { + width: 0; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border-left: 2.5px dashed rgba(0,0,0,.15); + margin: 1.2rem 0; +} +.ticket-notch { + position: absolute; + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--app-bg, #f0f0f0); + left: -10px; +} +.ticket-notch:first-child { top: -10px; } +.ticket-notch:last-child { bottom: -10px; } + +/* Stub side (right) */ +.ticket-side { + width: 150px; + min-width: 130px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.4rem 1rem; + text-align: center; + position: relative; + gap: .6rem; +} +.ticket-number { + font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace; + font-size: .75rem; + font-weight: 700; + letter-spacing: .04em; + color: #888; + transform: rotate(90deg); + white-space: nowrap; +} +.ticket-price-badge { + background: #111; + color: #fff; + padding: .45rem .9rem; + border-radius: .4rem; + font-weight: 700; + line-height: 1; +} +.ticket-price-val { font-size: 1.2rem; } +.ticket-price-cur { font-size: .65rem; opacity: .7; } +.ticket-admit { + font-size: .6rem; + text-transform: uppercase; + letter-spacing: .15em; + color: #bbb; + border-top: 1px solid #e0e0e0; + padding-top: .5rem; + width: 100%; +} +.ticket-watermark { + position: absolute; + bottom: .4rem; + right: .5rem; + font-size: 2.2rem; + opacity: .08; + line-height: 1; +} + +/* Dark mode ticket */ +@media (prefers-color-scheme: dark) { + .card.ticket-stub { background: #1c1c1e; border-color: rgba(255,255,255,.08); } + .card.ticket-stub .bd { background: #1c1c1e; } + .ticket-layout { background: #1c1c1e; } + .ticket-event { color: #f5f5f5; } + .ticket-desc { color: #999; } + .ticket-meta-cell small { color: #666; } + .ticket-meta-cell span { color: #ddd; } + .ticket-perf { border-left-color: rgba(255,255,255,.12); } + .ticket-notch { background: #0d0d0d; } + .ticket-number { color: #666; } + .ticket-price-badge { background: #f5f5f5; color: #111; } + .ticket-admit { color: #555; border-top-color: #333; } + .ticket-watermark { opacity: .05; } +} + + /* ────────────────────────────────────────────── Responsive / Mobile — small-screen polish. Stacks label/value grid, scales header, @@ -3207,4 +3355,15 @@ footer a:hover { color: var(--accent-2); } .faq-title { font-size: 1.15rem; padding-left: 1.2rem; padding-right: 1.2rem; } .faq-desc { padding-left: 1.2rem; padding-right: 1.2rem; } .faq-accordion { padding-left: 1.2rem; padding-right: 1.2rem; } + /* Ticket mobile — stack vertically */ + .ticket-layout { flex-direction: column; } + .ticket-main { padding: 1.2rem; } + .ticket-event { font-size: 1.2rem; } + .ticket-perf { border-left: none; border-top: 2.5px dashed rgba(0,0,0,.15); width: auto; height: 0; margin: 0 1.2rem; flex-direction: row; } + .ticket-notch { top: -10px; left: auto; position: absolute; } + .ticket-notch:first-child { left: -10px; top: -10px; } + .ticket-notch:last-child { right: -10px; left: auto; bottom: -10px; } + .ticket-side { width: auto; padding: 1rem 1.2rem; flex-direction: row; justify-content: space-around; } + .ticket-number { transform: none; } + .ticket-admit { border-top: none; border-left: 1px solid #e0e0e0; padding-top: 0; padding-left: .5rem; width: auto; } } diff --git a/app.js b/app.js index 60c30e7..845a93b 100644 --- a/app.js +++ b/app.js @@ -2023,6 +2023,104 @@ handled.add("mainEntity"); } + // ── TICKET (tear-off stub) ── + if (cfg.type === "Ticket") { + const card = document.querySelector(".card"); + if (card) card.classList.add("ticket-stub"); + + // Hide normal header — we build our own + const hd = document.querySelector(".hd"); + if (hd) hd.classList.add("hidden"); + + // Remove generic hero if present + const heroMount = document.getElementById("hero-mount"); + if (heroMount) heroMount.querySelector(".hero")?.remove(); + + // ── Stub layout ── + const stub = document.createElement("div"); stub.className = "ticket-layout"; + + // Left: main ticket body + const main = document.createElement("div"); main.className = "ticket-main"; + + // Event name as big title + const eventName = data["name"] || ""; + if (eventName) { + const h1 = document.createElement("h1"); h1.className = "ticket-event"; + h1.textContent = eventName; + main.appendChild(h1); + } + + // Description as subtitle + const desc = data["description"]; + if (desc) { + const p = document.createElement("p"); p.className = "ticket-desc"; + p.textContent = desc; + main.appendChild(p); + } + + // Meta grid: attendee + issued date + const metaGrid = document.createElement("div"); metaGrid.className = "ticket-meta-grid"; + const underName = data["underName"]; + if (underName) { + const cell = document.createElement("div"); cell.className = "ticket-meta-cell"; + cell.innerHTML = "Attendee" + underName + ""; + metaGrid.appendChild(cell); + } + const dateIssued = data["dateIssued"]; + if (dateIssued) { + const cell = document.createElement("div"); cell.className = "ticket-meta-cell"; + const d = new Date(dateIssued); + const formatted = isNaN(d.getTime()) ? dateIssued : d.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }); + cell.innerHTML = "Issued" + formatted + ""; + metaGrid.appendChild(cell); + } + if (metaGrid.children.length) main.appendChild(metaGrid); + + // ── Perforation divider ── + const perf = document.createElement("div"); perf.className = "ticket-perf"; + const notchL = document.createElement("span"); notchL.className = "ticket-notch"; + const notchR = document.createElement("span"); notchR.className = "ticket-notch"; + perf.append(notchL, notchR); + + // ── Right: stub side ── + const side = document.createElement("div"); side.className = "ticket-side"; + + // Ticket number in monospace + const ticketNum = data["ticketNumber"]; + if (ticketNum) { + const numEl = document.createElement("div"); numEl.className = "ticket-number"; + numEl.textContent = ticketNum; + side.appendChild(numEl); + } + + // Price badge + const totalPrice = data["totalPrice"]; + const priceCurrency = data["priceCurrency"]; + if (totalPrice) { + const priceEl = document.createElement("div"); priceEl.className = "ticket-price-badge"; + const currencySymbol = priceCurrency === "CZK" ? "Kč" : (priceCurrency || ""); + priceEl.innerHTML = "" + totalPrice + " " + currencySymbol + ""; + side.appendChild(priceEl); + } + + // ADMIT ONE label + const admit = document.createElement("div"); admit.className = "ticket-admit"; + admit.textContent = "ADMIT ONE"; + side.appendChild(admit); + + // Icon watermark + const watermark = document.createElement("div"); watermark.className = "ticket-watermark"; + watermark.textContent = cfg.icon || "🎟️"; + side.appendChild(watermark); + + stub.append(main, perf, side); + view.appendChild(stub); + + handled.add("name"); handled.add("image"); handled.add("description"); + handled.add("ticketNumber"); handled.add("underName"); handled.add("totalPrice"); + handled.add("priceCurrency"); handled.add("dateIssued"); + } + return handled; }