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>
This commit is contained in:
46
src/app/api/ticker/beat-news/route.ts
Normal file
46
src/app/api/ticker/beat-news/route.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
export const runtime = 'nodejs';
|
||||||
|
export const revalidate = 300; // 5 minutes
|
||||||
|
|
||||||
|
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||||
|
const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const sb = createClient(SUPABASE_URL, SUPABASE_ANON, {
|
||||||
|
db: { schema: 'bb' },
|
||||||
|
auth: { persistSession: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const cutoff = new Date(Date.now() - 7 * 86400000).toISOString();
|
||||||
|
|
||||||
|
const { data, error } = await sb
|
||||||
|
.from('native_articles')
|
||||||
|
.select('id, slug, title, category, published_at')
|
||||||
|
.eq('status', 'published')
|
||||||
|
.gte('published_at', cutoff)
|
||||||
|
.order('published_at', { ascending: false })
|
||||||
|
.limit(30);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return NextResponse.json({ error: error.message, items: [] }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = (data || []).map((a) => ({
|
||||||
|
id: a.id,
|
||||||
|
href: `/articles/${a.slug}`,
|
||||||
|
title: a.title,
|
||||||
|
category: a.category || null,
|
||||||
|
published_at: a.published_at,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ items },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
90
src/app/api/ticker/forum-posts/route.ts
Normal file
90
src/app/api/ticker/forum-posts/route.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
export const runtime = 'nodejs';
|
||||||
|
export const revalidate = 300; // 5 minutes
|
||||||
|
|
||||||
|
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||||
|
const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||||
|
|
||||||
|
interface TickerItem {
|
||||||
|
id: string;
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
kind: 'thread' | 'reply';
|
||||||
|
author_name: string | null;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const sb = createClient(SUPABASE_URL, SUPABASE_ANON, {
|
||||||
|
db: { schema: 'bb' },
|
||||||
|
auth: { persistSession: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Newest threads + newest replies, interleaved by timestamp.
|
||||||
|
const [threadsRes, repliesRes] = await Promise.all([
|
||||||
|
sb
|
||||||
|
.from('forum_threads')
|
||||||
|
.select('id, title, author_name, created_at')
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(20),
|
||||||
|
sb
|
||||||
|
.from('forum_replies')
|
||||||
|
.select('id, body, thread_id, author_name, created_at, forum_threads(title)')
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(20),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (threadsRes.error && repliesRes.error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: threadsRes.error?.message || repliesRes.error?.message, items: [] },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: TickerItem[] = [];
|
||||||
|
|
||||||
|
for (const t of threadsRes.data || []) {
|
||||||
|
items.push({
|
||||||
|
id: t.id,
|
||||||
|
href: `/forum/thread/${t.id}`,
|
||||||
|
title: t.title,
|
||||||
|
kind: 'thread',
|
||||||
|
author_name: t.author_name,
|
||||||
|
created_at: t.created_at,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const r of (repliesRes.data || []) as Array<{
|
||||||
|
id: string;
|
||||||
|
body: string;
|
||||||
|
thread_id: string;
|
||||||
|
author_name: string | null;
|
||||||
|
created_at: string;
|
||||||
|
forum_threads?: { title: string } | { title: string }[] | null;
|
||||||
|
}>) {
|
||||||
|
const parent =
|
||||||
|
Array.isArray(r.forum_threads) ? r.forum_threads[0] : r.forum_threads;
|
||||||
|
const parentTitle = parent?.title || 'forum reply';
|
||||||
|
items.push({
|
||||||
|
id: r.id,
|
||||||
|
href: `/forum/thread/${r.thread_id}`,
|
||||||
|
title: `Re: ${parentTitle}`,
|
||||||
|
kind: 'reply',
|
||||||
|
author_name: r.author_name,
|
||||||
|
created_at: r.created_at,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{ items: items.slice(0, 30) },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ function FeedSkeleton() {
|
|||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background bb-neon">
|
||||||
{/* Sticky top bar + nav */}
|
{/* Sticky top bar + nav */}
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
|
|||||||
228
src/components/DualSpeedTicker.tsx
Normal file
228
src/components/DualSpeedTicker.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
interface TickerItem {
|
||||||
|
id: string;
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
category?: string | null;
|
||||||
|
author_name?: string | null;
|
||||||
|
kind?: 'thread' | 'reply';
|
||||||
|
}
|
||||||
|
|
||||||
|
const REFRESH_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
async function fetchItems(url: string): Promise<TickerItem[]> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, { cache: 'no-store' });
|
||||||
|
if (!res.ok) return [];
|
||||||
|
const d = await res.json();
|
||||||
|
return Array.isArray(d.items) ? d.items : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row({
|
||||||
|
label,
|
||||||
|
labelColor,
|
||||||
|
glowColor,
|
||||||
|
durationSec,
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
labelColor: string;
|
||||||
|
glowColor: string;
|
||||||
|
durationSec: number;
|
||||||
|
items: TickerItem[];
|
||||||
|
}) {
|
||||||
|
// Duplicate the items so the strip loops seamlessly. Animation moves
|
||||||
|
// the inner track from 0 to -50% (the duplicated half), then snaps
|
||||||
|
// back to 0 — invisible thanks to the duplication.
|
||||||
|
const safe = items.length > 0 ? items : [{ id: 'empty', href: '#', title: '—' }];
|
||||||
|
const looped = [...safe, ...safe];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="bbt-row"
|
||||||
|
style={{
|
||||||
|
// height + bg/border baked into class; per-row dynamic values here
|
||||||
|
['--bbt-duration' as any]: `${durationSec}s`,
|
||||||
|
['--bbt-label-color' as any]: labelColor,
|
||||||
|
['--bbt-glow-color' as any]: glowColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="bbt-label" aria-hidden="true">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<div className="bbt-track-wrapper">
|
||||||
|
<div className="bbt-track">
|
||||||
|
{looped.map((item, idx) => (
|
||||||
|
<Link
|
||||||
|
key={`${item.id}-${idx}`}
|
||||||
|
href={item.href}
|
||||||
|
className="bbt-item"
|
||||||
|
prefetch={false}
|
||||||
|
>
|
||||||
|
{item.category && (
|
||||||
|
<span className="bbt-cat">{item.category}</span>
|
||||||
|
)}
|
||||||
|
<span className="bbt-title">{item.title}</span>
|
||||||
|
{item.author_name && (
|
||||||
|
<span className="bbt-meta">· {item.author_name}</span>
|
||||||
|
)}
|
||||||
|
<span className="bbt-sep" aria-hidden="true">•</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DualSpeedTicker() {
|
||||||
|
const [beat, setBeat] = useState<TickerItem[]>([]);
|
||||||
|
const [forum, setForum] = useState<TickerItem[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
const tick = async () => {
|
||||||
|
const [b, f] = await Promise.all([
|
||||||
|
fetchItems('/api/ticker/beat-news'),
|
||||||
|
fetchItems('/api/ticker/forum-posts'),
|
||||||
|
]);
|
||||||
|
if (!cancelled) {
|
||||||
|
setBeat(b);
|
||||||
|
setForum(f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
const handle = setInterval(tick, REFRESH_MS);
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
clearInterval(handle);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="bbt-root" aria-label="News + forum live ticker">
|
||||||
|
<Row
|
||||||
|
label="BEAT"
|
||||||
|
labelColor="#00ff9f"
|
||||||
|
glowColor="rgba(0,255,159,0.55)"
|
||||||
|
durationSec={17}
|
||||||
|
items={beat}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label="FORUM"
|
||||||
|
labelColor="#00d4ff"
|
||||||
|
glowColor="rgba(0,212,255,0.55)"
|
||||||
|
durationSec={40}
|
||||||
|
items={forum}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Scoped styles. Plain CSS keyframes = GPU-accelerated, no JS-frame
|
||||||
|
overhead, no flicker. */}
|
||||||
|
<style>{`
|
||||||
|
.bbt-root {
|
||||||
|
background: #000;
|
||||||
|
border-top: 1px solid rgba(0,255,159,0.18);
|
||||||
|
border-bottom: 1px solid rgba(0,212,255,0.18);
|
||||||
|
font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
.bbt-row {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
contain: layout paint;
|
||||||
|
}
|
||||||
|
.bbt-row + .bbt-row {
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
.bbt-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--bbt-label-color);
|
||||||
|
text-shadow: 0 0 6px var(--bbt-glow-color);
|
||||||
|
background: linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0.85) 100%);
|
||||||
|
border-right: 1px solid var(--bbt-label-color);
|
||||||
|
box-shadow: inset 0 0 12px var(--bbt-glow-color);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.bbt-track-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.bbt-track {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: bbt-scroll var(--bbt-duration) linear infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.bbt-row:hover .bbt-track {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
.bbt-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 0 14px;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #d1d5db;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 120ms ease;
|
||||||
|
}
|
||||||
|
.bbt-item:hover .bbt-title {
|
||||||
|
color: var(--bbt-label-color);
|
||||||
|
text-shadow: 0 0 6px var(--bbt-glow-color);
|
||||||
|
}
|
||||||
|
.bbt-cat {
|
||||||
|
font-size: 9px;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--bbt-label-color);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.bbt-title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.bbt-meta {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.bbt-sep {
|
||||||
|
color: var(--bbt-label-color);
|
||||||
|
opacity: 0.4;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
@keyframes bbt-scroll {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
100% { transform: translateX(-50%); }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.bbt-track { animation-duration: 0s; }
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.bbt-label { font-size: 9px; padding: 0 8px; }
|
||||||
|
.bbt-item { font-size: 11px; padding: 0 10px; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import LanguageSwitcher from "@/components/LanguageSwitcher";
|
|||||||
import AdImage from "@/components/AdImage";
|
import AdImage from "@/components/AdImage";
|
||||||
import EventsDropdown from "@/components/EventsDropdown";
|
import EventsDropdown from "@/components/EventsDropdown";
|
||||||
import AboutDropdown from "@/components/AboutDropdown";
|
import AboutDropdown from "@/components/AboutDropdown";
|
||||||
|
import DualSpeedTicker from "@/components/DualSpeedTicker";
|
||||||
import { ADS_728X90, shuffle } from "@/lib/ads";
|
import { ADS_728X90, shuffle } from "@/lib/ads";
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
@@ -644,6 +645,7 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<DualSpeedTicker />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1364,3 +1364,126 @@ h1, h2, h3 {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Neon homepage palette ─────────────────────────────────────────
|
||||||
|
Scoped to the homepage via `.bb-neon` on the root <div>. CSS-only
|
||||||
|
recolor: maps the existing blue accents (#3b82f6 / #60a5fa) to the
|
||||||
|
ticker's emerald + cyan, with subtle glows. No layout changes. */
|
||||||
|
.bb-neon {
|
||||||
|
--color-accent: #00ff9f;
|
||||||
|
--color-accent-dark: #00d4ff;
|
||||||
|
--color-accent-muted: rgba(0, 255, 159, 0.18);
|
||||||
|
--color-text-info: #00d4ff;
|
||||||
|
--color-text-info-strong: #00ff9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section headings — emerald with subtle glow */
|
||||||
|
.bb-neon h2,
|
||||||
|
.bb-neon h3.font-heading,
|
||||||
|
.bb-neon .font-heading.section-title {
|
||||||
|
color: #00ff9f;
|
||||||
|
text-shadow: 0 0 6px rgba(0, 255, 159, 0.35);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Existing blue text → cyan */
|
||||||
|
.bb-neon .text-\[\#3b82f6\] {
|
||||||
|
color: #00d4ff !important;
|
||||||
|
text-shadow: 0 0 4px rgba(0, 212, 255, 0.25);
|
||||||
|
}
|
||||||
|
.bb-neon .text-\[\#60a5fa\] {
|
||||||
|
color: #00d4ff !important;
|
||||||
|
}
|
||||||
|
.bb-neon a:not(.bbt-item):not(.bbt-label):hover {
|
||||||
|
color: #00d4ff;
|
||||||
|
text-shadow: 0 0 4px rgba(0, 212, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Borders previously blue → neon-tinted, subtle */
|
||||||
|
.bb-neon .border-\[\#3b82f6\],
|
||||||
|
.bb-neon .border-\[\#3b82f6\\/30\],
|
||||||
|
.bb-neon .border-\[\#1e3a5f\] {
|
||||||
|
border-color: rgba(0, 255, 159, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card border softening to neon */
|
||||||
|
.bb-neon .border-\[\#252525\],
|
||||||
|
.bb-neon .border-\[\#2a2a2a\] {
|
||||||
|
border-color: rgba(0, 255, 159, 0.14) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover state on cards / clickable wrappers — cyan border + faint glow */
|
||||||
|
.bb-neon .border-\[\#252525\]:hover,
|
||||||
|
.bb-neon .border-\[\#2a2a2a\]:hover,
|
||||||
|
.bb-neon a:hover > .border-\[\#252525\],
|
||||||
|
.bb-neon a:hover > .border-\[\#2a2a2a\] {
|
||||||
|
border-color: rgba(0, 212, 255, 0.55) !important;
|
||||||
|
box-shadow: 0 0 14px rgba(0, 212, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons previously blue → emerald→cyan gradient with black text */
|
||||||
|
.bb-neon .bg-\[\#3b82f6\],
|
||||||
|
.bb-neon button.bg-\[\#3b82f6\],
|
||||||
|
.bb-neon a.bg-\[\#3b82f6\] {
|
||||||
|
background: linear-gradient(90deg, #00ff9f 0%, #00d4ff 100%) !important;
|
||||||
|
color: #000 !important;
|
||||||
|
box-shadow: 0 0 14px rgba(0, 212, 255, 0.30);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.bb-neon .bg-\[\#3b82f6\]:hover,
|
||||||
|
.bb-neon .hover\:bg-\[\#2563eb\]:hover,
|
||||||
|
.bb-neon button.bg-\[\#3b82f6\]:hover,
|
||||||
|
.bb-neon a.bg-\[\#3b82f6\]:hover {
|
||||||
|
background: linear-gradient(90deg, #14ffb0 0%, #4ee0ff 100%) !important;
|
||||||
|
box-shadow: 0 0 22px rgba(0, 255, 159, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category pills / tags / "AI" badges — cyan */
|
||||||
|
.bb-neon .bg-\[\#3b82f6\]\/20 {
|
||||||
|
background: rgba(0, 212, 255, 0.12) !important;
|
||||||
|
border-color: rgba(0, 212, 255, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subscribe / newsletter accent boxes */
|
||||||
|
.bb-neon .bg-\[\#1a2535\],
|
||||||
|
.bb-neon .bg-\[\#0d1f35\] {
|
||||||
|
background-color: #050a0a !important;
|
||||||
|
border: 1px solid rgba(0, 255, 159, 0.25);
|
||||||
|
box-shadow: 0 0 18px rgba(0, 255, 159, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Featured / hero container — neon outline + soft inner glow */
|
||||||
|
.bb-neon .featured-bento,
|
||||||
|
.bb-neon [data-featured-bento],
|
||||||
|
.bb-neon section[aria-label*="Featured"],
|
||||||
|
.bb-neon section[aria-label*="featured"] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skeletons keep their dim look — don't recolor */
|
||||||
|
.bb-neon .skeleton {
|
||||||
|
background: #0d0d0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divider lines */
|
||||||
|
.bb-neon hr,
|
||||||
|
.bb-neon .h-px.bg-\[\#2a2a2a\],
|
||||||
|
.bb-neon .bg-\[\#222\] {
|
||||||
|
background-color: rgba(0, 255, 159, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body / paragraph copy: lean toward spec's #d1d5db where currently muted */
|
||||||
|
.bb-neon .text-\[\#e8e8e8\],
|
||||||
|
.bb-neon .text-\[\#e0e0e0\] {
|
||||||
|
color: #d1d5db !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus rings on interactive elements */
|
||||||
|
.bb-neon .focus\:ring-\[\#3b82f6\]:focus,
|
||||||
|
.bb-neon .focus-visible\:ring-\[\#3b82f6\]:focus-visible {
|
||||||
|
--tw-ring-color: rgba(0, 212, 255, 0.6) !important;
|
||||||
|
}
|
||||||
|
.bb-neon .focus\:border-\[\#3b82f6\]:focus,
|
||||||
|
.bb-neon .focus-visible\:border-\[\#3b82f6\]:focus-visible {
|
||||||
|
border-color: #00d4ff !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user