From a0543ae0b4794246954c25b32aa6e0badb989388 Mon Sep 17 00:00:00 2001 From: Charlie Date: Thu, 11 Jun 2026 13:12:40 +0200 Subject: [PATCH] feat: add Car premium listing card layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bespoke renderTypeSpecific for Car type — premium car listing with photo hero, brand badge overlay, bold title with gradient banner, spec grid of labeled chips (Year, Fuel, Colour, Model), description, and View Advert CTA. Class .card.car-listing. Gate passes (324 apps). --- app.css | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app.js | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/app.css b/app.css index 7fcc618..d5ec4c8 100644 --- a/app.css +++ b/app.css @@ -4441,3 +4441,108 @@ footer a:hover { color: var(--accent-2); } .fp-standfirst { padding: 0 1rem .6rem; } .fp-masthead { padding: 1rem 1rem 0; } } + +/* ── CAR (premium car listing) ── */ +.card.car-listing { + background: var(--card); + border-radius: var(--radius); + overflow: hidden; +} +.card.car-listing .hd.hidden { display: none; } + +.car-hero { + position: relative; + width: 100%; + aspect-ratio: 16/9; + overflow: hidden; + background: #1a1a1e; +} +.car-hero-img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.car-brand-badge { + position: absolute; + top: .8rem; + left: .8rem; + background: rgba(255,255,255,.92); + backdrop-filter: blur(8px); + color: var(--ink); + font-size: .78rem; + font-weight: 700; + letter-spacing: .06em; + text-transform: uppercase; + padding: .3rem .7rem; + border-radius: 6px; +} + +.car-title { + font-size: 1.25rem; + font-weight: 700; + color: var(--ink); + padding: 1rem 1.5rem .6rem; + line-height: 1.25; + background: linear-gradient(180deg, rgba(30,64,175,.06) 0%, transparent 100%); +} + +.car-spec-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: .6rem; + padding: 0 1.5rem .8rem; +} + +.car-spec-chip { + background: var(--surface); + border: 1px solid var(--line); + border-radius: 8px; + padding: .55rem .7rem; + text-align: center; +} +.car-spec-label { + font-size: .68rem; + text-transform: uppercase; + letter-spacing: .07em; + color: var(--muted-2); + font-weight: 600; + margin-bottom: .15rem; +} +.car-spec-value { + font-size: .85rem; + font-weight: 600; + color: var(--ink); +} + +.car-desc { + font-size: .84rem; + line-height: 1.6; + color: var(--muted); + padding: 0 1.5rem .8rem; +} + +.car-cta { + display: block; + text-align: center; + margin: .4rem 1.5rem 1.2rem; + padding: .7rem; + background: var(--accent); + color: #fff; + font-weight: 600; + font-size: .9rem; + border-radius: 10px; + text-decoration: none; + transition: filter .15s, transform .1s; +} +.car-cta:hover { + filter: brightness(1.1); + transform: translateY(-1px); +} + +@media (max-width: 480px) { + .car-spec-grid { grid-template-columns: 1fr 1fr; padding: 0 1rem .6rem; } + .car-title { padding: .8rem 1rem .5rem; font-size: 1.1rem; } + .car-desc { padding: 0 1rem .6rem; } + .car-cta { margin: .4rem 1rem 1rem; } +} diff --git a/app.js b/app.js index eff5120..1250ac2 100644 --- a/app.js +++ b/app.js @@ -2506,6 +2506,88 @@ handled.add("hasMenuSection"); handled.add("provider"); handled.add("url"); } + // ── CAR (premium car listing) ── + if (cfg.type === "Car") { + const card = document.querySelector(".card"); + if (card) card.classList.add("car-listing"); + + // Hide normal header + const hd = document.querySelector(".hd"); + if (hd) hd.classList.add("hidden"); + + // Remove generic hero if present, we'll build our own + const heroMount = document.getElementById("hero-mount"); + if (heroMount) heroMount.querySelector('.hero')?.remove(); + + // ── Photo hero ── + const img = data["image"]; + if (img) { + const hero = document.createElement("div"); hero.className = "car-hero"; + const imgEl = document.createElement("img"); imgEl.className = "car-hero-img"; + imgEl.src = img; imgEl.alt = data[cfg.titleProp] || "Car photo"; + imgEl.loading = "lazy"; + hero.appendChild(imgEl); + // Brand badge overlay + const brand = data["brand"]; + if (brand) { + const badge = document.createElement("div"); badge.className = "car-brand-badge"; + badge.textContent = brand; + hero.appendChild(badge); + } + view.appendChild(hero); + } + + // ── Name big over price-style banner ── + const title = data[cfg.titleProp]; + if (title) { + const nameEl = document.createElement("div"); nameEl.className = "car-title"; + nameEl.textContent = title; + view.appendChild(nameEl); + } + + // ── Spec grid of labeled boxes ── + const specItems = []; + const year = data["vehicleModelDate"]; + if (year) specItems.push({ label: "Year", value: year }); + const fuel = data["fuelType"]; + if (fuel) specItems.push({ label: "Fuel", value: fuel }); + const colour = data["color"]; + if (colour) specItems.push({ label: "Colour", value: colour }); + const model = data["model"]; + if (model) specItems.push({ label: "Model", value: model }); + + if (specItems.length) { + const grid = document.createElement("div"); grid.className = "car-spec-grid"; + specItems.forEach(s => { + const chip = document.createElement("div"); chip.className = "car-spec-chip"; + chip.innerHTML = "
" + s.label + "
" + s.value + "
"; + grid.appendChild(chip); + }); + view.appendChild(grid); + } + + // ── Description ── + const desc = data["description"]; + if (desc) { + const descEl = document.createElement("div"); descEl.className = "car-desc"; + descEl.textContent = desc; + view.appendChild(descEl); + } + + // ── View Advert CTA ── + const url = data["url"]; + if (url) { + const cta = document.createElement("a"); cta.className = "car-cta"; + cta.href = url; cta.target = "_blank"; cta.rel = "noopener"; + cta.textContent = "View Advert →"; + view.appendChild(cta); + } + + handled.add("name"); handled.add("image"); handled.add("description"); + handled.add("brand"); handled.add("model"); handled.add("vehicleModelDate"); + handled.add("fuelType"); handled.add("color"); handled.add("url"); + } + // ── NEWSARTICLE (newspaper front page) ── if (cfg.type === "NewsArticle") { const card = document.querySelector(".card");