Commit Graph

283 Commits

Author SHA1 Message Date
Ryan Salazar
c6bf372b50 header: align ticker + browse bar to featured card's visual edges (px-4 inside max-w-container) 2026-05-22 01:42:21 +00:00
Ryan Salazar
ca062eb4c8 team: pin Ryan Salazar first w/ FOUNDER / EDITOR-IN-CHIEF title 2026-05-22 01:38:57 +00:00
Ryan Salazar
836bd1de0e ticker: slow both rows by 30% (THE LATEST 100→130s, FORUM 144→187s) 2026-05-22 01:37:21 +00:00
Ryan Salazar
a92aa38b78 ticker: top row 50s→100s (50% slower) + rename BEAT→THE LATEST; bottom 120s→144s (20% slower) 2026-05-22 01:33:31 +00:00
Ryan Salazar
9da8122871 header: align About dropdown vertically + slow ticker scroll (BEAT 17→50s, FORUM 40→120s) 2026-05-22 01:29:00 +00:00
Ryan Salazar
d2ef1ba6c3 header: constrain ticker + browse bar to max-w-container (1200px) aligned with featured box 2026-05-22 01:23:55 +00:00
Ryan Salazar
e6eb3cc99c header+ticker: new menu (HOME/FEATURED/THE LATEST/SHOW COVERAGE/FORUM/ABOUT) + drop reduced-motion rule that was freezing ticker animation 2026-05-22 01:22:12 +00:00
Ryan Salazar
00a79f12b7 header: move browse bar (main menu) below the ticker 2026-05-22 01:16:17 +00:00
Ryan Salazar
7abd613113 header: move 728x90 banner inline between logo and right-side icons 2026-05-22 01:12:16 +00:00
Ryan Salazar
e7b5c29a07 header: revert to original logo.png wordmark 2026-05-22 01:09:04 +00:00
Ryan Salazar
729547764e ticker: query wp_imported_posts (not native_articles) + fallback when 7d window empty 2026-05-22 00:46:44 +00:00
Ryan Salazar
ef1470a58b css: drop escaped-slash selectors that broke cssnano 2026-05-22 00:41:28 +00:00
Ryan Salazar
6e7e660927 brand: neon BROADCAST BEAT wordmark (SVG + PNG)
- SVG logo at public/brand/logo.svg with pink-magenta BROADCAST,
  cyan BEAT, cyan heartbeat waveform underline, ™ mark, and
  "THE PULSE OF PRODUCTION INTELLIGENCE" tagline
- PNG renders at 1x (1200x320), 2x (2400x640), small (600x160)
- Header swapped from /assets/images/logo.png to /brand/logo.svg

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 00:30:35 +00:00
Ryan Salazar
aee67ef9f6 homepage: neon dual-speed ticker + emerald/cyan palette pass
- New DualSpeedTicker component (CSS keyframes, GPU-accelerated,
  pause on hover, 28px rows, 17s BEAT / 40s FORUM, refresh 5min)
- /api/ticker/beat-news: last 7 days from bb.native_articles
- /api/ticker/forum-posts: latest threads + replies interleaved
- Mounted at the bottom of Header so every page gets it
- Scoped .bb-neon palette on homepage root: section heads +
  text-shadow glow, neon-tinted card borders, emerald→cyan button
  gradient, cyan hover state on links, subscribe boxes neon-bordered
- CSS-only — no layout changes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:58:12 +00:00
Ryan Salazar
5eaf64925f homepage: remove news ticker
Per request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:24:02 +00:00
Ryan Salazar
a8b75593c2 forum: require auth for posting + add login/register/edit pages
- API: require auth on thread+reply POST (401 if !user); lazy-create
  forum_user_profile via service-role; author_name = display_name
- UI: surface API errors (kill silent catch); auto-redirect to login
  on 401; gate new-thread + reply forms with "Sign in to post" CTA
- /forum/login + /forum/register: forum-themed Supabase Auth pages
  with ?next= redirect support
- /forum/profile/edit + /api/forum/profile/me: edit own forum profile
  (username, display_name, bio, role, company, location, avatar)
- Header: logout button + sign-in/join links when not authed
- forum/user/[username]: show Edit Profile button when viewing own

Fixes posting bug: RLS rejected anon inserts with cryptic message
that the UI silently swallowed; now blocked with friendly 401 + redirect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:41:28 +00:00
Ryan Salazar
585a9503ab homepage: make '/' the canonical route, 301 /home-page → /
Previous state: '/' was 307-redirected to '/home-page', and the
home-page route declared canonical='/home-page'. Net effect on
SEO: Google indexed '/home-page' as the canonical homepage and
treated '/' as a transient redirect (no link equity passed).

Changes:
- next.config.mjs: replace the source:'/' destination:'/home-page'
  temporary redirect with source:'/home-page' destination:'/'
  permanent (301). Any existing crawler entry for /home-page now
  consolidates onto '/'.
- src/app/page.tsx (new): re-exports default + metadata from
  ./home-page/page so '/' serves the homepage directly. No file
  moves required, no relative-import breakage in the home-page
  component subtree.
- src/app/home-page/page.tsx: canonical and openGraph.url
  '/home-page' → '/'. The page still builds at /home-page but
  the 301 above will redirect any hit before render.
- src/app/sitemap.ts: drop the duplicate /home-page entry — Google
  shouldn't be told about a URL that just redirects.
2026-05-21 17:37:45 +00:00
Ryan Salazar
78c2ee656e homepage: typography upgrade + ArticleFeed polish
- layout.tsx: load Lora (serif) + Inter (sans) via next/font/google,
  self-hosted at build time, exposed as --font-serif / --font-sans
  CSS variables on <html>. No CDN fetch, no FOIT, no GDPR concern.
- tailwind.css: --font-heading and --font-body now reference the
  next/font variables with Georgia / -apple-system fallbacks for the
  brief window before the self-hosted face is ready. --font-mono
  upgraded to IBM Plex Mono → ui-monospace → Courier New fallback
  (DoubleTicker chips already specify IBM Plex Mono directly).
- ArticleFeed: drop the FEATURED filter chip (duplicates the
  Staff Editorial section above), add publish date alongside the
  byline on every card, lift the homepage feed cap from 200 → 5000.
- api/public/posts: lift MAX_LIMIT to 10000 so client-side
  pagination + infinite-scroll see the full archive.
2026-05-21 01:51:29 +00:00
Ryan Salazar
567a45079f homepage: shrink Staff Editorial header to match The Latest
Earlier this evening I bumped the Staff Editorial header to a
font-serif 2xl/3xl bold — too loud, fought with The Latest section
header below it on the same page.

Reverted to the same shape The Latest uses:
  <span class="section-label">Staff Editorial</span>
  <div class="flex-1 h-px bg-[#2a2a2a]" />
  <Link>All featured →</Link>

That's the global .section-label utility (font-body 13px,
font-weight:700, letter-spacing:0.08em, uppercase, color
accent-blue, with a 3px accent border on the left). Now the two
section headers on the homepage are visually consistent.
2026-05-20 21:52:53 +00:00
Ryan Salazar
74677a80b8 homepage: remove top bar + fix subscribe + Staff Editorial header
1. Remove SystemStatusBar from root layout. The "29,220 articles
   indexed / 13 events tracked / [v2.0.0]" strip ate vertical space
   above the fold without serving readers.

2. /api/newsletter/subscribe was returning 500 "Failed to save
   subscription" because:
   - The route used the anon-session client and tried to UPSERT on
     bb.newsletter_subscribers, but the table's RLS only has an
     INSERT policy for the public role. Upsert needs INSERT+UPDATE,
     so the second branch failed RLS.
   - Switched to createAdminClient (service-role) — bypasses RLS,
     which is fine because this is a server-side endpoint with
     server-side email validation.
   - Welcome email now sends via nodemailer in a non-blocking
     background task so a slow SMTP doesn't hold up the response.
     Newer HTML template, brand-aligned, with a real CTA back to
     /news.

   Side work to wire SMTP (done out-of-band):
   - Created newsletter@broadcastbeat.com mailbox in Mailcow via
     the API (2GB quota, active, no force-pw-update).
   - Set SMTP_HOST=mail.onsethost.com, SMTP_PORT=587,
     SMTP_USER/PASS/FROM_EMAIL/FROM_NAME in Coolify env so the
     welcome email actually fires after the next deploy.

3. FeaturedBentoFromDb section header: "Featured · staff editorial"
   (small mono caps) → "Staff Editorial" (font-serif 2xl/3xl, bold,
   with a thin metadata line "Selected by our newsroom" beside it
   and a 2px bottom rule). Matches the editorial typography on the
   article pages.
2026-05-20 18:51:25 +00:00
Ryan Salazar
8f8eaa5fe8 newsletter: site-wide signup modal with 7-day dismiss
Renders on every page via root layout. Behavior:
- 5s delay after first paint so it doesn't slam the user.
- Animates from a pill into the full card (bb-modal-pop keyframe);
  respects prefers-reduced-motion.
- Heading "Get the latest in broadcast & media", email field,
  Subscribe + "Not now" + close (X).
- Subscribe POSTs to the existing /api/newsletter/subscribe route
  (native system — newsletter_subscribers table + SMTP welcome
  via Mailcow/SMTP2GO). Listmonk wasn't installed anywhere on the
  infra; switching to it later is a one-line URL change.
- "Not now" + backdrop click + Escape + X all dismiss for 7 days
  via localStorage["bb_newsletter_dismissed"].
- On success → "Check your email" panel + auto-close at 4s;
  subscribed flag stored in localStorage so the modal never
  re-prompts that visitor.

Styling matches the site's dark-navy + electric-blue palette
(NOT the neon ticker palette). No layout/page shifts — overlay
only.
2026-05-20 17:55:29 +00:00
Ryan Salazar
418027b469 search: real autocomplete previews + clearer placeholder
PLACEHOLDER: "Search broadcast news…" → "Search articles, gear,
vendors". Drops the weird ellipsis and the "we're a broadcast site
so 'broadcast news' is redundant" feel.

API (/api/search/suggest):
- Match expanded from {title, author_name} to
  {title, excerpt, content, author_name} — surfaces hits when the
  phrase lives deep in the article body.
- Returns a snippet object {text, matchStart, matchEnd} sliced
  from the first hit (HTML-stripped), with ~80 chars of context on
  either side and "…" markers when truncated.
- Returns matched_in: 'title' | 'excerpt' | 'content' | 'author' so
  the UI can show "match in article body" / "match in byline".
- Returns date + author_name on each result.

UI dropdown (Header.tsx):
- Each suggestion now renders three rows:
  1) Title (bold) + category chip
  2) Snippet with the matched substring wrapped in <mark>
  3) Author byline + date + (if match wasn't in title) a tag
     showing where the match occurred
- Vertical stacking with a colored left-border on hover so the
  active suggestion is visible.

CSS:
- Stack item layout, snippet with -webkit-line-clamp:2 so long
  matches don't overflow.
- <mark> background uses the accent blue at 28% so the highlight
  reads against the dark card.
2026-05-20 16:22:04 +00:00
Ryan Salazar
c1705e1a4b ai: bump hybridRouter connection timeout 8s → 30s
Manual fire of bb-company-coverage-scan tonight failed because the
8s connect timeout was tripping on llama3.1:70b cold-load when
another model was resident in VRAM on AI_001. Bumped to 30s so
the orchestrator can wait through a model swap.

Also bumped background request timeout 120s → 180s for the same
reason — story generation against the 70B can take >2 min on a
saturated GPU.

Coolify env OLLAMA_ENDPOINT was simultaneously flipped from
AI_001 (10.10.0.66) to AI_002 (10.10.0.67) since AI_002 is less
loaded — AI_001 is doing author enrichment and scrubber work.
2026-05-20 13:32:09 +00:00
Ryan Salazar
78e08046a7 ticker: redesign — news-wire aesthetic, drop the bright label box
The previous version used solid neon-green / neon-cyan blocks for
the "Live Wire" / "On Calendar" labels which read as too aggressive
against the otherwise dark-navy site. Replaced with a real news-wire
look:

- Pulse-dot indicator (animated breathing dot) + small-caps mono
  label on the left, separated from the feed by a thin rule.
- Item codes (SONY / MTRX / NAB) become small bordered chips with
  subtle neon background tint instead of bare colored text.
- Timestamps are mid-gray monospace; titles are clean Inter at
  13px with slight letter-spacing.
- Top + bottom of each bar get a hairline gradient (neon→cyan
  on row 1, dimmer cyan-only between rows) instead of a hard
  border.
- Items are separated by faint vertical hairlines, padded
  consistently.
- Marquee slowed slightly (110s / 170s) so titles are legible.

Same data sources (news + events), same scroll directions, just
better visual integration with the rest of the dark-navy site.
2026-05-20 13:13:25 +00:00
Ryan Salazar
097181eb43 forum: render threads on /forum default view (not just search mode)
Bug: the index page only fetched threads when user was actively
searching, filtering by category, or sorting non-default. Default
load → setThreads([]) → forum looked empty even though 50+
threads existed in the DB.

Fix: always fetch on mount + filter changes (drop the
isSearchMode gate on the effect), and add a Recent Threads
section in the default view above the Browse Categories list,
showing the 12 most recent threads with author + reply count.

API + DB unchanged.
2026-05-20 13:11:32 +00:00
Ryan Salazar
f6b52f7c2d forum: user profile pages + clickable bylines + neon pulse favicon
USER PROFILES (/forum/user/[username]):
- New API at /api/forum/user-by-username/[username] resolves by
  username OR display_name (URL-encoded), since forum threads/replies
  store author_name = display_name. Returns the persona's profile,
  thread history (up to 50), and recent replies (up to 20).
- New page renders the persona card with archetype badge
  (Audio Engineer / Colorist / etc.) + expertise level badge
  (beginner/intermediate/senior) + role title, company, location,
  years_experience, join date, and an expertise-tags chip row.
- Two columns underneath: "Threads started" and "Recent replies"
  with proper deep-links into each thread.
- Author names in /forum, /forum/[slug], and /forum/thread/[id] are
  now <Link> elements pointing to /forum/user/<displayname>, with
  onClick=stopPropagation so the card-level click handler doesn't
  also fire.

FAVICON:
- Hand-built neon pulse waveform — emerald→cyan→purple gradient on
  dark navy background, designed to read at 16×16 to 256×256.
- Multi-resolution favicon.ico (16/32/48/64) + SVG + PNGs at
  16/32/48/64/96/192/256 under /static/favicons/.
- layout.tsx icons block now references all sizes including the
  apple-touch sizes (192, 256).

No env / DB changes here — pure deploy.
2026-05-20 12:56:56 +00:00
Ryan Salazar
dc166dfb10 forum: fix API + UI column-name mismatch against bb.forum_threads
The schema has upvotes / downvotes / vote_score; API + UI were
querying/reading upvote_count / downvote_count (non-existent
columns). Threads existed in the DB but didn't render.

- /api/forum/threads route.ts:
  - 'top-voted' sort: order by vote_score (was upvote_count)
  - 'newest' default sort: forum_threads has no 'status' column,
    so order pinned-first via is_pinned, then last_reply_at desc
- UI ForumThread interface + components: rename
  upvote_count → upvotes, add downvotes + vote_score so the
  vote display in the thread cards and the admin moderation
  list pulls live data from the existing columns.

After this lands and the deploy completes, the 50 seeded
threads + the 9 ai-seeded new threads + 253 backfilled replies
already in the DB will all render at /forum.
2026-05-20 08:34:05 +00:00
Ryan Salazar
4e11438963 seo: open site to search engines + expand sitemap to full catalog
- layout.tsx: flip robots.index from false→true (and the matching
  googleBot block). Drop the manual <meta name="robots"
  content="index, follow"> in the JSX head — it was duplicating
  (and conflicting with) the metadata-driven tag, so Google was
  seeing both "index, follow" AND "noindex, nofollow" and taking
  the more restrictive one. Now the metadata block is the single
  source of truth.
- sitemap.ts: expand from 5,000 articles to up to 45,000 (catalog
  is ~26k; cap leaves room for static + authors + events under
  Google's 50k-per-file ceiling). Add /authors/[slug] entries
  pulled from bb.author_profiles, and /events/[slug] entries
  pulled from bb.events. Drop the duplicate /articles/[slug]
  emissions — /news/[slug] is the canonical news URL.
- legacy-source.ts: page through the supabase query in 1,000-row
  chunks via Range headers so the 25k cap actually returns the
  full catalog (the previous `.limit(25000)` was getting silently
  truncated at PostgREST's default 1,000 max_rows).

Phase B (Ollama rewriter) is live; the press-release scrubber +
AI featured-image jobs are running in background to clean the
catalog. User explicitly chose to open SEO now rather than wait
for those jobs to finish.
2026-05-20 07:17:59 +00:00
Ryan Salazar
a9823b1d8a ticker: replace LiveWire with dual-row neon ticker (news + events)
Row 1 (Live Wire, scrolls →): most recent imported + AI-rewritten
articles, neon-green label, monospaced [HH:MM] [SOURCE_CODE] prefix.
Row 2 (On Calendar, scrolls ←, slower): upcoming events from
bb.events (confirmed/tentative, start_date >= today), neon-cyan
label, [Mon DD] [SHORT_NAME] prefix.

Defensive: each query is in its own try/catch so a partial failure
renders an empty row rather than crashing the page — that was the
silent build crash that took down the prior DoubleTicker incarnation.
If BOTH queries return zero rows we render nothing instead of two
blank bars.

CSS tokens added to redesign-tokens.css:
- bb-marquee--reverse + bb-marquee--slow modifiers
- .bb-neon-bar / .bb-neon-label / .bb-neon-time / .bb-neon-code
  (scoped class names so this is the ONLY place on the site that
  uses the neon palette — rest of the site keeps dark-navy + blue)

User cancelled the wholesale neon redesign; only the dual-ticker
keeps the neon green/cyan styling.
2026-05-20 07:02:15 +00:00
Ryan Salazar
5c3432915a preview: drop neon redesign sample at /previews/neon.html 2026-05-20 06:57:01 +00:00
Ryan Salazar
80b307ba92 ai: implement ollama rewrite provider + ryan-salazar byline default
The press-release rewriter (ai_rewrite_jobs) was stuck because:
- BB_REWRITE_PROVIDER defaulted to "anthropic" in code, AND
- production env still had BB_REWRITE_PROVIDER=anthropic, AND
- ollamaProvider.rewrite() was a stub that threw "not implemented".
Every job since 2026-01-26 hit Anthropic 400 "credit balance too
low" — 12 failed_api in a row.

Changes:
- Implement ollamaProvider against /v1/chat/completions on the local
  Ollama (works against AI_001:11434 / llama3.1:70b). Same JSON
  contract as the Anthropic provider; gets prompt_tokens /
  completion_tokens out of Ollama's usage block.
- Flip the default provider from "anthropic" → "ollama" in
  getProvider(), so the rewriter works out of the box now that the
  Ollama implementation exists.
- Make the BB auto-story byline configurable via env. Default flips
  from a 12-name pen rotation to a single "Ryan Salazar" byline for
  Broadcast Beat (per editorial directive). AV Beat keeps its own
  pen pool. Set BB_AUTOSTORY_BYLINE=rotate to restore the old
  behaviour.

Production env still needs (set in Coolify dashboard):
  OLLAMA_ENDPOINT=http://10.10.0.66:11434
  OLLAMA_MODEL_DEFAULT=llama3.1:70b
  BB_REWRITE_PROVIDER=ollama
  BB_REWRITE_MODEL=llama3.1:70b
  (remove ANTHROPIC_API_KEY)
2026-05-20 06:42:22 +00:00
Ryan Salazar
f7c462891f authors: db-driven /authors/[slug] for all 160 contributors
Drops the 540-line hand-curated map and replaces it with a real
bb.author_profiles table + view (author_profiles_with_stats) that
computes first_post_date / total_articles live from
wp_imported_posts. The page now resolves any slug correctly:

- Slug looked up in author_profiles; if no row, the slug is
  titleized ("desert-moon-communications" → "Desert Moon
  Communications") and used as the wp_imported_posts.author_name
  filter, so articles + join date still resolve.
- "Joined ..." line is now the actual MIN(wp_published_at) for that
  author, formatted as Month YYYY. Fixes Desert Moon showing 2020
  when its first post was Nov 2013.
- Role line defaults to "Contributor"; staff/founder roles ride on
  the new role column. Ryan seeded with "Founder of Broadcast Beat"
  in the migration.
- About tab renders a contact card (address / phone / email /
  website) whenever any of those fields exist on the profile row,
  plus a profile-facts panel with location + member-since +
  articles published + role.
- Bio is from the profile row; when empty, page shows
  "No biography has been added yet for {name}" instead of falling
  back to another author's bio.

Bios + contact details for the remaining ~160 authors are populated
by a separate Ollama+web-fetch enrichment job (next commit).
2026-05-20 06:25:08 +00:00
Ryan Salazar
fa94e92af5 chore: purge all rocket.new references from source
- Replace img.rocket.new mock URLs in authors/[slug] + FeaturedBento
  with /assets/images/article-placeholder.svg (live DB data drives
  these pages now; the static maps are fallback only).
- Replace broadcastb5322.builtwithrocket.new with broadcastbeat.com
  in admin API routes, news detail, forum layout, and the 4 Supabase
  edge functions that build outbound email links.
- Swap rocket-hosted JSON-LD logo URL in root layout for the local
  /assets/images/logo.png we already ship.
- Drop img.rocket.new from the Next.js image-hosts allowlist;
  add supabase.onsethost.com so storage proxy URLs can render.

The DB has had zero rocket.new image refs for weeks; this finishes
the job in the codebase so no page on broadcastbeat.com can render
a rocket-hosted asset anymore.
2026-05-20 06:14:02 +00:00
Ryan Salazar
9eadda4b9e authors: real article list from DB by author_name + version bump
Authors page was rendering a hardcoded mock article list per slug,
with Ryan Salazar's slug falling through to the staff-reporter mock
list (847 fake articles). New behaviour:

  - GET /api/author/[slug] returns ALL real articles where
    bb.wp_imported_posts.author_name matches the canonical name(s)
    for that slug. Slug → names map:
      ryan-salazar     → ["Ryan Salazar"]
      james-whitfield  → ["James Whitfield"]
      sarah-chen       → ["Sarah Chen"]
      … etc.
    Unknown slugs fall back to a Title-Cased guess.
    Response: { total, articles[] }, cached 5min / SWR 10min.

  - /authors/[slug] now fetches that endpoint on mount and replaces
    the hardcoded list with the live one. Article count shown on the
    profile header + sidebar is now the live total (not the
    hardcoded 1200/847/etc).

  - Version badge in the status bar bumped v4.2.1 → v2.0.0 to match
    the actual public release.

Ryan Salazar's profile now correctly shows his 135 real bylines from
WordPress instead of the staff-reporter mock 847.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 06:04:30 +00:00
Ryan Salazar
6e9c1b59bd authors: add ryan-salazar profile with real bio; fix staff-reporter joinedDate
/authors/ryan-salazar was falling through to the generic
  'staff-reporter' entry — which is why the page rendered as
  "Broadcast Beat / Broadcast Beat Editorial Team / Joined January
  2020". Add a proper ryan-salazar entry to the authors map:

    name        Ryan Salazar
    title       Founder of Broadcast Beat
    joinedDate  December 2013
    bio         (~150 words, sourced from public bios on theorg.com,
                 JVC casebook, AV Beat, Broadcast Beat Q&A — covers
                 ProComm 1997 → SunSpots CTO → AV Beat 2005 → SMPTE
                 LIVE 2014 → Broadcast Beat 2013 → Broadcast Beat
                 Studios 2020 → NAB Show LIVE executive producer.)

  Also corrected the staff-reporter joinedDate to "December 2013"
  (matches actual founding date of the publication).

  Separately: bb.user_profiles.role for Ryan was 'administrator' and
  has been updated to 'founder' so any role-based displays show the
  right title.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 06:01:55 +00:00
Ryan Salazar
cb00f7f343 search: return all matches (not just 100) + add typeahead autocomplete
- searchLegacyArticles() default limit: 50 → 5000. /search page
    explicitly passes 5000 too. ilike on title/excerpt/author is fast
    enough on the 29k-row archive that returning everything is fine.

  - New API route /api/search/suggest?q=...&limit=8 returns the top
    matching article titles + category + slug, with a 30-second CDN
    cache + 60-second stale-while-revalidate.

  - The search input in the Browse bar now debounces (150ms) and
    fires that API per keystroke once 2+ chars are typed. Matches
    appear in a styled dropdown below the input — click to navigate
    directly to /news/<slug>, or press Enter to fall through to the
    full /search page. "See all results for …" link in the dropdown
    footer.

  - Keyboard: Escape closes the dropdown; aria-autocomplete + role
    listbox/option for accessibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 06:00:10 +00:00
Ryan Salazar
e8552794fe categories: update SECTION_CATEGORIES map to match the AI-derived taxonomy
After the AI re-categorization, bb.wp_imported_posts.category holds the
new taxonomy: 'Industry News', 'Show Coverage', 'Cameras & Capture',
'Streaming & IP', 'Audio', 'Live Production', 'Cloud & Workflow',
'Post Production', 'Broadcast Production', 'AI & Automation'.

The map that drives /gear, /technology, /show-coverage, /news page
queries still referenced the OLD taxonomy ('Gear', 'Reviews',
'Technology', 'Streaming') — so /gear and /technology effectively
returned nothing-matched and fell through to the catch-all section,
causing the duplicate-posts-across-sections behaviour the user
reported.

  /gear         → Cameras & Capture, Audio
  /technology   → Streaming & IP, Cloud & Workflow, AI & Automation,
                  Live Production, Post Production, Broadcast Production
  /show-coverage→ Show Coverage
  /news         → Industry News

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:55:43 +00:00
Ryan Salazar
81913320cb align: unify container width to max-w-container across all pages; fix banner edge alignment
The Featured-story card's right edge wasn't lining up with any of the
ad banners below or beside it on the homepage. Two root causes:

  1. Pages I redesigned (/about, /about/contact, /about/team,
     /about/advertise, /show-coverage/[slug]) used `max-w-6xl px-6`
     (1152px / 24px-pad). Every other page uses `max-w-container
     px-4` (1200px / 16px-pad). Pages didn't line up with the
     header leaderboard or with each other.

  2. ArticleFeed used a flexible 12-col grid where lg:col-span-4
     ends up ~280px wide (depending on container) — so the 300x250
     image inside the sidebar didn't extend to the column's right
     edge, leaving a visual gap relative to the full-width Featured
     card above.

Fix:
  - All redesigned pages now use `max-w-container mx-auto px-4`.
  - ArticleFeed switches to `lg:grid-cols-[1fr,300px]` so the sidebar
    column is *exactly* 300px wide. The 300x250 ads fill that column
    flush, so their right edge meets the container's right padding
    edge — same x-position as the Featured card's right edge above.

Also linkified the "Relevant Media Properties" copyright string in
Footer.tsx → opens https://www.relevantmediaproperties.com/ in a new
tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:55:10 +00:00
Ryan Salazar
a1829e7628 ads + i18n: forum/newsletter banners; smarter language autodetect
Forum:
  - /forum landing: switch to 2-col layout, add SidebarAdStack
    (300x600 + every 300x250) on the right.
  - /forum/thread/[id]: insert one 300x250 "Sponsored" tile after
    every 4 replies (idx 3, 7, 11 …). Trailing-ad guard so the last
    reply isn't followed by an ad with no replies after it. Cycles
    through the full ADS_300X250 pool by index so every banner gets
    impressions across long threads.

Newsletter:
  - /newsletter/archive: drop SidebarAdStack into the existing
    right rail above the signup CTA + topic browser.

LanguageSwitcher autodetect (US default for US users was failing):
  1. Timezone override: if the user is in America/*, Pacific/Honolulu,
     Pacific/Auckland|Wellington, Australia/*, Europe/London|Dublin,
     or Atlantic/Reykjavik — always return 'en'. Fixes US-based
     users whose OS locale happened to be set to a non-English value.
  2. Otherwise walk navigator.languages (the full preference list,
     not just navigator.language[0]) and pick the first base code we
     support (en/es/pt/fr/de/zh/ja/ar/hi).
  3. Fallback to 'en'.

  All non-en selections still route through Google Translate's hosted
  proxy on the .translate.goog subdomain — search engines and US
  visitors continue to only ever see English on broadcastbeat.com.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:52:00 +00:00
Ryan Salazar
b4eb56c81d news: SidebarAdStack in /news sidebar (300x600 + every 300x250); 'Industry News' title 2026-05-20 05:45:20 +00:00
Ryan Salazar
4e867e2819 branding: 'Latest Broadcast News' → 'Industry News'; 'Broadcast Technology' → 'Production Technology' (page + footer + metadata) 2026-05-20 05:43:58 +00:00
Ryan Salazar
d2d941c576 about/show-coverage: redesign with sidebar (300x600 + rotating 300x250s)
Switch the bare single-column layouts on these pages to a two-column
grid that pulls in <SidebarAdStack /> on the right (pinned 300x600 +
every 300x250 rotated). Header keeps the leaderboard above. The
in-content 728x90 banners added earlier are removed since the sidebar
now carries the ad load (avoids stacking two of the same unit on the
page).

Pages updated:
  - /show-coverage/[slug]   event detail
  - /about
  - /about/contact
  - /about/team
  - /about/advertise (also gets a real-banner grid in "Available units":
    the actual 728x90, 300x600, and 300x250 sample creatives are
    rendered next to each unit's description, so advertisers see what
    the inventory looks like in production rather than just a label).

The /about pages also got a small refresh: serif H1 + accent-blue
"Broadcast Beat" eyebrow line + bordered content blocks, bringing the
look closer to the cinematic homepage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:43:02 +00:00
Ryan Salazar
4e1e5c22e8 header: align EventsDropdown + AboutDropdown buttons with new Browse bar height (fix alignment) 2026-05-20 05:40:22 +00:00
Ryan Salazar
c5d4c1d075 header/footer chrome polish: logo in footer, search in NAV bar, NAV bar restyle
- Footer bottom: replace the plain "Broadcast Beat" text wordmark
    with the same logo image used in the top-left header (so footer
    matches header brand mark).

  - Move the site-wide search input out of the top sticky row and
    into the Browse bar that sits below the 728x90 leaderboard. Top
    row is now just logo / reading-list / notification bell.

  - Browse bar redesign for personality + alignment:
      * 48px tall, dark navy gradient with a 2px accent-blue bottom
        border and a horizontal sheen across the top edge
      * Search input (pill shape, focus-glow ring) sits flush left
      * Menu items (Events ▾, Newsletter, Forum, About ▾) are now
        right-aligned via flex justify-between
      * Each menu item is uppercase + tracked, hover lights an
        accent-blue underline that scales in from the left
      * EventsDropdown and AboutDropdown buttons now use the same
        .bb-browse-item class — fixes the vertical alignment issue
        where they were rendering at the old 60px nav-link-bb height
        while the new bar is 48px.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:40:04 +00:00
Ryan Salazar
3bb27e2e0a ads/chrome: banners on /search and /reading-list; wrap /contributor with site chrome
- /search and /reading-list already had Header + Footer; add a
    single in-content 728x90 banner at the bottom of each main column
    (gated on ADS_728X90.length > 0, hidden on mobile).

  - /contributor was a bare dashboard with sidebar + main, no site
    chrome at all. Wrap it in the standard Header + Footer; drop the
    inner sidebar/main wrapper from min-h-screen to a natural-height
    flex so the dashboard sits below the header instead of competing
    with it for full viewport. Header brings the site-wide 728x90
    leaderboard with it, so contributors see the same ad inventory
    as the public site without a second in-content banner cluttering
    the dashboard panes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:07:49 +00:00
Ryan Salazar
a1d5ecf51d ads: add in-content 728x90 banner to gear, technology, forum, authors
These four landing pages already had the site-wide header leaderboard
via <Header />, but no additional in-content banner. Add one 728x90
slot at the bottom of the main column on each, gated on
ADS_728X90.length > 0 and hidden on mobile to match the header
banner's display rules.

  - /gear            — category landing
  - /technology      — category landing
  - /forum           — community landing
  - /authors/[slug]  — author profile

Brings these pages in line with /show-coverage and /about/* which got
the same treatment earlier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 04:15:45 +00:00
Ryan Salazar
1b0b541c40 about: add Header/Footer to /about/contact + /about/advertise; banners on all three
/about/contact and /about/advertise were rendering bare <main> tags
with no site chrome at all — no header, no footer, no banners. Both
are now wrapped with the standard
  <div className="min-h-screen bg-background"><Header />…<Footer /></div>
shell that the rest of the site uses, which auto-brings the site-wide
728x90 leaderboard with it.

/about/team already had Header+Footer but no in-content banner.

In addition to the header leaderboard, each of the three pages now
also renders one in-content 728x90 banner (gated on
ADS_728X90.length > 0, hidden on mobile to match the header banner's
display rules). This brings the about/* pages in line with the ad
inventory of /show-coverage and the other category landing pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 03:59:37 +00:00
Ryan Salazar
751eca2f16 show-coverage: add site Header + Footer + in-content 728x90 banner
Both /show-coverage and /show-coverage/[slug] were rendering bare
<main> elements with no site chrome — no top nav, no leaderboard ad
above the page, no footer, no in-content banners.

- Wrap each page in <div className="min-h-screen bg-background">
  <Header />…<Footer /></div>. <Header /> already brings the site-wide
  728x90 leaderboard with it (see Header.tsx footer of the component)
  so that banner appears automatically above the page content.
- Add one in-content 728x90 banner per page (between Upcoming/Past on
  the index, and between event header / Coverage list on the detail
  page) so the show-coverage pages carry the same ad inventory as
  other sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 03:56:23 +00:00
Ryan Salazar
7f0675a0e3 header: restore bb-browse-bar styles after DoubleTicker revert
The DoubleTicker commit (a52f8ad, since reverted) had bundled the
.bb-browse-bar / .bb-browse-row / .bb-browse-item CSS rules together
with the ticker CSS. Reverting the ticker removed the Browse bar
styles too, but Header.tsx still uses those classes for the
below-the-leaderboard nav. Re-add just the Browse bar selectors so
that nav renders correctly post-revert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 03:54:12 +00:00
Ryan Salazar
b1378de6d6 Revert "home: replace single LiveWire ticker with stacked dual ticker"
This reverts commit a52f8adb21.
2026-05-20 03:53:31 +00:00