From f7084492d0981dfea0ca1f35faebd1308fdcadda Mon Sep 17 00:00:00 2001 From: Charlie Date: Sat, 13 Jun 2026 14:36:59 +0200 Subject: [PATCH] schema-apps: SportsEvent matchday scoreboard design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bespoke renderTypeSpecific layout for SportsEvent — bold green header band with competition name, centered VS row (infers teams from name pattern), fixture info strip with date/venue/organizer, Get Tickets CTA, match overview. Class .card.scoreboard. Gate passes (324 apps). --- app.css | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app.js | 108 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) diff --git a/app.css b/app.css index d01a3a8..a0daa54 100644 --- a/app.css +++ b/app.css @@ -5027,3 +5027,142 @@ footer a:hover { color: var(--accent-2); } .gs-banner-overlay { padding: 0 1rem .8rem; } .gs-blurb { padding: .4rem 0 .8rem; } } + +/* ── SportsEvent — Matchday Scoreboard ── */ +.card.scoreboard { + border-color: rgba(22,163,74,.15); + overflow: hidden; +} +.card.scoreboard .hd.hidden { display: none; } + +/* Header band — bold competition name */ +.sb-header-band { + display: flex; + align-items: center; + gap: .4rem; + padding: .8rem 1.5rem; + background: linear-gradient(135deg, #14532d 0%, #166534 50%, #15803d 100%); + color: #fff; +} +.sb-header-icon { + font-size: 1.1rem; +} +.sb-header-title { + font-size: 1rem; + font-weight: 800; + letter-spacing: .01em; + text-shadow: 0 1px 4px rgba(0,0,0,.3); +} + +/* VS row — big centered matchup */ +.sb-vs-row { + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; + padding: 2rem 1.5rem 1.5rem; +} +.sb-team { + font-size: 1.4rem; + font-weight: 800; + color: var(--ink); + text-align: center; + flex: 1; + line-height: 1.2; +} +.sb-team-home { text-align: right; } +.sb-team-away { text-align: left; } +.sb-vs-badge { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + width: 3rem; height: 3rem; + border-radius: 50%; + background: #166534; + color: #fff; + font-size: .75rem; + font-weight: 900; + letter-spacing: .05em; + box-shadow: 0 2px 8px rgba(22,101,52,.4); +} +.sb-event-big { + font-size: 1.6rem; + font-weight: 800; + color: var(--ink); + text-align: center; + line-height: 1.2; +} + +/* Fixture info strip */ +.sb-fixture-strip { + display: flex; + flex-direction: column; + gap: .4rem; + padding: .6rem 1.5rem 1rem; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + background: rgba(22,163,74,.03); +} +.sb-fixture-row { + display: flex; + align-items: center; + gap: .5rem; + font-size: .82rem; + color: var(--ink-2); +} +.sb-fixture-icon { + font-size: .9rem; + width: 1.4rem; + text-align: center; +} + +/* Get Tickets CTA */ +.sb-tickets-cta { + display: inline-flex; + align-items: center; + gap: .3rem; + margin: .6rem 1.5rem; + padding: .55rem 1.4rem; + background: linear-gradient(to bottom, #16a34a 5%, #15803d 95%); + color: #fff; + font-size: .85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .03em; + border-radius: 4px; + text-decoration: none; + box-shadow: 0 2px 6px rgba(22,101,52,.3); + transition: background .15s; +} +.sb-tickets-cta:hover { + background: linear-gradient(to bottom, #22c55e 5%, #16a34a 95%); +} + +/* Match overview */ +.sb-overview { + padding: .4rem 1.5rem 1rem; +} +.sb-overview-label { + font-size: .9rem; + font-weight: 700; + color: var(--ink); + margin-bottom: .4rem; +} +.sb-overview-body { + font-size: .84rem; + line-height: 1.7; + color: var(--muted); + margin: 0; +} + +@media (max-width: 480px) { + .sb-header-band { padding: .6rem 1rem; } + .sb-header-title { font-size: .85rem; } + .sb-vs-row { gap: .8rem; padding: 1.5rem 1rem 1rem; } + .sb-team { font-size: 1.1rem; } + .sb-vs-badge { width: 2.5rem; height: 2.5rem; font-size: .65rem; } + .sb-fixture-strip { padding: .5rem 1rem .8rem; } + .sb-tickets-cta { margin: .5rem 1rem; } + .sb-overview { padding: .4rem 1rem .8rem; } +} diff --git a/app.js b/app.js index d4cb26e..a998b5d 100644 --- a/app.js +++ b/app.js @@ -3280,6 +3280,114 @@ } } + // ── SPORTSEVENT (Matchday Scoreboard) ── + if (cfg.type === "SportsEvent") { + const card = document.querySelector(".card"); + if (card) card.classList.add("scoreboard"); + + // Hide normal header + const hd = document.querySelector(".hd"); + if (hd) hd.classList.add("hidden"); + + // Remove generic hero — we build our own + const heroMount = document.getElementById("hero-mount"); + if (heroMount) heroMount.querySelector('.hero')?.remove(); + + // ── Bold header band with competition name ── + const eventName = data[cfg.titleProp] || ""; + const headerBand = document.createElement("div"); headerBand.className = "sb-header-band"; + const iconSpan = document.createElement("span"); iconSpan.className = "sb-header-icon"; + iconSpan.textContent = (cfg.icon || "⚽") + " "; + headerBand.appendChild(iconSpan); + const bandTitle = document.createElement("span"); bandTitle.className = "sb-header-title"; + bandTitle.textContent = eventName; + headerBand.appendChild(bandTitle); + heroMount.appendChild(headerBand); + handled.add("name"); handled.add("image"); + + // ── Big centered VS row ── + // Try to infer home vs away from the name (e.g. "Team A vs Team B") + let homeName = "", awayName = ""; + const vsMatch = eventName.match(/(.+?)\s+(?:vs?\.?|[:–\-])\s+(.+)/i); + if (vsMatch) { + homeName = vsMatch[1].trim(); + awayName = vsMatch[2].trim(); + } + const vsRow = document.createElement("div"); vsRow.className = "sb-vs-row"; + if (homeName && awayName) { + const homeEl = document.createElement("div"); homeEl.className = "sb-team sb-team-home"; + homeEl.textContent = homeName; + const vsBadge = document.createElement("div"); vsBadge.className = "sb-vs-badge"; + vsBadge.textContent = "VS"; + const awayEl = document.createElement("div"); awayEl.className = "sb-team sb-team-away"; + awayEl.textContent = awayName; + vsRow.append(homeEl, vsBadge, awayEl); + } else { + // No VS pattern — just show the event name large + const eventEl = document.createElement("div"); eventEl.className = "sb-event-big"; + eventEl.textContent = eventName; + vsRow.appendChild(eventEl); + } + view.appendChild(vsRow); + + // ── Fixture info strip: date + venue + organizer ── + const fixtureStrip = document.createElement("div"); fixtureStrip.className = "sb-fixture-strip"; + + const startDate = data["startDate"]; + if (startDate) { + const d = new Date(startDate); + const dateStr = isNaN(d.getTime()) ? startDate : d.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", year: "numeric" }); + const timeStr = isNaN(d.getTime()) ? "" : d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }); + const dateRow = document.createElement("div"); dateRow.className = "sb-fixture-row"; + dateRow.innerHTML = "Ὄ5 " + dateStr + (timeStr ? " · " + timeStr : "") + ""; + fixtureStrip.appendChild(dateRow); + handled.add("startDate"); + } + const endDate = data["endDate"]; + if (endDate) handled.add("endDate"); + + const loc = data["location"]; + if (loc) { + const locRow = document.createElement("div"); locRow.className = "sb-fixture-row"; + locRow.innerHTML = "📍 " + loc + ""; + fixtureStrip.appendChild(locRow); + handled.add("location"); + } + + const org = data["organizer"]; + if (org) { + const orgRow = document.createElement("div"); orgRow.className = "sb-fixture-row"; + orgRow.innerHTML = "🎫 " + org + ""; + fixtureStrip.appendChild(orgRow); + handled.add("organizer"); + } + if (fixtureStrip.children.length) view.appendChild(fixtureStrip); + + // ── Get Tickets CTA ── + const url = data["url"]; + if (url) { + const cta = document.createElement("a"); cta.className = "sb-tickets-cta"; + cta.href = url; cta.target = "_blank"; cta.rel = "noopener"; + cta.innerHTML = "Get Tickets "; + view.appendChild(cta); + handled.add("url"); + } + + // ── Description as match overview ── + const desc = data["description"]; + if (desc) { + const overview = document.createElement("div"); overview.className = "sb-overview"; + const label = document.createElement("div"); label.className = "sb-overview-label"; + label.textContent = "Match Overview"; + overview.appendChild(label); + const body = document.createElement("p"); body.className = "sb-overview-body"; + body.textContent = desc; + overview.appendChild(body); + view.appendChild(overview); + handled.add("description"); + } + } + return handled; }