From 1977e077f8c0f58c77d6e05f8569b537f92d944c Mon Sep 17 00:00:00 2001 From: Local Administrator Date: Wed, 3 Jun 2026 13:20:32 +0000 Subject: [PATCH] =?UTF-8?q?fix(avbeat):=203=20homepage=20bugs=20=E2=80=94?= =?UTF-8?q?=20invisible=20AI=20sidebar=20titles,=20dark=20tagline=20on=20b?= =?UTF-8?q?lue=20header,=20literal=20

=20in=20excerpts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (A) src/components/AISuggestedArticles.tsx :186 + :287 — 'AI Suggested Articles' card titles used text-[#e0e0e0] on now-white card surfaces, making the title invisible until group-hover flipped it to #1D4ED8. Both occurrences changed to text-[#0F172A]. (ArticleFeed.tsx:627 and NewsPageClient.tsx:276 were already navy from the prior contrast-fix deploy and cover both pagination + infinite-scroll variants.) (B) src/components/AvBeatLogo.tsx :99 — header tagline 'Inside the Future of Pro AV' was rendered in --brand-text-muted (#475569 slate) on a now-blue nav (#1D4ED8), near-invisible. Changed to rgba(255,255,255,0.85) so it reads against the blue while sitting visually subordinate to the wordmark above it. (C) src/lib/articles/excerpt.ts — cleanExcerpt now strips HTML tags (

,
, , etc.) and decodes common entities ( , &, <, >, ", ', ') at render time, then collapses whitespace before the existing trailing-junk/last-word logic runs. Display-layer only — stored excerpts are not mutated, matching the 'fix at the DISPLAY layer' instruction. --- src/components/AISuggestedArticles.tsx | 4 ++-- src/components/AvBeatLogo.tsx | 2 +- src/lib/articles/excerpt.ts | 25 ++++++++++++++++++++----- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/AISuggestedArticles.tsx b/src/components/AISuggestedArticles.tsx index 1239949..fecb22a 100644 --- a/src/components/AISuggestedArticles.tsx +++ b/src/components/AISuggestedArticles.tsx @@ -183,7 +183,7 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {

{article.category}

-

+

{article.title}

@@ -284,7 +284,7 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) { {article.category} -

+

{article.title}

{article.readTime}

diff --git a/src/components/AvBeatLogo.tsx b/src/components/AvBeatLogo.tsx index d92552e..029835a 100644 --- a/src/components/AvBeatLogo.tsx +++ b/src/components/AvBeatLogo.tsx @@ -96,7 +96,7 @@ export default function AvBeatLogo({ fontWeight: 500, fontSize: 11, letterSpacing: "0.04em", - color: "var(--brand-text-muted, #475569)", + color: "rgba(255, 255, 255, 0.85)", lineHeight: 1.2, whiteSpace: "nowrap", }} diff --git a/src/lib/articles/excerpt.ts b/src/lib/articles/excerpt.ts index 94ebfd4..0ef67df 100644 --- a/src/lib/articles/excerpt.ts +++ b/src/lib/articles/excerpt.ts @@ -1,16 +1,31 @@ // Render-time excerpt sanitizer. Some imported / AI-generated excerpts were // hard-truncated at a fixed byte count, leaving sentences clipped mid-word -// (e.g. "Since 1976, the co"). Until the upstream excerpt generator gets the -// same treatment, this helper makes any clipped excerpt visually clean: -// strip trailing junk, snap to the last complete word, and append an -// ellipsis only when the excerpt isn't already a complete sentence. +// (e.g. "Since 1976, the co"). Others arrive with raw HTML still in the +// string (e.g. "

In today's fast-paced..."), which renders as visible +// "

" text on the card. This helper handles both at render time without +// mutating stored data. const SENTENCE_END = /[.!?…]['")\]]?$/; const TRAILING_JUNK = /[\s,;:—–\-—_]+$/; export function cleanExcerpt(raw: string | null | undefined): string { if (!raw) return ""; - let s = raw.replace(/ /g, " ").trim(); + // Strip any HTML tags first — excerpts are rendered as plain text, so + //

,
, , etc. should be removed rather than escaped. + // Replace tags with a space so adjacent words don't collide when + // formerly-block-level boundaries are removed. + let s = raw.replace(/<[^>]*>/g, " "); + // Decode the common entities that show up in WP/AI-imported excerpts. + s = s + .replace(/ /g, " ") + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'"); + // Collapse multi-whitespace runs left behind by tag removal. + s = s.replace(/\s+/g, " ").trim(); if (!s) return ""; if (SENTENCE_END.test(s)) return s; // Drop the trailing partial token.