diff --git a/app/globals.css b/app/globals.css index 9824c11..a7b8eb6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -309,6 +309,29 @@ input, textarea { font-family: inherit; } inset: 0; background: linear-gradient(to top, rgba(12, 11, 9, 0.95) 12%, rgba(12, 11, 9, 0.25) 55%, transparent); } +/* no-image lead: a faint giant "BAM" watermark so the hero never reads as a + dead dark void (most Bing-wire stories carry no photo). */ +.lead .hero.noimg { + background: + radial-gradient(120% 140% at 78% 18%, rgba(255, 205, 36, 0.10), transparent 55%), + repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.014) 0 2px, transparent 2px 26px), + var(--bg-2); +} +.lead .hero.noimg .ghost { + position: absolute; + right: 4%; + bottom: -6%; + z-index: 0; + font-family: var(--font-display); + font-weight: 900; + font-size: clamp(72px, 15vw, 190px); + letter-spacing: 0.02em; + color: rgba(255, 205, 36, 0.07); + text-shadow: 0 0 40px rgba(255, 205, 36, 0.05); + user-select: none; + pointer-events: none; + line-height: 1; +} .lead .hero .ph-tag { position: absolute; left: 12px; bottom: 12px; diff --git a/app/page.js b/app/page.js index d755a3e..ee511d2 100644 --- a/app/page.js +++ b/app/page.js @@ -258,7 +258,8 @@ export default function Home() { )} {lead && (
openStory(lead)}> -
+
+ {!lead.image && BAM}
{lead.named && ★ NAMED BAM} {sectionName(lead.category)} diff --git a/tests/shots/01-front.png b/tests/shots/01-front.png new file mode 100644 index 0000000..6486a27 Binary files /dev/null and b/tests/shots/01-front.png differ diff --git a/tests/shots/02-reader.png b/tests/shots/02-reader.png new file mode 100644 index 0000000..5024092 Binary files /dev/null and b/tests/shots/02-reader.png differ diff --git a/tests/shots/03-board-empty.png b/tests/shots/03-board-empty.png new file mode 100644 index 0000000..b8ca916 Binary files /dev/null and b/tests/shots/03-board-empty.png differ diff --git a/tests/shots/04-board-posted.png b/tests/shots/04-board-posted.png new file mode 100644 index 0000000..f397a81 Binary files /dev/null and b/tests/shots/04-board-posted.png differ diff --git a/tests/shots/05-desk-education.png b/tests/shots/05-desk-education.png new file mode 100644 index 0000000..e7f47a3 Binary files /dev/null and b/tests/shots/05-desk-education.png differ diff --git a/tests/shots/06-front-mobile.png b/tests/shots/06-front-mobile.png new file mode 100644 index 0000000..d261bbc Binary files /dev/null and b/tests/shots/06-front-mobile.png differ diff --git a/tests/ui-check.mjs b/tests/ui-check.mjs new file mode 100644 index 0000000..6e75e8d --- /dev/null +++ b/tests/ui-check.mjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node +// Playwright visual check for the BAM front page (headless Chromium). +// Seeds the bama_ui scratch DB with realistic stories first, then: +// - loads / (asserts no console/page errors, masthead present) +// - screenshots the front page +// - clicks a story card -> reader opens with body +// - switches to the board view, posts a thank-you, asserts it renders +// - switches to a desk view +import pw from "playwright"; +import { MongoClient } from "mongodb"; + +const BASE = process.env.BASE || "http://127.0.0.1:4005"; +const OUT = "/root/projects/bidaangmamamayan/tests/shots"; +import { mkdirSync } from "fs"; +mkdirSync(OUT, { recursive: true }); + +// ── seed (shape MUST match what lib/crawl.js stores, or the feed query +// `discoveredAt >= now-14d` returns nothing) ─────────────────────────────── +const mc = new MongoClient("mongodb://127.0.0.1:27099/bama"); +await mc.connect(); +const db = mc.db("bama_ui"); +await db.collection("stories").deleteMany({}); +const now = Date.now(); +const H = 3600e3; +const mk = (o) => ({ + url: o.url, title: o.title, summary: o.description, image: null, + source: o.source, category: o.category, desk: "BAM Wire", feedKind: "query", + named: !!o.named, fallback: false, + published: new Date(o.published), + discoveredAt: new Date(o.published + H), // discovered an hour after publish + ingestedAt: new Date(o.published + H), +}); +const rawSeed = [ + { + url: "https://www.inquirer.net/bam/1", title: "Sen. Bam Aquino unveils college scholarship expansion for Pampanga students", + description: "Senator Bam Aquino on Tuesday unveiled the expanded scholarship program that will fund 50,000 college students across the country, calling education 'the great equalizer'.", + source: "Inquirer.net", published: now - 2 * H, named: true, category: "education", + }, + { + url: "https://www.pna.gov.ph/bam/2", title: "Aquino echoes House call to probe VisMin power outages, rates", + description: "Senator Bam Aquino backed the congressional inquiry into rotational brownouts in the Visayas, urging regulators to audit system loss charges.", + source: "Philippine News Agency", published: now - 5 * H, named: true, category: "energy", + }, + { + url: "https://rappler.com/bam/3", title: "Bam Aquino's esports bill clears Senate committee", + description: "The bill that would recognize esports as a sport and fund university esports programs passed the committee, with Sen. Bam Aquino as prime sponsor.", + source: "Rappler", published: now - 8 * H, named: true, category: "gaming", + }, + { + url: "https://www.pna.gov.ph/bam/4", title: "Aquino joins senators in okays of barangay, SK terms bill", + description: "The Senate approved the bill setting five-year terms for barangay and SK officials on third reading.", + source: "Philippine News Agency", published: now - 20 * H, named: false, category: "politics", + }, + { + url: "https://www.inquirer.net/bam/5", title: "Bam Aquino pushes student loan reform for higher education", + description: "The Pampanga senator said the reform would cap interest rates on student loans and extend repayment windows for fresh graduates.", + source: "Inquirer.net", published: now - 30 * H, named: true, category: "education", + }, + { + url: "https://www.inquirer.net/bam/6", title: "Senate okays 5-year barangay, SK terms; 2026 polls moved to 2028", + description: "The upper house approved the measure on third reading.", + source: "Inquirer.net", published: now - 40 * H, named: false, category: "politics", + }, + { + url: "https://rappler.com/bam/7", title: "Bam Aquino calls for fairer power rate adjustment mechanism", + description: "The senator said consumers should not shoulder transmission losses.", + source: "Rappler", published: now - 50 * H, named: true, category: "energy", + }, + { + url: "https://www.inquirer.net/bam/8", title: "Gaming: Bamboo Aquino's e-sports arena bill heads to plenary", + description: "The measure will fund e-sports facilities in state colleges and universities nationwide.", + source: "Inquirer.net", published: now - 60 * H, named: false, category: "gaming", + }, +]; +const seed = rawSeed.map(mk); +await db.collection("stories").insertMany(seed); +await db.collection("board").deleteMany({}); +console.log("seeded", seed.length, "stories"); + +// ── browser ────────────────────────────────────────────────────────────── +const browser = await pw.chromium.launch(); +const page = await browser.newPage({ viewport: { width: 1280, height: 900 } }); +const errors = []; +page.on("pageerror", (e) => errors.push("pageerror: " + e.message)); +page.on("console", (m) => { if (m.type() === "error") errors.push("console: " + m.text()); }); + +await page.goto(BASE, { waitUntil: "domcontentloaded", timeout: 30000 }); +await page.waitForTimeout(2500); // feed fetch + render + +const masthead = await page.textContent("body"); +if (!/BIDA ANG MAMAMAYAN/.test(masthead || "")) errors.push("masthead missing"); +if (!/BAM/.test(masthead || "")) errors.push("BAM highlight missing"); +await page.screenshot({ path: OUT + "/01-front.png" }); +console.log("front page screenshot done"); + +// open the lead story +const lead = page.locator(".panel.lead"); +if (await lead.count()) { + await lead.click(); + await page.waitForTimeout(1500); + await page.screenshot({ path: OUT + "/02-reader.png" }); + const readerText = await page.textContent("body"); + if (!/SCHOLARSHIP|scholarship|PAMPANGA|Pampanga/i.test(readerText || "")) errors.push("reader did not open lead story"); + await page.keyboard.press("Escape"); + await page.waitForTimeout(400); +} else { + errors.push("no lead story card"); +} + +// board view — the nav tab (there's also a "SIGN THE BOARD" link on the front) +await page.getByRole("link", { name: "Thank You Board" }).click(); +await page.waitForTimeout(800); +await page.screenshot({ path: OUT + "/03-board-empty.png" }); +await page.fill("#nick", "PampangaGamer"); +await page.fill("#msg", "Thank you Senator BAM for the scholarship expansion — my whole batch is riding on it!"); +await page.locator("button.go").click(); +await page.waitForTimeout(1200); +await page.screenshot({ path: OUT + "/04-board-posted.png" }); +const boardText = await page.textContent(".postlist"); +if (!/PampangaGamer/.test(boardText || "")) errors.push("posted nick not rendered"); +if (!/Thank you Senator BAM for the scholarship/.test(boardText || "")) errors.push("posted message not rendered"); + +// back to front, then a desk view — click the Education desk card (grid lives on front) +await page.getByRole("link", { name: "Front", exact: true }).click(); +await page.waitForTimeout(400); +const eduCard = page.locator(".deskcard", { hasText: /Education/ }).first(); +if (await eduCard.count()) { + await eduCard.click(); + await page.waitForTimeout(600); + await page.screenshot({ path: OUT + "/05-desk-education.png" }); + const deskBody = await page.textContent("main"); + if (!/scholarship|Scholarship|student|Student/i.test(deskBody || "")) errors.push("education desk view shows no education story"); +} else { + errors.push("education desk card missing from grid"); +} + +// desktop + mobile front +await page.getByRole("link", { name: "Front", exact: true }).click(); +await page.waitForTimeout(500); +await page.setViewportSize({ width: 390, height: 844 }); +await page.waitForTimeout(600); +await page.screenshot({ path: OUT + "/06-front-mobile.png" }); + +console.log(errors.length ? "UI ERRORS:\n" + errors.join("\n") : "UI CLEAN — no console/page errors"); +await browser.close(); +await mc.close(); +process.exit(errors.length ? 1 : 0);