Files
avbeat-com/src/components/FeaturedCarousel.tsx
Ryan Salazar (via Claude) 52fe7bceb6 slice 4: accent palette refinement — blue → teal
Replace the four accent-family hex codes site-wide with the teal palette
(keeps the dark theme; only swaps the accent family, not the dark base):

  #3b82f6 (accent primary CTA)   → #5B7C8D  (teal)        [839×]
  #2563eb (accent-dark / hover)  → #4A6473  (darker teal) [44×]
  #60a5fa (info link, lighter)   → #8FB0C3  (mid teal)    [68×]
  #1e3a5f (accent-muted bg)      → #2F4F5F  (navy)        [44×]

tailwind.config.js tokens updated to match (accent/accent-dark/accent-muted).
Sweep also covers tailwind.css. 108 files touched. The dark base (#0d0d0d,
#111, #161616, #1a1a1a etc.) is intentionally untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 23:33:04 +00:00

176 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
interface Slide {
id: string;
slug: string;
title: string;
excerpt: string;
image: string | null;
category: string;
author: string;
author_initials: string;
author_beat: string | null;
date: string;
read_min: number;
ago_min: number;
neural_summary: NeuralSummary | null;
}
interface NeuralSummary {
bullets?: string[];
confidence?: number;
entities?: string[];
}
export default function FeaturedCarousel({ slides }: { slides: Slide[] }) {
const [i, setI] = useState(0);
const [paused, setPaused] = useState(false);
useEffect(() => {
if (slides.length < 2 || paused) return;
const t = setInterval(() => setI((v) => (v + 1) % slides.length), 4000);
return () => clearInterval(t);
}, [slides.length, paused]);
if (slides.length === 0) return null;
const s = slides[i];
return (
<section
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
className="max-w-container mx-auto px-4 my-8"
aria-label="Featured editorial"
>
<div className="flex items-center justify-between mb-3">
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280]">
Featured · staff editorial
</h2>
<span className="inline-flex items-center gap-1.5 text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-info,#8FB0C3)]">
<span className="bb-pulse-dot" /> auto · 4s
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 rounded border border-[#252525] bg-[#0b0f17] overflow-hidden">
<div className="relative aspect-[16/10] bg-[#111]">
{s.image ? (
<img src={s.image} alt={s.title} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-[#6b7280] font-mono text-xs">
no image
</div>
)}
<div className="absolute top-2 right-2 flex gap-1 text-[10px] font-mono">
<span className="bg-black/60 text-[var(--color-text-info,#8FB0C3)] px-1.5 py-0.5 rounded">
[ID {s.id.slice(0, 4).toUpperCase()}]
</span>
<span className="bg-black/60 text-white px-1.5 py-0.5 rounded uppercase">
[{s.category.replace(/\s+/g, "-")}]
</span>
</div>
</div>
<div className="p-5">
<div className="flex items-center gap-2 text-[10px] font-mono text-[#6b7280] mb-2">
<span>{s.category}</span>
<span>·</span>
<span>{s.ago_min}min ago</span>
<span>·</span>
<span>{s.read_min}min read</span>
</div>
<Link href={`/news/${s.slug}`} className="block group">
<h3 className="font-serif text-2xl leading-snug text-[#e5e7eb] group-hover:text-[var(--color-text-info,#8FB0C3)]">
{s.title}
</h3>
</Link>
<p className="font-serif text-[15px] text-[#b0b0b0] mt-3 line-clamp-3">
{s.excerpt}
</p>
{s.neural_summary && Array.isArray(s.neural_summary.bullets) && s.neural_summary.bullets.length > 0 && (
<div className="relative mt-4 p-3 border border-[#252525] rounded">
<span aria-hidden className="absolute top-1 left-1 w-2.5 h-2.5 border-t border-l border-[var(--color-text-info,#8FB0C3)]" />
<span aria-hidden className="absolute bottom-1 right-1 w-2.5 h-2.5 border-b border-r border-[var(--color-text-info,#8FB0C3)]" />
<div className="flex items-center justify-between mb-2">
<span className="text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-info,#8FB0C3)]">
Neural summary
</span>
{typeof s.neural_summary.confidence === "number" && (
<span className="text-[10px] font-mono text-[#6b7280]">
conf {(s.neural_summary.confidence * 100).toFixed(0)}%
</span>
)}
</div>
<ul className="space-y-1 text-xs text-[#d1d5db]">
{s.neural_summary.bullets.slice(0, 3).map((b, idx) => (
<li key={idx} className="flex gap-2">
<span className="text-[var(--color-text-info,#8FB0C3)]">·</span>
<span>{b}</span>
</li>
))}
</ul>
{s.neural_summary.entities && s.neural_summary.entities.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{s.neural_summary.entities.slice(0, 5).map((e) => (
<span key={e} className="text-[10px] font-mono px-1.5 py-0.5 rounded bg-[#1a1f2e] text-[var(--color-text-info,#8FB0C3)]">
{e}
</span>
))}
</div>
)}
</div>
)}
<div className="mt-4 pt-3 border-t border-[#252525] flex items-center gap-2">
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-[#1a1f2e] text-[var(--color-text-info,#8FB0C3)] text-xs font-semibold">
{s.author_initials}
</span>
<div>
<div className="text-sm text-[#e5e7eb]">{s.author}</div>
{s.author_beat && (
<div className="text-[10px] font-mono text-[#6b7280] uppercase tracking-wider">{s.author_beat}</div>
)}
</div>
</div>
<div className="mt-4 flex items-center justify-between">
<div className="flex gap-1.5">
{slides.map((_, n) => (
<button
key={n}
onClick={() => setI(n)}
aria-label={`Slide ${n + 1}`}
className={`w-2 h-2 rounded-full transition-colors ${
n === i ? "bg-[var(--color-text-info,#8FB0C3)]" : "bg-[#374151]"
}`}
/>
))}
</div>
<div className="flex items-center gap-3 text-xs font-mono text-[#6b7280]">
<button
onClick={() => setI((v) => (v - 1 + slides.length) % slides.length)}
aria-label="Previous"
className="hover:text-[var(--color-text-info,#8FB0C3)]"
>
</button>
<span>{String(i + 1).padStart(2, "0")} / {String(slides.length).padStart(2, "0")}</span>
<button
onClick={() => setI((v) => (v + 1) % slides.length)}
aria-label="Next"
className="hover:text-[var(--color-text-info,#8FB0C3)]"
>
</button>
</div>
</div>
</div>
</div>
</section>
);
}