diff --git a/services/frontend/src/app/(dashboard)/channels/view.tsx b/services/frontend/src/app/(dashboard)/channels/view.tsx
index 26c5cb68..580d3c07 100644
--- a/services/frontend/src/app/(dashboard)/channels/view.tsx
+++ b/services/frontend/src/app/(dashboard)/channels/view.tsx
@@ -23,7 +23,7 @@ export function ChannelsView({
CHANNEL ROSTER · SIGNAL_MAP
-
+
NODES:
{cultures?.length ?? 0}
diff --git a/services/frontend/src/app/(dashboard)/messages/view.tsx b/services/frontend/src/app/(dashboard)/messages/view.tsx
index ed1574f8..f0e75cdb 100644
--- a/services/frontend/src/app/(dashboard)/messages/view.tsx
+++ b/services/frontend/src/app/(dashboard)/messages/view.tsx
@@ -399,7 +399,7 @@ export function MessagesView({
diff --git a/services/frontend/src/app/(dashboard)/recordings/view.tsx b/services/frontend/src/app/(dashboard)/recordings/view.tsx
index f2215d3e..aa0286f6 100644
--- a/services/frontend/src/app/(dashboard)/recordings/view.tsx
+++ b/services/frontend/src/app/(dashboard)/recordings/view.tsx
@@ -202,7 +202,7 @@ export function RecordingsView({
href={r.download_url}
target="_blank"
rel="noreferrer"
- className="inline-flex items-center gap-1 rounded-[5px] border border-hairline bg-surface-2 px-2 py-1 font-mono text-[10px] text-ink-soft transition-colors hover:bg-surface hover:text-ink"
+ className="inline-flex items-center gap-1 rounded-md border border-hairline bg-surface-2 px-2 py-1 font-mono text-[10px] text-ink-soft transition-colors hover:bg-surface hover:text-ink"
>
RAW
@@ -211,7 +211,7 @@ export function RecordingsView({
type="button"
onClick={() => onDelete(r.id)}
disabled={del.isPending}
- className="inline-flex items-center gap-1 rounded-[5px] border border-vermilion/30 bg-vermilion/10 px-2 py-1 font-mono text-[10px] text-vermilion transition-colors hover:bg-vermilion/20"
+ className="inline-flex items-center gap-1 rounded-md border border-vermilion/30 bg-vermilion/10 px-2 py-1 font-mono text-[10px] text-vermilion transition-colors hover:bg-vermilion/20"
>
PURGE
diff --git a/services/frontend/src/app/globals.css b/services/frontend/src/app/globals.css
index c72e1ae3..44af3732 100644
--- a/services/frontend/src/app/globals.css
+++ b/services/frontend/src/app/globals.css
@@ -166,14 +166,7 @@
border-color: var(--panel-border-hover);
}
- .glass-2 {
- background: var(--panel-bg-subtle);
- border: 1px solid var(--panel-border);
- border-radius: var(--radius-r);
- }
-
/* HUD Tactical Card */
- .linear-card,
.hud-card {
position: relative;
background: var(--panel-bg);
@@ -184,7 +177,6 @@
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
- .linear-card:hover,
.hud-card:hover {
border-color: var(--panel-border-hover);
transform: translateY(-1px);
@@ -263,14 +255,105 @@
100% { transform: scale(2.2); opacity: 0; }
}
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+@keyframes eq {
+ 0%, 100% { transform: scaleY(0.35); }
+ 50% { transform: scaleY(1); }
+}
+
+@keyframes fade-up {
+ from { opacity: 0; transform: translateY(6px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes toast-in {
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+}
+
+@keyframes scan {
+ 0% { transform: translateY(-100%); }
+ 100% { transform: translateY(100%); }
+}
+
.animate-breathe { animation: breathe 2.5s ease-in-out infinite; }
.animate-spin-disc { animation: spin-disc 10s linear infinite; }
.animate-pulse-ring { animation: pulse-ring 2s cubic-bezier(0.16, 1, 0.3, 1) infinite; }
+/* Skeleton shimmer — theme-aware base + moving highlight sheen. */
+.animate-shimmer {
+ background-color: var(--panel-bg-subtle);
+ background-image: linear-gradient(
+ 105deg,
+ transparent 40%,
+ color-mix(in oklab, var(--text-primary) 6%, transparent) 50%,
+ transparent 60%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.6s ease-in-out infinite;
+}
+
+/* Live equalizer bars (mini-player / recording player). */
+.animate-eq {
+ transform-origin: bottom;
+ animation: eq 0.9s ease-in-out infinite;
+}
+
+/* Entrance for popovers, toasts, mini-player. */
+.animate-fade-up { animation: fade-up 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
+.animate-toast-in { animation: toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
+
+/* Sweeping glow line across a hero/placeholder panel. */
+.scan-line {
+ position: absolute;
+ inset: 0;
+ height: 100%;
+ pointer-events: none;
+ background: linear-gradient(
+ 180deg,
+ transparent,
+ var(--accent-signal-glow),
+ transparent
+ );
+ opacity: 0.5;
+ animation: scan 2.6s ease-in-out infinite;
+}
+
+/* Mobile nav hover/active sweep fill. */
+.game-sweep {
+ position: absolute;
+ inset: 0;
+ overflow: hidden;
+ border-radius: inherit;
+ pointer-events: none;
+}
+
+.game-sweep i {
+ position: absolute;
+ inset: 0;
+ background: var(--accent-signal-bg);
+ transform: translateY(100%);
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.game-nav-item.is-active .game-sweep i,
+.game-nav-item:hover .game-sweep i {
+ transform: translateY(0);
+}
+
@media (prefers-reduced-motion: reduce) {
.animate-breathe,
.animate-spin-disc,
- .animate-pulse-ring {
+ .animate-pulse-ring,
+ .animate-shimmer,
+ .animate-eq,
+ .animate-fade-up,
+ .animate-toast-in,
+ .scan-line {
animation: none !important;
}
*,
diff --git a/services/frontend/src/components/CategoryDrilldown.tsx b/services/frontend/src/components/CategoryDrilldown.tsx
index 43a65277..3a775e50 100644
--- a/services/frontend/src/components/CategoryDrilldown.tsx
+++ b/services/frontend/src/components/CategoryDrilldown.tsx
@@ -71,7 +71,7 @@ export function CategoryDrilldown({
{c.name}
-
+
Math.max(m, h.total), 0);
const intensity = (v: number) => {
- if (max <= 0) return "bg-white/5";
+ if (max <= 0) return "bg-hairline";
const t = Math.max(0, Math.min(1, v / max));
- if (t < 0.25) return "bg-white/[0.06]";
+ if (t < 0.25) return "bg-surface-2";
if (t < 0.5) return "bg-signal/25";
if (t < 0.75) return "bg-signal/50";
return "bg-vermilion/60";
diff --git a/services/frontend/src/components/ScamDomains.tsx b/services/frontend/src/components/ScamDomains.tsx
index 6478e4b7..152824a5 100644
--- a/services/frontend/src/components/ScamDomains.tsx
+++ b/services/frontend/src/components/ScamDomains.tsx
@@ -48,9 +48,9 @@ export function ScamDomains({ domains }: { domains: FlaggedDomain[] }) {
{d.domain}
-
+
diff --git a/services/frontend/src/components/TopChannels.tsx b/services/frontend/src/components/TopChannels.tsx
index a61cf94d..21aa12e7 100644
--- a/services/frontend/src/components/TopChannels.tsx
+++ b/services/frontend/src/components/TopChannels.tsx
@@ -53,9 +53,9 @@ export function TopChannels({ channels }: { channels: FlaggedChannel[] }) {
{c.channel_name ?? c.channel_id}
-
+
diff --git a/services/frontend/src/components/TopicTrends.tsx b/services/frontend/src/components/TopicTrends.tsx
index 431630df..6f90dc0c 100644
--- a/services/frontend/src/components/TopicTrends.tsx
+++ b/services/frontend/src/components/TopicTrends.tsx
@@ -29,7 +29,7 @@ function BarRow({
return (
{label}
-
+
(
))}
diff --git a/services/frontend/src/components/chatbot/chatbot.tsx b/services/frontend/src/components/chatbot/chatbot.tsx
index 3b32e373..935f02c8 100644
--- a/services/frontend/src/components/chatbot/chatbot.tsx
+++ b/services/frontend/src/components/chatbot/chatbot.tsx
@@ -169,7 +169,7 @@ export function Chatbot() {
@@ -156,11 +156,11 @@ export function CommandPalette() {
key={c.id}
className={`flex w-full items-center gap-3 rounded-[10px] px-3 py-2.5 text-left text-sm transition-colors ${
i === active
- ? "bg-signal/12 text-ink"
- : "text-ink-soft hover:bg-white/5"
+ ? "bg-signal/15 text-ink"
+ : "text-ink-soft hover:bg-surface"
}`}
>
-
+
{c.icon}
{c.label}
diff --git a/services/frontend/src/components/media/mini-player.tsx b/services/frontend/src/components/media/mini-player.tsx
index 99ea8015..0f266237 100644
--- a/services/frontend/src/components/media/mini-player.tsx
+++ b/services/frontend/src/components/media/mini-player.tsx
@@ -63,7 +63,7 @@ export function MiniPlayer() {
className="flex min-w-0 flex-1 items-center gap-3"
aria-label="Open full media player"
>
-
+
{current.thumbnailUrl ? (
// biome-ignore lint/performance/noImgElement: external CDN thumbnails, next/image needs remote allowlist
skip.mutate()}
disabled={skip.isPending}
aria-label="Skip to next track"
- className="flex size-8 items-center justify-center rounded-full text-ink-soft transition-colors hover:bg-white/10 hover:text-signal active:scale-95"
+ className="flex size-8 items-center justify-center rounded-full text-ink-soft transition-colors hover:bg-surface hover:text-signal active:scale-95"
>
@@ -151,7 +151,7 @@ export function MiniPlayer() {
className={`hidden size-8 items-center justify-center rounded-full text-xs transition-colors sm:flex ${
media?.loop
? "bg-signal/15 text-signal"
- : "text-ink-faint hover:bg-white/10 hover:text-ink"
+ : "text-ink-faint hover:bg-surface hover:text-ink"
}`}
>
↻
diff --git a/services/frontend/src/components/primitives/badge.tsx b/services/frontend/src/components/primitives/badge.tsx
index 983719ff..758b9af6 100644
--- a/services/frontend/src/components/primitives/badge.tsx
+++ b/services/frontend/src/components/primitives/badge.tsx
@@ -39,7 +39,7 @@ export function Badge({
return (
@@ -39,7 +39,7 @@ export function Spinner({ className }: { className?: string }) {
return (
diff --git a/services/frontend/src/components/primitives/select.tsx b/services/frontend/src/components/primitives/select.tsx
index 965f60bc..0e0e9d1e 100644
--- a/services/frontend/src/components/primitives/select.tsx
+++ b/services/frontend/src/components/primitives/select.tsx
@@ -46,7 +46,7 @@ export function Select({
type="button"
onClick={() => setOpen((o) => !o)}
className={cn(
- "flex w-full items-center justify-between gap-2 rounded-[11px] border border-hairline bg-white/5 text-left text-ink transition-colors hover:border-signal/40",
+ "flex w-full items-center justify-between gap-2 rounded-[8px] border border-hairline bg-surface-2 text-left text-ink transition-colors hover:border-signal/40",
"focus:outline-none focus:border-signal/60",
size === "sm" ? "h-9 px-3 text-xs" : "h-10 px-3.5 text-sm",
)}
@@ -79,10 +79,10 @@ export function Select({
setOpen(false);
}}
className={cn(
- "flex w-full items-center justify-between gap-2 rounded-[9px] px-3 py-2 text-left text-sm transition-colors",
+ "flex w-full items-center justify-between gap-2 rounded-[8px] px-3 py-2 text-left text-sm transition-colors",
o.value === value
? "bg-signal/15 text-signal"
- : "text-ink hover:bg-white/6",
+ : "text-ink hover:bg-surface",
)}
>
{o.label}
diff --git a/services/frontend/src/components/primitives/skeleton.tsx b/services/frontend/src/components/primitives/skeleton.tsx
index 9534851d..830da44c 100644
--- a/services/frontend/src/components/primitives/skeleton.tsx
+++ b/services/frontend/src/components/primitives/skeleton.tsx
@@ -7,7 +7,7 @@ export function Skeleton({
return (
x.id !== t.id);
emit();
}}
- className="rounded-md p-1 text-ink-faint transition-colors hover:bg-white/8 hover:text-ink"
+ className="rounded-md p-1 text-ink-faint transition-colors hover:bg-surface hover:text-ink"
>
diff --git a/services/frontend/src/components/shared/markdown.tsx b/services/frontend/src/components/shared/markdown.tsx
index 772181c7..9b0eaa2c 100644
--- a/services/frontend/src/components/shared/markdown.tsx
+++ b/services/frontend/src/components/shared/markdown.tsx
@@ -33,7 +33,7 @@ function renderInline(text: string, keyBase: string): ReactNode[] {
nodes.push(
{m[4]}
,
@@ -56,7 +56,7 @@ function renderBlock(content: string, keyBase: string): ReactNode[] {
return (
{code.replace(/\n$/, "")}
diff --git a/services/frontend/src/components/shared/states.tsx b/services/frontend/src/components/shared/states.tsx
index 520004fc..53345a56 100644
--- a/services/frontend/src/components/shared/states.tsx
+++ b/services/frontend/src/components/shared/states.tsx
@@ -62,7 +62,7 @@ export function SkeletonRows({
{Array.from({ length: rows }).map((_, i) => (
@@ -111,7 +111,7 @@ export function EmptyState({
)}
>
{icon && (
-
+
{icon}
)}
diff --git a/services/frontend/src/components/shell/mobile-nav.tsx b/services/frontend/src/components/shell/mobile-nav.tsx
index 70a5b66b..24348f9a 100644
--- a/services/frontend/src/components/shell/mobile-nav.tsx
+++ b/services/frontend/src/components/shell/mobile-nav.tsx
@@ -26,14 +26,14 @@ export function MobileNav() {
className={cn(
"game-nav-item relative flex min-h-[44px] flex-1 flex-col items-center justify-center rounded-t-[20px] px-2 pt-2 pb-[calc(0.4rem+env(safe-area-inset-bottom))] md:hidden",
active
- ? "is-active text-white"
+ ? "is-active text-signal"
: "text-ink-faint hover:text-ink-soft",
)}
>
{active && (
)}
diff --git a/services/frontend/src/components/shell/nav-rail.tsx b/services/frontend/src/components/shell/nav-rail.tsx
index 75036164..7d4099fa 100644
--- a/services/frontend/src/components/shell/nav-rail.tsx
+++ b/services/frontend/src/components/shell/nav-rail.tsx
@@ -28,7 +28,7 @@ function NavItem({
aria-label={label}
aria-current={active ? "page" : undefined}
className={cn(
- "nav-dock-item group relative flex size-9 items-center justify-center rounded-[7px] transition-all duration-150",
+ "nav-dock-item group relative flex size-9 items-center justify-center rounded-[8px] transition-all duration-150",
active
? "is-active bg-signal/15 text-signal font-semibold shadow-xs border border-signal/30"
: "text-ink-muted hover:bg-surface-2 hover:text-ink",
@@ -40,7 +40,7 @@ function NavItem({
)}
{/* Tooltip on hover */}
-
+
{label}