From af3c852a5bf93077e71e6e85a117fe63c3017cde Mon Sep 17 00:00:00 2001 From: asepharyana Date: Mon, 6 Jul 2026 00:58:41 +0700 Subject: [PATCH] Refactor code structure for improved readability and maintainability --- services/frontend/frontend/index.html | 2 +- .../messages/components/message_card.rs | 58 +- .../frontend/src/styles/animations.css | 543 +++ .../frontend/frontend/src/styles/bundle.css | 3120 +++++++++++++++++ .../frontend/src/styles/dashboard.css | 13 + .../frontend/frontend/src/styles/live.css | 16 + .../frontend/frontend/src/styles/messages.css | 125 + .../frontend/frontend/src/styles/polish.css | 51 +- .../frontend/frontend/src/styles/reset.css | 2 +- .../frontend/src/styles/utilities.css | 53 + services/frontend/shared-types/src/message.rs | 29 + 11 files changed, 3990 insertions(+), 22 deletions(-) create mode 100644 services/frontend/frontend/src/styles/animations.css create mode 100644 services/frontend/frontend/src/styles/bundle.css diff --git a/services/frontend/frontend/index.html b/services/frontend/frontend/index.html index a5d4072..bc04cd6 100644 --- a/services/frontend/frontend/index.html +++ b/services/frontend/frontend/index.html @@ -6,7 +6,7 @@ IMPHNEN -- Discord Moderation - + diff --git a/services/frontend/frontend/src/features/messages/components/message_card.rs b/services/frontend/frontend/src/features/messages/components/message_card.rs index d6a52e0..9a77dc3 100644 --- a/services/frontend/frontend/src/features/messages/components/message_card.rs +++ b/services/frontend/frontend/src/features/messages/components/message_card.rs @@ -1,5 +1,5 @@ use leptos::prelude::*; -use shared_types::message::{AiSeverity, AiStatus, AttachmentRef, MessageRecord}; +use shared_types::message::{AiSeverity, AiStatus, AttachmentRef, MessageRecord, ReferenceInfo}; use std::sync::Arc; use super::message_actions::ReanalyzeButton; @@ -79,6 +79,39 @@ pub fn MessageRow( .cloned() .unwrap_or_default(); + // Extract reply info before view! to avoid closure capture issues + let reply_el = message.is_reply.unwrap_or(false).then(|| { + message + .metadata + .as_ref() + .and_then(|m| m.reference.as_ref()) + .map(|ref_info| { + let r_user = ref_info.replied_username.as_deref().unwrap_or("unknown").to_string(); + let r_content = ref_info + .content + .as_deref() + .unwrap_or("") + .to_string(); + (r_user, r_content) + }) + }); + let reply_user = reply_el.as_ref().map(|r| r.as_ref().map(|(u, _)| u.clone())); + let reply_user = reply_user.flatten(); + let reply_content = reply_el.as_ref().map(|r| r.as_ref().map(|(_, c)| c.clone())); + let reply_content = reply_content.flatten(); + let reply_content_snippet = reply_content.as_ref().map(|c| { + if c.len() > 48 { + format!("{}…", &c[..48]) + } else { + c.clone() + } + }); + let reply_initial = reply_user + .as_ref() + .and_then(|u| u.chars().next()) + .map(|c| c.to_uppercase().to_string()) + .unwrap_or_else(|| "?".to_string()); + view! {
{/* Header */} @@ -104,6 +137,29 @@ pub fn MessageRow(
+ {/* Reply indicator — Discord-style reference block */} + {reply_user.as_ref().map(|r_user| { + let r_user_cloned = r_user.clone(); + let snippet = reply_content_snippet.clone(); + let initial = reply_initial.clone(); + view! { +
+
+
+ {initial} + "Replying to" + "@" {r_user_cloned} + {(!snippet.as_deref().unwrap_or("").is_empty()).then(|| { + let s = snippet.unwrap_or_default(); + view! { + {s} + } + })} +
+
+ } + })} + {/* Content */} {show.then(|| { let rendered = render_emojis(display); diff --git a/services/frontend/frontend/src/styles/animations.css b/services/frontend/frontend/src/styles/animations.css new file mode 100644 index 0000000..a33f36e --- /dev/null +++ b/services/frontend/frontend/src/styles/animations.css @@ -0,0 +1,543 @@ +/* ── Animations — Live Dashboard Effects ──────────────── * + * Keyframes, applied animations, and utility classes. * + * Respects prefers-reduced-motion. * + * ──────────────────────────────────────────────────────── */ + +/* ════════════════════════════════════════════════════════ + 1. KEYFRAMES + ════════════════════════════════════════════════════════ */ + +/* ── Entry / Reveal ─────────────────────────────────── */ +@keyframes slide-in-left { + from { opacity: 0; transform: translateX(-20px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes slide-in-down { + from { opacity: 0; transform: translateY(-12px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes scale-bounce { + 0% { opacity: 0; transform: scale(0.85); } + 60% { opacity: 1; transform: scale(1.04); } + 100% { opacity: 1; transform: scale(1); } +} + +@keyframes pop-in { + 0% { opacity: 0; transform: scale(0.8); } + 70% { opacity: 1; transform: scale(1.08); } + 100% { opacity: 1; transform: scale(1); } +} + +/* ── Status / Alive ─────────────────────────────────── */ +@keyframes breathe { + 0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 4px currentColor; } + 50% { opacity: 0.6; transform: scale(1.2); box-shadow: 0 0 14px currentColor; } +} + +@keyframes breathe-soft { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +@keyframes ring-pulse { + 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } + 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } +} + +@keyframes ring-pulse-success { + 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); } + 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } +} + +@keyframes ring-pulse-error { + 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); } + 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } +} + +@keyframes processing-sweep { + 0% { background-position: -200% center; } + 100% { background-position: 200% center; } +} + +@keyframes live-indicator { + 0%, 100% { opacity: 1; box-shadow: 0 0 6px rgba(239, 68, 68, 0.4); } + 50% { opacity: 0.7; box-shadow: 0 0 16px rgba(239, 68, 68, 0.7); } +} + +/* ── Ambient / Decorative ───────────────────────────── */ +/* Slow breathing for background glow layers */ +@keyframes ambient-glow { + 0%, 100% { opacity: 0.3; } + 50% { opacity: 0.7; } +} + +/* Dynamic mesh drift — shifts gradient center positions */ +@keyframes bg-mesh-drift { + 0% { background-position: 0% 0%, 100% 100%, 50% 50%; } + 25% { background-position: 30% 20%, 70% 80%, 20% 60%; } + 50% { background-position: 60% 10%, 40% 60%, 80% 30%; } + 75% { background-position: 20% 60%, 80% 20%, 40% 80%; } + 100% { background-position: 0% 0%, 100% 100%, 50% 50%; } +} + +/* Slow hue rotation for orb glow */ +@keyframes hue-rotate-slow { + 0% { filter: hue-rotate(0deg); } + 50% { filter: hue-rotate(30deg); } + 100% { filter: hue-rotate(0deg); } +} + +/* Orb size pulsing */ +@keyframes orb-pulse { + 0%, 100% { transform: scale(1); opacity: 0.4; } + 50% { transform: scale(1.08); opacity: 0.6; } +} + +/* Expanded orb drift — wider, slower, organic */ +@keyframes orb-drift-enhanced { + 0% { transform: translate(0, 0) scale(1); } + 20% { transform: translate(80px, -50px) scale(1.1); } + 40% { transform: translate(-50px, 70px) scale(0.9); } + 60% { transform: translate(100px, 30px) scale(1.05); } + 80% { transform: translate(-70px, -60px) scale(0.95); } + 100% { transform: translate(0, 0) scale(1); } +} + +/* Grain/noise texture shifting */ +@keyframes grain-shift { + 0%, 100% { transform: translate(0, 0) rotate(0deg); } + 10% { transform: translate(-5%, -5%) rotate(0.5deg); } + 20% { transform: translate(-10%, 0%) rotate(-0.5deg); } + 30% { transform: translate(0%, 5%) rotate(1deg); } + 40% { transform: translate(5%, -3%) rotate(-0.3deg); } + 50% { transform: translate(-3%, -8%) rotate(0.8deg); } + 60% { transform: translate(8%, 3%) rotate(-0.6deg); } + 70% { transform: translate(-6%, 6%) rotate(0.4deg); } + 80% { transform: translate(4%, -6%) rotate(-0.7deg); } + 90% { transform: translate(-2%, 2%) rotate(0.2deg); } +} + +@keyframes gradient-shimmer { + 0% { background-position: 0% center; } + 100% { background-position: 200% center; } +} + +@keyframes float-variation { + 0%, 100% { transform: translateY(0) rotate(0deg); } + 33% { transform: translateY(-8px) rotate(1.5deg); } + 66% { transform: translateY(-4px) rotate(-1deg); } +} + +@keyframes fade-in-down { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ── Interactive ────────────────────────────────────── */ +@keyframes glow-border { + 0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } + 50% { box-shadow: 0 0 12px 1px rgba(59, 130, 246, 0.15); } +} + +@keyframes ripple { + 0% { transform: scale(0); opacity: 0.6; } + 100% { transform: scale(4); opacity: 0; } +} + +/* ── Bar pulses (audio / visualizer) ────────────────── */ +@keyframes bar-pulse-1 { + 0%, 100% { transform: scaleY(1); } + 50% { transform: scaleY(0.4); } +} +@keyframes bar-pulse-2 { + 0%, 100% { transform: scaleY(0.5); } + 50% { transform: scaleY(1.2); } +} +@keyframes bar-pulse-3 { + 0%, 100% { transform: scaleY(0.7); } + 50% { transform: scaleY(1); } +} + +@keyframes count-up { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + + +/* ════════════════════════════════════════════════════════ + 2. UTILITY ANIMATION CLASSES + Use these in Rust components: class="animate-breathe" + ════════════════════════════════════════════════════════ */ + +.animate-breathe { + animation: breathe 3s ease-in-out infinite; +} + +.animate-breathe-soft { + animation: breathe-soft 2s ease-in-out infinite; +} + +.animate-ring-pulse { + animation: ring-pulse 2s ease-in-out infinite; +} + +.animate-live-indicator { + animation: live-indicator 1.5s ease-in-out infinite; +} + +.animate-float { + animation: float-variation 5s ease-in-out infinite; +} + +.animate-gradient-shimmer { + background-size: 200% 100%; + animation: gradient-shimmer 4s ease-in-out infinite alternate; +} + +.animate-spin-slow { + animation: spin 3s linear infinite; +} + +.animate-pulse-connecting { + animation: pulse-connecting 1s ease-in-out infinite; +} + +.animate-scale-bounce { + animation: scale-bounce 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +.animate-pop-in { + animation: pop-in 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +.animate-fade-in-down { + animation: fade-in-down var(--transition-normal) ease-out; +} + +.animate-slide-in-left { + animation: slide-in-left var(--transition-normal) ease-out; +} + +.animate-count-up { + animation: count-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.animate-shimmer-sweep { + background: linear-gradient( + 110deg, + transparent 30%, + rgba(59, 130, 246, 0.08) 50%, + transparent 70% + ); + background-size: 200% 100%; + animation: processing-sweep 1.5s ease-in-out infinite; +} + + +/* ════════════════════════════════════════════════════════ + 3. APPLIED ANIMATIONS — auto-wired to selectors + ════════════════════════════════════════════════════════ */ + +/* ── Ambient animated mesh background ────────────────── */ +.app-shell::before { + content: ''; + position: fixed; + inset: -50%; + pointer-events: none; + z-index: -1; + background: + radial-gradient(ellipse at 20% 30%, rgba(59, 130, 246, 0.06) 0%, transparent 40%), + radial-gradient(ellipse at 80% 70%, rgba(99, 102, 241, 0.05) 0%, transparent 40%), + radial-gradient(ellipse at 40% 80%, rgba(16, 185, 129, 0.03) 0%, transparent 40%), + radial-gradient(ellipse at 60% 20%, rgba(245, 158, 11, 0.02) 0%, transparent 40%); + background-size: 200% 200%, 200% 200%, 200% 200%, 200% 200%; + animation: bg-mesh-drift 20s ease-in-out infinite alternate; + will-change: background-position; +} + +/* ── Subtle noise/grain texture overlay ─────────────── */ +.app-shell::after { + content: ''; + position: fixed; + inset: -50%; + pointer-events: none; + z-index: 0; + opacity: 0.015; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); + background-repeat: repeat; + background-size: 256px 256px; + animation: grain-shift 0.5s steps(4) infinite; + will-change: transform; +} + +/* ── Light theme mesh adjustment ────────────────────── */ +[data-theme="light"] .app-shell::before { + background: + radial-gradient(ellipse at 20% 30%, rgba(37, 99, 235, 0.04) 0%, transparent 40%), + radial-gradient(ellipse at 80% 70%, rgba(99, 102, 241, 0.04) 0%, transparent 40%), + radial-gradient(ellipse at 40% 80%, rgba(16, 185, 129, 0.03) 0%, transparent 40%), + radial-gradient(ellipse at 60% 20%, rgba(245, 158, 11, 0.02) 0%, transparent 40%); +} +[data-theme="light"] .app-shell::after { + opacity: 0.008; +} + +/* ── Gradient text shimmer (brand elements) ─────────── */ +.sidebar-brand-name, +.live-title, +.auth-title { + background-size: 200% 100%; + animation: gradient-shimmer 6s ease-in-out infinite alternate; +} + +/* ── Connected status dot: alive breath ─────────────── */ +.status-dot, +.voice-connection-dot.connected { + animation: breathe 3s ease-in-out infinite; +} + +/* Keep existing connecting animation (overrides above) */ +.status-dot.is-connecting, +.voice-connection-dot.connecting { + animation: pulse-connecting 1s ease-in-out infinite !important; +} + +.status-dot.disconnected, +.voice-connection-dot.disconnected { + animation: none !important; +} + +/* ── Live indicator (red recording dot) ─────────────── */ +.msg-card-ai-badge.flagged::before, +.msg-analysis.flagged::before { + content: ''; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-error); + margin-right: 4px; + vertical-align: middle; + animation: live-indicator 1.5s ease-in-out infinite; +} + +/* ── Particle orbs: ambient breathing overlay ───────── */ +.particle-bg { + animation: ambient-glow 6s ease-in-out infinite alternate; +} + +/* ── Live bento grid: staggered entry ───────────────── */ +.live-bento > * { + animation: fade-in-up 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.live-bento > :nth-child(1) { animation-delay: 0.03s; } +.live-bento > :nth-child(2) { animation-delay: 0.06s; } +.live-bento > :nth-child(3) { animation-delay: 0.09s; } +.live-bento > :nth-child(4) { animation-delay: 0.12s; } +.live-bento > :nth-child(5) { animation-delay: 0.15s; } +.live-bento > :nth-child(6) { animation-delay: 0.18s; } + +/* ── Dashboard metric cards: staggered entry ────────── */ +.dashboard-metric-card { + animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.dashboard-metric-card:nth-child(1) { animation-delay: 0.04s; } +.dashboard-metric-card:nth-child(2) { animation-delay: 0.08s; } +.dashboard-metric-card:nth-child(3) { animation-delay: 0.12s; } +.dashboard-metric-card:nth-child(4) { animation-delay: 0.16s; } +.dashboard-metric-card:nth-child(5) { animation-delay: 0.20s; } +.dashboard-metric-card:nth-child(6) { animation-delay: 0.24s; } +.dashboard-metric-card:nth-child(7) { animation-delay: 0.28s; } +.dashboard-metric-card:nth-child(8) { animation-delay: 0.32s; } + +/* ── Dashboard rows: staggered fade-in ──────────────── */ +.dashboard-summary-row, +.dashboard-top-channel-row { + animation: fade-in 0.4s ease-out both; +} +.dashboard-summary-row:nth-child(1), .dashboard-top-channel-row:nth-child(1) { animation-delay: 0.02s; } +.dashboard-summary-row:nth-child(2), .dashboard-top-channel-row:nth-child(2) { animation-delay: 0.04s; } +.dashboard-summary-row:nth-child(3), .dashboard-top-channel-row:nth-child(3) { animation-delay: 0.06s; } +.dashboard-summary-row:nth-child(4), .dashboard-top-channel-row:nth-child(4) { animation-delay: 0.08s; } +.dashboard-summary-row:nth-child(5), .dashboard-top-channel-row:nth-child(5) { animation-delay: 0.10s; } +.dashboard-summary-row:nth-child(6), .dashboard-top-channel-row:nth-child(6) { animation-delay: 0.12s; } +.dashboard-summary-row:nth-child(7), .dashboard-top-channel-row:nth-child(7) { animation-delay: 0.14s; } +.dashboard-summary-row:nth-child(8), .dashboard-top-channel-row:nth-child(8) { animation-delay: 0.16s; } +.dashboard-summary-row:nth-child(9), .dashboard-top-channel-row:nth-child(9) { animation-delay: 0.18s; } +.dashboard-summary-row:nth-child(10),.dashboard-top-channel-row:nth-child(10) { animation-delay: 0.20s; } + +/* ── Message cards: staggered entry ─────────────────── */ +.msg-card { + animation: fade-in-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.msg-card:nth-child(1) { animation-delay: 0.02s; } +.msg-card:nth-child(2) { animation-delay: 0.05s; } +.msg-card:nth-child(3) { animation-delay: 0.08s; } +.msg-card:nth-child(4) { animation-delay: 0.11s; } +.msg-card:nth-child(5) { animation-delay: 0.14s; } +.msg-card:nth-child(6) { animation-delay: 0.17s; } +.msg-card:nth-child(7) { animation-delay: 0.20s; } +.msg-card:nth-child(8) { animation-delay: 0.23s; } +.msg-card:nth-child(9) { animation-delay: 0.26s; } +.msg-card:nth-child(10) { animation-delay: 0.29s; } + +/* ── Recording items: slide in from left ────────────── */ +.rec-item { + animation: slide-in-left 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.rec-item:nth-child(1) { animation-delay: 0.02s; } +.rec-item:nth-child(2) { animation-delay: 0.06s; } +.rec-item:nth-child(3) { animation-delay: 0.10s; } +.rec-item:nth-child(4) { animation-delay: 0.14s; } +.rec-item:nth-child(5) { animation-delay: 0.18s; } + +/* ── Speaker items: pop in ──────────────────────────── */ +.speak-item { + animation: fade-in 0.3s ease-out both; +} +.speak-item:nth-child(1) { animation-delay: 0.02s; } +.speak-item:nth-child(2) { animation-delay: 0.05s; } +.speak-item:nth-child(3) { animation-delay: 0.08s; } +.speak-item:nth-child(4) { animation-delay: 0.11s; } +.speak-item:nth-child(5) { animation-delay: 0.14s; } + +/* ── Audio visualizer bars: individual timing ───────── */ +.audio-bar { + animation: bar-pulse-2 1.2s ease-in-out infinite; +} +.audio-bar:nth-child(odd) { + animation-name: bar-pulse-1; + animation-duration: 0.9s; +} +.audio-bar:nth-child(3n) { + animation-name: bar-pulse-3; + animation-duration: 1.5s; +} + +/* ── Interactive cards: hover glow effect ────────────── */ +.card-interactive:hover { + animation: glow-border 0.8s ease-in-out; + border-color: var(--color-primary); +} + +/* ── Nav active indicator: continuous subtle glow ───── */ +.sidebar-nav-item.is-active::after { + animation: indicator-grow 250ms cubic-bezier(0.16, 1, 0.3, 1) both, + breathe-soft 3s ease-in-out 0.3s infinite; +} + +/* ── Filter chip active: pop scale ──────────────────── */ +.filter-chip-v2.is-active { + animation: pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +/* ── AI badge processing: shimmer sweep ─────────────── */ +.msg-card-ai-badge.processing, +.status-badge-processing { + background: linear-gradient( + 110deg, + var(--color-primary-muted) 25%, + rgba(59, 130, 246, 0.35) 50%, + var(--color-primary-muted) 75% + ) !important; + background-size: 200% 100% !important; + animation: processing-sweep 1.4s ease-in-out infinite; +} +.status-badge-processing::before { + animation: ring-pulse 1.2s ease-in-out infinite !important; +} + +/* ── Image grid items: reveal ───────────────────────── */ +.image-grid-item { + animation: fade-in 0.4s ease-out both; +} +.image-grid-item:nth-child(1) { animation-delay: 0.02s; } +.image-grid-item:nth-child(2) { animation-delay: 0.05s; } +.image-grid-item:nth-child(3) { animation-delay: 0.08s; } +.image-grid-item:nth-child(4) { animation-delay: 0.11s; } +.image-grid-item:nth-child(5) { animation-delay: 0.14s; } +.image-grid-item:nth-child(6) { animation-delay: 0.17s; } +.image-grid-item:nth-child(7) { animation-delay: 0.20s; } +.image-grid-item:nth-child(8) { animation-delay: 0.23s; } + +/* ── Dashboard queue value: count-up feel ───────────── */ +.dashboard-queue-value { + animation: count-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Voice connection panel: entry ──────────────────── */ +.voice-connection { + animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Now playing: slide in ──────────────────────────── */ +.np-body { + animation: slide-in-left 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Skeleton: enhanced shimmer ─────────────────────── */ +.skeleton, +.msg-skel-avatar, +.msg-skel-line, +.msg-skel-badge, +.dashboard-skel-avatar, +.dashboard-skel-line { + background: linear-gradient( + 110deg, + var(--surface-container) 28%, + rgba(59, 130, 246, 0.06) 48%, + var(--surface-container) 68% + ) !important; + background-size: 200% 100% !important; +} + +/* ── Tab content: stronger entry ────────────────────── */ +.tab-content { + animation: fade-in-up 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Mobile tab active: subtle indicator ────────────── */ +.mobile-tab-item.is-active { + animation: pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +/* ── Mascot launcher: enhanced float ────────────────── */ +.mascot-launcher { + animation: float-variation 5s ease-in-out infinite; +} +.mascot-launcher:hover { + animation: none !important; +} + +/* ── Empty state icons: subtle float ────────────────── */ +.empty-state-icon { + animation: float-variation 6s ease-in-out infinite; +} + +/* ── Recent message action buttons ──────────────────── */ +.btn-icon-sm:active:not(:disabled), +.btn-icon:active:not(:disabled) { + animation: ripple 0.4s ease-out; +} + +/* ── Small decorative: channel list rows ────────────── */ +.dashboard-top-channel-row:hover { + animation: none; /* Keep the existing hover translateX */ +} + + +/* ════════════════════════════════════════════════════════ + 4. RESPECT REDUCED MOTION + ════════════════════════════════════════════════════════ */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/services/frontend/frontend/src/styles/bundle.css b/services/frontend/frontend/src/styles/bundle.css new file mode 100644 index 0000000..c3eb139 --- /dev/null +++ b/services/frontend/frontend/src/styles/bundle.css @@ -0,0 +1,3120 @@ +/* ── Design Tokens — IMPHNEN Neuform Dark ────────────── * + * Dark default (no attribute selector) * + * Light: [data-theme="light"] * + * ──────────────────────────────────────────────────────── */ + +:root { + /* ── Surfaces ──────────────────────────────────── */ + --surface-base: #050510; + --surface-raised: #0a0a1a; + --surface-overlay: #12122a; + --surface-container: #1a1a35; + --surface-border: rgba(255, 255, 255, 0.06); + --surface-hover: rgba(59, 130, 246, 0.06); + --surface-glass: rgba(5, 5, 16, 0.78); + + /* ── Text ──────────────────────────────────────── */ + --text-primary: #f1f1f9; + --text-secondary: #9d9db5; + --text-tertiary: #5c5c78; + --text-inverse: #050510; + + /* ── Brand ─────────────────────────────────────── */ + --color-primary: #3b82f6; + --color-primary-hover: #60a5fa; + --color-primary-active: #2563eb; + --color-primary-muted: rgba(59, 130, 246, 0.12); + --gradient-primary: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%); + --gradient-brand: linear-gradient(135deg, #3b82f6 0%, #5865f2 50%, #6366f1 100%); + + /* ── Semantics ─────────────────────────────────── */ + --color-success: #10b981; + --color-warning: #f59e0b; + --color-error: #ef4444; + --color-info: #3b82f6; + --color-destructive: #ef4444; + + /* ── AI Status ─────────────────────────────────── */ + --color-ai-flagged: #ef4444; + --color-ai-clean: #10b981; + --color-ai-warn: #f59e0b; + --color-ai-pending: #5c5c78; + --color-ai-processing: #3b82f6; + --color-ai-error: #dc2626; + --color-ai-deleted: #6b7280; + + /* ── Shadows ───────────────────────────────────── */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35); + --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.4); + + /* ── Radii ─────────────────────────────────────── */ + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 23px; + --radius-pill: 9999px; + + /* ── Spacing ───────────────────────────────────── */ + --space-0: 0px; + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + --space-10: 40px; + --space-12: 48px; + --space-16: 64px; + + /* ── Layout ────────────────────────────────────── */ + --sidebar-width: 240px; + --header-height: 0px; + + /* ── Transitions ───────────────────────────────── */ + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-bounce: 400ms cubic-bezier(0.34, 1.56, 0.64, 1); + + /* ── Z-index ───────────────────────────────────── */ + --z-sidebar: 30; + --z-header: 40; + --z-overlay: 50; + --z-modal: 60; + --z-toast: 70; +} + +/* ── Light Theme ──────────────────────────────────────── */ +[data-theme="light"] { + --surface-base: #f4f6fb; + --surface-raised: #ffffff; + --surface-overlay: #eeeff4; + --surface-container: #dde0e8; + --surface-border: rgba(0, 0, 0, 0.06); + --surface-hover: rgba(37, 99, 235, 0.05); + --surface-glass: rgba(255, 255, 255, 0.72); + + --text-primary: #0f172a; + --text-secondary: #475569; + --text-tertiary: #94a3b8; + --text-inverse: #ffffff; + + --color-primary: #2563eb; + --color-primary-hover: #3b82f6; + --color-primary-active: #1d4ed8; + --color-primary-muted: rgba(37, 99, 235, 0.1); + --gradient-primary: linear-gradient(135deg, #2563eb 0%, #6366f1 100%); + --gradient-brand: linear-gradient(135deg, #2563eb 0%, #5865f2 50%, #6366f1 100%); + + --color-success: #10b981; + --color-warning: #f59e0b; + --color-error: #ef4444; + --color-info: #2563eb; + --color-destructive: #ef4444; + + --color-ai-flagged: #ef4444; + --color-ai-clean: #10b981; + --color-ai-warn: #f59e0b; + --color-ai-pending: #94a3b8; + --color-ai-processing: #2563eb; + --color-ai-error: #dc2626; + --color-ai-deleted: #6b7280; + + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.08); +} +/* ── Reset & Base ─────────────────────────────────────── */ + +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + line-height: 1.6; + min-height: 100vh; + overflow-x: hidden; + background: var(--surface-base); + color: var(--text-primary); +} + +a { + color: var(--color-primary); + text-decoration: none; + transition: color var(--transition-fast); +} +a:hover { color: var(--color-primary-hover); } + +img { max-width: 100%; height: auto; } +svg { display: inline-block; vertical-align: middle; } + +/* ── Scrollbar ────────────────────────────────────────── */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { + background: var(--color-primary); + border-radius: var(--radius-pill); +} + +::selection { + background: rgba(59, 130, 246, 0.25); + color: var(--text-primary); +} +[data-theme="light"] ::selection { + background: rgba(37, 99, 235, 0.25); +} +/* ── Utility Classes ──────────────────────────────────── */ + +/* Layout */ +.flex { display: flex; } +.inline-flex { display: inline-flex; } +.grid { display: grid; } +.block { display: block; } +.hidden { display: none; } +.flex-col { flex-direction: column; } +.flex-row { flex-direction: row; } +.flex-wrap { flex-wrap: wrap; } +.flex-1 { flex: 1 1 0%; } +.flex-shrink-0, .shrink-0 { flex-shrink: 0; } +.items-center { align-items: center; } +.items-start { align-items: flex-start; } +.items-end { align-items: flex-end; } +.items-baseline { align-items: baseline; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } +.justify-end { justify-content: flex-end; } +.gap-0 { gap: 0; } +.gap-1 { gap: var(--space-1); } +.gap-1\.5 { gap: 6px; } +.gap-2 { gap: var(--space-2); } +.gap-3 { gap: var(--space-3); } +.gap-4 { gap: var(--space-4); } +.gap-6 { gap: var(--space-6); } +.gap-8 { gap: var(--space-8); } +.grid-cols-2 { grid-template-columns: repeat(2, 1fr); } +.grid-cols-3 { grid-template-columns: repeat(3, 1fr); } + +/* Width / Height */ +.w-full { width: 100%; } +.w-auto { width: auto; } +.h-full { height: 100%; } +.h-auto { height: auto; } +.min-w-0 { min-width: 0; } +.min-h-0 { min-height: 0; } + +/* Sizing helpers */ +.h-3 { height: 12px; } +.h-4 { height: 16px; } +.h-5 { height: 20px; } +.h-6 { height: 24px; } +.h-8 { height: 32px; } +.h-10 { height: 40px; } +.h-12 { height: 48px; } +.h-16 { height: 64px; } +.h-28 { height: 112px; } +.w-3 { width: 12px; } +.w-4 { width: 16px; } +.w-5 { width: 20px; } +.w-6 { width: 24px; } +.w-8 { width: 32px; } +.w-10 { width: 40px; } +.w-12 { width: 48px; } +.w-16 { width: 64px; } +.w-48 { width: 192px; } + +/* Position */ +.relative { position: relative; } +.absolute { position: absolute; } +.fixed { position: fixed; } +.sticky { position: sticky; } +.inset-0 { inset: 0; } +.top-0 { top: 0; } +.right-0 { right: 0; } +.bottom-0 { bottom: 0; } +.left-0 { left: 0; } + +/* Overflow */ +.overflow-auto { overflow: auto; } +.overflow-hidden { overflow: hidden; } +.overflow-y-auto { overflow-y: auto; } +.overflow-x-auto { overflow-x: auto; } + +/* Z-index */ +.z-0 { z-index: 0; } +.z-10 { z-index: 10; } +.z-50 { z-index: 50; } + +/* Margin */ +.m-0 { margin: 0; } +.mx-auto { margin-left: auto; margin-right: auto; } +.ml-auto { margin-left: auto; } +.mr-auto { margin-right: auto; } +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--space-1); } +.mt-2 { margin-top: var(--space-2); } +.mt-3 { margin-top: var(--space-3); } +.mt-4 { margin-top: var(--space-4); } +.mt-6 { margin-top: var(--space-6); } +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--space-1); } +.mb-2 { margin-bottom: var(--space-2); } +.mb-3 { margin-bottom: var(--space-3); } +.mb-4 { margin-bottom: var(--space-4); } +.mb-6 { margin-bottom: var(--space-6); } +.ml-0 { margin-left: 0; } +.ml-1 { margin-left: var(--space-1); } +.ml-2 { margin-left: var(--space-2); } +.mr-1 { margin-right: var(--space-1); } +.mr-2 { margin-right: var(--space-2); } +.mr-1\.5 { margin-right: 6px; } + +/* Padding */ +.p-0 { padding: 0; } +.p-1 { padding: var(--space-1); } +.p-2 { padding: var(--space-2); } +.p-3 { padding: var(--space-3); } +.p-4 { padding: var(--space-4); } +.p-5 { padding: var(--space-5); } +.p-6 { padding: var(--space-6); } +.px-1 { padding-left: var(--space-1); padding-right: var(--space-1); } +.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); } +.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); } +.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); } +.px-6 { padding-left: var(--space-6); padding-right: var(--space-6); } +.py-0 { padding-top: 0; padding-bottom: 0; } +.py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); } +.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); } +.py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); } +.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); } +.pt-2 { padding-top: var(--space-2); } +.pt-3 { padding-top: var(--space-3); } +.pt-4 { padding-top: var(--space-4); } +.pb-2 { padding-bottom: var(--space-2); } +.pb-3 { padding-bottom: var(--space-3); } +.pb-4 { padding-bottom: var(--space-4); } +.pl-2 { padding-left: var(--space-2); } +.pr-2 { padding-right: var(--space-2); } + +/* Border */ +.border { border: 1px solid var(--surface-border); } +.border-0 { border: none; } +.border-t { border-top: 1px solid var(--surface-border); } +.border-b { border-bottom: 1px solid var(--surface-border); } +.border-l { border-left: 1px solid var(--surface-border); } +.border-r { border-right: 1px solid var(--surface-border); } +.border-l-3 { border-left-width: 3px; } +.border-destructive { border-color: var(--color-error); } +.border-border { border-color: var(--surface-border); } +.border-primary { border-color: var(--color-primary); } +.rounded-sm { border-radius: var(--radius-sm); } +.rounded { border-radius: var(--radius-md); } +.rounded-md { border-radius: var(--radius-md); } +.rounded-lg { border-radius: var(--radius-lg); } +.rounded-xl { border-radius: var(--radius-xl); } +.rounded-full { border-radius: var(--radius-pill); } + +/* Typography */ +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } +.whitespace-nowrap { white-space: nowrap; } +.whitespace-pre-wrap { white-space: pre-wrap; } +.break-words { word-break: break-word; } +.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.text-xs { font-size: 0.75rem; line-height: 1rem; } +.text-sm { font-size: 0.875rem; line-height: 1.25rem; } +.text-base { font-size: 1rem; line-height: 1.5rem; } +.text-lg { font-size: 1.125rem; line-height: 1.75rem; } +.text-xl { font-size: 1.25rem; line-height: 1.75rem; } +.text-2xl { font-size: 1.5rem; line-height: 2rem; } +.text-3xl { font-size: 1.875rem; line-height: 2.25rem; } +.font-normal { font-weight: 400; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-bold { font-weight: 700; } +.font-extrabold { font-weight: 800; } +.text-primary { color: var(--text-primary); } +.text-secondary { color: var(--text-secondary); } +.text-tertiary { color: var(--text-tertiary); } +.text-primary-color { color: var(--color-primary); } +.text-error { color: var(--color-error); } +.text-success { color: var(--color-success); } +.text-warning { color: var(--color-warning); } +.text-inverse { color: var(--text-inverse); } + +/* Background */ +.bg-surface { background: var(--surface-raised); } +.bg-overlay { background: var(--surface-overlay); } +.bg-base { background: var(--surface-base); } +.bg-primary { background: var(--color-primary); } +.bg-transparent { background: transparent; } + +/* Opacity / Misc */ +.opacity-0 { opacity: 0; } +.opacity-50 { opacity: 0.5; } +.opacity-60 { opacity: 0.6; } +.opacity-70 { opacity: 0.7; } +.opacity-80 { opacity: 0.8; } +.opacity-85 { opacity: 0.85; } +.object-contain { object-fit: contain; } +.object-cover { object-fit: cover; } +.cursor-pointer { cursor: pointer; } +.cursor-default { cursor: default; } +.cursor-not-allowed { cursor: not-allowed; } +.select-none { user-select: none; } +.pointer-events-none { pointer-events: none; } +.pointer-events-auto { pointer-events: auto; } + +/* Transitions */ +.transition-all { transition: all var(--transition-fast); } +.transition-transform { transition: transform var(--transition-fast); } +.transition-opacity { transition: opacity var(--transition-fast); } +.transition-colors { transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast); } +.transition-shadow { transition: box-shadow var(--transition-fast); } +.hover\:scale-105:hover { transform: scale(1.05); } +.hover\:opacity-80:hover { opacity: 0.8; } + +/* Animation */ +.animate-spin { animation: spin 1s linear infinite; } +@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } + +/* Responsive */ +@media (max-width: 768px) { + .grid-cols-2 { grid-template-columns: 1fr; } + .grid-cols-3 { grid-template-columns: 1fr; } +} + +/* ── Extra Utilities (component-used aliases) ────────── */ +.active { background: var(--gradient-primary); color: white; } +.is-active { background: var(--gradient-primary); color: white; } +.is-deleted { opacity: 0.6; } +.is-connecting { animation: pulse-connecting 1s ease-in-out infinite; } +.is-scroll { overflow-x: auto; flex-wrap: nowrap; } +.separated > * + * { margin-top: 0.625rem; padding-top: 0.625rem; border-top: 1px solid var(--surface-border); } +.tall { height: 112px; width: 64px; } +.typing { animation: pulse-dot 1.5s ease-in-out infinite; } +.mascot { font-variant-numeric: tabular-nums; } + +.ml-0\.5 { margin-left: 2px; } +.space-y-2 > * + * { margin-top: var(--space-2); } +.tracking-tight { letter-spacing: -0.025em; } +.text-foreground { color: var(--text-primary); } +.text-muted-foreground { color: var(--text-tertiary); } +.text-destructive { color: var(--color-error); } +.text-\[10px\] { font-size: 10px; } +.h-3\.5 { height: 14px; } +.w-3\.5 { width: 14px; } + +.bg-background { background: var(--surface-base); } +.bg-card { background: var(--surface-raised); } +.border-input { border-color: var(--surface-border); } + +.head-left { display: flex; align-items: center; gap: var(--space-3); } +.head-right { display: flex; align-items: center; gap: var(--space-4); margin-left: auto; } +.head-status { display: flex; align-items: center; gap: var(--space-1); font-size: 0.75rem; color: var(--text-secondary); } +.head-status-dot { width: 8px; height: 8px; border-radius: 50%; transition: all var(--transition-fast); } + +.card-bordered { border: 1px solid var(--surface-border); } +.card-elevated { box-shadow: var(--shadow-md); } + +.empty-state-icon { font-size: 3rem; margin-bottom: var(--space-4); opacity: 0.5; } + +.input-error { border-color: var(--color-error); } +.input-soft { background: var(--surface-container); border: 1px solid transparent; } + +.theme-toggle { width: 44px; height: 44px; border: 1px solid var(--surface-border); border-radius: var(--radius-md); background: var(--surface-overlay); color: var(--text-tertiary); cursor: pointer; font-size: 1.25rem; display: flex; align-items: center; justify-content: center; transition: all var(--transition-fast); } +.theme-toggle:hover { color: var(--color-primary); border-color: var(--color-primary); } + +.mascot-controls { display: flex; align-items: center; gap: var(--space-2); } +.mascot-inline { display: flex; align-items: center; gap: var(--space-1); } + +.placeholder-muted-foreground::placeholder { color: var(--text-tertiary); } + +.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +@media (max-width: 768px) { .md\:grid-cols-2 { grid-template-columns: 1fr; } } + +.bg-destructive\/15 { background: rgba(239, 68, 68, 0.15); } +.border-border\/50 { border-color: rgba(255, 255, 255, 0.03); } +[data-theme="light"] .border-border\/50 { border-color: rgba(0, 0, 0, 0.03); } +/* ── App Shell & Layout ───────────────────────────────── */ + +.app-shell { + position: relative; + z-index: 1; + display: flex; + height: 100vh; + background: var(--surface-base); + color: var(--text-primary); +} + +/* ── Sidebar ─────────────────────────────────────────── */ +.app-sidebar { + width: var(--sidebar-width); + flex-shrink: 0; + height: 100vh; + display: flex; + flex-direction: column; + border-right: 1px solid var(--surface-border); + background: var(--surface-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + position: sticky; + top: 0; + z-index: var(--z-sidebar); +} + +.sidebar-brand { + display: flex; + align-items: center; + gap: var(--space-3); + height: 60px; + padding: 0 var(--space-4); + flex-shrink: 0; + border-bottom: 1px solid var(--surface-border); +} + +.sidebar-brand-icon { + font-size: 1.25rem; + line-height: 1; +} + +.sidebar-brand-text { + display: flex; + flex-direction: column; +} + +.sidebar-brand-name { + font-weight: 800; + font-size: 1rem; + background: var(--gradient-brand); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: -0.03em; + line-height: 1.2; +} + +.sidebar-brand-subtitle { + color: var(--text-tertiary); + font-size: 0.6875rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.08em; + line-height: 1.3; +} + +/* ── Navigation ──────────────────────────────────────── */ +.sidebar-nav { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); + padding: var(--space-3); + overflow-y: auto; +} + +.sidebar-nav-item { + display: flex; + align-items: center; + gap: var(--space-3); + height: 40px; + padding: 0 var(--space-3); + border: none; + border-radius: 10px; + background: transparent; + color: var(--text-secondary); + font-family: inherit; + font-size: 14px; + font-weight: 500; + line-height: 20px; + cursor: pointer; + transition: all var(--transition-fast); + text-align: left; + width: 100%; + position: relative; + -webkit-tap-highlight-color: transparent; +} + +.sidebar-nav-item:hover { + background: var(--surface-hover); + color: var(--color-primary); +} + +.sidebar-nav-item.is-active { + background: var(--surface-overlay); + color: var(--color-primary); + font-weight: 600; +} + +.sidebar-nav-item.is-active::after { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 24px; + border-radius: var(--radius-pill); + background: var(--gradient-primary); + box-shadow: 0 0 12px rgba(59, 130, 246, 0.4); + animation: indicator-grow 250ms cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.sidebar-nav-icon { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; + font-size: 1rem; +} + +/* ── Sidebar Footer ──────────────────────────────────── */ +.sidebar-footer { + flex-shrink: 0; + padding: var(--space-3); + border-top: 1px solid var(--surface-border); + display: flex; + align-items: center; + gap: var(--space-2); +} + +.sidebar-footer-status { + display: flex; + align-items: center; + gap: var(--space-1); + flex: 1; + min-width: 0; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + transition: background var(--transition-fast); +} + +.status-dot.is-connecting { + animation: pulse-connecting 1s ease-in-out infinite; +} + +.status-text { + font-size: 0.75rem; + color: var(--text-tertiary); + white-space: nowrap; +} + +/* ── Content Area ────────────────────────────────────── */ +.app-content { + flex: 1; + min-width: 0; + overflow-y: auto; + padding: var(--space-6); + max-width: 1400px; + animation: content-enter 500ms cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.app-content > * { + animation: card-in 500ms cubic-bezier(0.16, 1, 0.3, 1) both; +} +.app-content > :nth-child(1) { animation-delay: 30ms; } +.app-content > :nth-child(2) { animation-delay: 60ms; } +.app-content > :nth-child(3) { animation-delay: 90ms; } + +/* ── Theme Toggle in Sidebar ──────────────────────────── */ +.sidebar-theme-btn { + width: 32px; + height: 32px; + border: 1px solid var(--surface-border); + border-radius: var(--radius-sm); + background: var(--surface-overlay); + color: var(--text-tertiary); + cursor: pointer; + font-size: 0.875rem; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); + flex-shrink: 0; +} +.sidebar-theme-btn:hover { + color: var(--color-primary); + border-color: var(--color-primary); +} + +/* ── Mobile Tab Bar ──────────────────────────────────── */ +.mobile-tab-bar { + display: none; +} + +@media (max-width: 768px) { + .app-main { flex-direction: column; } + .app-sidebar { display: none; } + .app-content { padding: var(--space-4); } + + .mobile-tab-bar { + display: flex; + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--surface-base); + border-top: 1px solid var(--surface-border); + z-index: var(--z-overlay); + } + + .mobile-tab-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + padding: 0.5rem; + background: none; + border: none; + font-family: inherit; + font-size: 0.625rem; + cursor: pointer; + color: var(--text-tertiary); + transition: color var(--transition-fast); + -webkit-tap-highlight-color: transparent; + } + + .mobile-tab-item.is-active { + color: var(--color-primary); + } + + .mobile-tab-item-icon { + font-size: 1.25rem; + line-height: 1; + } +} + +/* ── Animations ──────────────────────────────────────── */ +@keyframes content-enter { + from { opacity: 0; transform: translateY(16px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes card-in { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes indicator-grow { + from { height: 0; opacity: 0; } + to { height: 24px; opacity: 1; } +} +@keyframes pulse-connecting { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.7); } +} +/* ── UI Primitives ────────────────────────────────────── */ + +/* ── Button ──────────────────────────────────────────── */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: 12px 24px; + height: 44px; + border: 1px solid transparent; + border-radius: var(--radius-pill); + font-family: inherit; + font-size: 14px; + font-weight: 600; + line-height: 20px; + letter-spacing: 0.02em; + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; + user-select: none; + -webkit-tap-highlight-color: transparent; + text-decoration: none; +} +.btn:active:not(:disabled) { transform: scale(0.97); } +.btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; } + +.btn-primary { + background: var(--gradient-primary); + color: white; + border: none; +} +.btn-primary:hover:not(:disabled) { + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.25); + transform: translateY(-1px); +} + +.btn-secondary { + background: transparent; + color: var(--color-primary); + border: 1.5px solid var(--color-primary); +} +.btn-secondary:hover:not(:disabled) { + background: var(--color-primary-muted); + transform: translateY(-1px); +} + +.btn-destructive { + background: var(--color-error); + color: white; + border: none; +} +.btn-destructive:hover:not(:disabled) { + filter: brightness(1.1); + transform: translateY(-1px); +} + +.btn-outline { + background: var(--surface-glass); + border: 1px solid var(--surface-border); + color: var(--text-secondary); +} +.btn-outline:hover:not(:disabled) { + background: var(--surface-raised); + border-color: var(--color-primary); + color: var(--color-primary); +} + +.btn-ghost { + background: transparent; + color: var(--text-secondary); + border: none; + height: auto; + padding: 0.5rem 0.75rem; + border-radius: var(--radius-md); +} +.btn-ghost:hover:not(:disabled) { + background: var(--surface-hover); + color: var(--text-primary); +} + +.btn-link { + background: transparent; + color: var(--color-primary); + border: none; + height: auto; + padding: 0; + font-weight: 500; +} +.btn-link:hover:not(:disabled) { text-decoration: underline; } + +/* Sizes */ +.btn-sm { padding: 8px 16px; height: 36px; font-size: 12px; border-radius: var(--radius-md); } +.btn-lg { padding: 14px 28px; height: 48px; font-size: 16px; } +.btn-icon { width: 44px; height: 44px; padding: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 1.25rem; border-radius: var(--radius-pill); } +.btn-icon-sm { width: 36px; height: 36px; padding: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 1rem; border-radius: var(--radius-md); } + +/* ── Card ────────────────────────────────────────────── */ +.card { + background: var(--surface-raised); + border: 1px solid var(--surface-border); + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-sm); + transition: all var(--transition-normal); +} +.card:hover { + box-shadow: var(--shadow-md); +} +.card-interactive:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-md); + transform: translateY(-1px); +} + +.card-header { + padding: var(--space-4) var(--space-6); + border-bottom: 1px solid var(--surface-border); +} +.card-title { + font-size: 1rem; + font-weight: 600; + color: var(--text-primary); +} +.card-description { + font-size: 0.875rem; + color: var(--text-secondary); + margin-top: var(--space-1); +} +.card-content { padding: var(--space-6); } +.card-footer { + padding: var(--space-4) var(--space-6); + border-top: 1px solid var(--surface-border); +} + +/* ── Badge ───────────────────────────────────────────── */ +.badge { + display: inline-flex; + align-items: center; + padding: 4px 8px; + border-radius: var(--radius-sm); + font-size: 12px; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.03em; + background: var(--surface-container); + color: var(--text-secondary); + border: none; + transition: all var(--transition-fast); +} +.badge-primary { background: var(--color-primary-muted); color: var(--color-primary); } +.badge-success { background: rgba(16, 185, 129, 0.12); color: var(--color-success); } +.badge-warning { background: rgba(245, 158, 11, 0.12); color: var(--color-warning); } +.badge-destructive { background: rgba(239, 68, 68, 0.12); color: var(--color-error); } +.badge-outline { background: transparent; border: 1px solid var(--surface-border); color: var(--text-tertiary); } +.badge-info { background: var(--color-primary-muted); color: var(--color-primary); } + +/* ── Status Badge ────────────────────────────────────── */ +.status-badge { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: 3px 10px; + border-radius: var(--radius-pill); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.02em; + transition: all var(--transition-fast); +} +.status-badge::before { + content: ''; + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} +.status-badge-flagged { background: rgba(239, 68, 68, 0.12); color: var(--color-ai-flagged); } +.status-badge-flagged::before { background: var(--color-ai-flagged); box-shadow: 0 0 8px rgba(239, 68, 68, 0.6); } +.status-badge-clean { background: rgba(16, 185, 129, 0.12); color: var(--color-ai-clean); } +.status-badge-clean::before { background: var(--color-ai-clean); box-shadow: 0 0 8px rgba(16, 185, 129, 0.4); } +.status-badge-warn { background: rgba(245, 158, 11, 0.12); color: var(--color-ai-warn); } +.status-badge-warn::before { background: var(--color-ai-warn); box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); } +.status-badge-pending { background: rgba(92, 92, 120, 0.12); color: var(--color-ai-pending); } +.status-badge-pending::before { background: var(--color-ai-pending); } +.status-badge-processing { background: var(--color-primary-muted); color: var(--color-ai-processing); } +.status-badge-processing::before { background: var(--color-ai-processing); animation: pulse-dot 1.5s ease-in-out infinite; } +.status-badge-error { background: rgba(220, 38, 38, 0.12); color: var(--color-ai-error); } +.status-badge-error::before { background: var(--color-ai-error); box-shadow: 0 0 8px rgba(220, 38, 38, 0.4); } + +@keyframes pulse-dot { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +/* ── Input ───────────────────────────────────────────── */ +.input { + display: block; + width: 100%; + padding: var(--space-3); + background: var(--surface-container); + border: 1.5px solid transparent; + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 16px; + font-weight: 400; + line-height: 24px; + transition: all var(--transition-fast); + outline: none; +} +.input::placeholder { color: var(--text-tertiary); } +.input:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-muted), 0 0 16px rgba(59, 130, 246, 0.08); + background: var(--surface-raised); +} +.input[aria-invalid="true"] { + border-color: var(--color-error); + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.12); +} + +/* ── Select ──────────────────────────────────────────── */ +.select { + display: block; + width: 100%; + padding: var(--space-3) 2rem var(--space-3) var(--space-3); + background: var(--surface-container); + border: 1.5px solid transparent; + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 0.875rem; + cursor: pointer; + transition: all var(--transition-fast); + outline: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235c5c78' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.75rem center; +} +.select:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-muted); + background: var(--surface-raised); +} + +/* ── Skeleton ────────────────────────────────────────── */ +.skeleton { + background: linear-gradient(110deg, var(--surface-container) 30%, rgba(59, 130, 246, 0.04) 50%, var(--surface-container) 70%); + background-size: 200% 100%; + animation: shimmer 1.8s ease-in-out infinite; + border-radius: var(--radius-sm); +} +.skeleton-circular { border-radius: 50%; } +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* ── Empty State ─────────────────────────────────────── */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--space-16) var(--space-4); + text-align: center; +} +.empty-state-title { + font-size: 0.9375rem; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: var(--space-2); +} +.empty-state-description { + font-size: 0.8125rem; + color: var(--text-tertiary); + max-width: 280px; + line-height: 1.5; +} + +/* ── Tabs ────────────────────────────────────────────── */ +.tabs { display: flex; flex-direction: column; } +.tab-list { + display: flex; + gap: var(--space-2); + border-bottom: 2px solid var(--surface-border); +} +.tab-trigger { + padding: 8px 16px; + background: transparent; + border: none; + border-bottom: 2px solid transparent; + margin-bottom: -2px; + color: var(--text-secondary); + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all var(--transition-fast); +} +.tab-trigger:hover { color: var(--text-primary); } +.tab-trigger.active, +.tab-trigger[aria-selected="true"] { + color: var(--color-primary); + font-weight: 600; + border-bottom-color: var(--color-primary); +} +.tab-content { + padding-top: var(--space-4); + animation: fade-in-up var(--transition-normal) ease-out; +} + +/* ── Modal ───────────────────────────────────────────── */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.55); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + z-index: var(--z-modal); + animation: fade-in var(--transition-fast) ease-out; +} +.modal-content { + background: var(--surface-raised); + border-radius: var(--radius-lg); + border: 1px solid var(--surface-border); + box-shadow: var(--shadow-lg); + max-width: 90vw; + max-height: 85vh; + overflow: auto; + animation: scale-in var(--transition-bounce); +} +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-4) var(--space-6); + border-bottom: 1px solid var(--surface-border); +} +.modal-body { padding: var(--space-6); } +.modal-footer { + display: flex; + justify-content: flex-end; + gap: var(--space-2); + padding: var(--space-4) var(--space-6); + border-top: 1px solid var(--surface-border); +} + +/* ── Toast ───────────────────────────────────────────── */ +.toast-container { + position: fixed; + bottom: var(--space-4); + right: var(--space-4); + z-index: var(--z-toast); + display: flex; + flex-direction: column; + gap: var(--space-2); + pointer-events: none; +} +.toast { + display: flex; + align-items: center; + gap: var(--space-3); + padding: 0.75rem 1rem; + background: var(--surface-raised); + border: 1px solid var(--surface-border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + min-width: 300px; + max-width: 420px; + pointer-events: auto; + animation: slide-in-right var(--transition-bounce); +} +.toast-success { border-left: 3px solid var(--color-success); } +.toast-error { border-left: 3px solid var(--color-error); } +.toast-warning { border-left: 3px solid var(--color-warning); } +.toast-info { border-left: 3px solid var(--color-primary); } +.toast-close { + margin-left: auto; + background: none; + border: none; + color: var(--text-tertiary); + cursor: pointer; + padding: 0.25rem; + transition: color var(--transition-fast); +} +.toast-close:hover { color: var(--text-primary); } + +/* ── Animations ──────────────────────────────────────── */ +@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } +@keyframes fade-in-up { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } +@keyframes scale-in { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } } +@keyframes slide-in-right { from { opacity: 0; transform: translateX(120%); } to { opacity: 1; transform: translateX(0); } } + +.animate-fade-in { animation: fade-in var(--transition-normal) ease-out; } +.animate-fade-in-up { animation: fade-in-up var(--transition-normal) ease-out; } +.animate-scale-in { animation: scale-in var(--transition-normal) ease-out; } +.animate-slide-in-right { animation: slide-in-right var(--transition-normal) ease-out; } + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} +/* ── Messages Panel ───────────────────────────────────── */ + +.messages-panel { display: flex; flex-direction: column; gap: var(--space-5); } + +/* ── Filter Bar ──────────────────────────────────────── */ +.filter-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-3); +} + +.filter-bar-search { + position: relative; + flex: 1; + min-width: 200px; +} + +.filter-bar-search-icon { + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-tertiary); + pointer-events: none; +} + +.filter-bar-input { + width: 100%; + padding: 0.5rem 0.75rem 0.5rem 2.25rem; + height: 36px; + background: var(--surface-container); + border: 1px solid transparent; + border-radius: var(--radius-pill); + color: var(--text-primary); + font-family: inherit; + font-size: 0.875rem; + transition: all var(--transition-fast); + outline: none; +} +.filter-bar-input::placeholder { color: var(--text-tertiary); } +.filter-bar-input:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-muted); + background: var(--surface-raised); +} + +.filter-bar-chips { + display: flex; + align-items: center; + gap: 0.375rem; + flex-wrap: wrap; +} + +.filter-chip { + padding: 0.25rem 0.75rem; + border-radius: var(--radius-pill); + font-size: 0.6875rem; + font-weight: 500; + border: 1px solid transparent; + background: var(--surface-hover); + color: var(--text-tertiary); + cursor: pointer; + transition: all var(--transition-fast); + font-family: inherit; +} +.filter-chip:hover { + color: var(--text-secondary); + background: var(--surface-container); +} +.filter-chip.is-active { + background: var(--gradient-primary); + color: white; +} + +.filter-bar-live-count { + font-size: 0.875rem; + color: var(--text-tertiary); + white-space: nowrap; +} + +/* ── Stats Bar ───────────────────────────────────────── */ +.stats-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-1); +} + +/* ── Message Card ────────────────────────────────────── */ +.msg-card { + background: var(--surface-raised); + border: 1px solid var(--surface-border); + border-radius: var(--radius-md); + overflow: hidden; + transition: all var(--transition-fast); + box-shadow: var(--shadow-sm); +} +.msg-card:hover { + border-color: rgba(59, 130, 246, 0.20); + box-shadow: var(--shadow-md); + transform: translateY(-1px); +} +.msg-card.is-deleted { + opacity: 0.6; + border-color: var(--color-error); +} + +.msg-card-inner { + display: flex; + gap: var(--space-3); + padding: var(--space-4); +} + +.msg-card-avatar { + width: 40px; + height: 40px; + flex-shrink: 0; + border-radius: 50%; + object-fit: cover; +} + +.msg-card-body { min-width: 0; flex: 1; } + +.msg-card-meta { + display: flex; + align-items: baseline; + gap: var(--space-2); + margin-bottom: var(--space-2); + font-size: 0.75rem; + color: var(--text-secondary); + font-family: 'JetBrains Mono', monospace; +} + +.msg-card-username { + font-size: 0.8125rem; + font-weight: 600; + color: var(--text-primary); +} + +.msg-card-channel { + display: inline-flex; + align-items: center; + gap: var(--space-1); + font-size: 0.6875rem; + color: var(--text-tertiary); + background: var(--surface-container); + padding: 1px 8px; + border-radius: var(--radius-pill); +} +.msg-card-channel::before { content: '#'; opacity: 0.5; } + +.msg-card-time { + font-size: 0.6875rem; + color: var(--text-tertiary); + font-variant-numeric: tabular-nums; + margin-left: auto; +} + +/* ── AI Badge ────────────────────────────────────────── */ +.msg-card-ai-badge { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 2px 8px; + border-radius: 6px; + font-size: 0.625rem; + font-weight: 600; + margin-left: var(--space-1); +} +.msg-card-ai-badge.clean { + background: rgba(16, 185, 129, 0.10); + color: var(--color-ai-clean); +} +.msg-card-ai-badge.warn { + background: rgba(245, 158, 11, 0.10); + color: var(--color-ai-warn); +} +.msg-card-ai-badge.flagged { + background: rgba(239, 68, 68, 0.10); + color: var(--color-ai-flagged); +} +.msg-card-ai-badge.processing { + background: var(--color-primary-muted); + color: var(--color-ai-processing); +} +.msg-card-ai-badge.pending { + background: rgba(92, 92, 120, 0.10); + color: var(--color-ai-pending); +} + +/* ── Reply indicator (Discord-style reference block) ──── */ +.msg-row-reply { + display: flex; + align-items: stretch; + margin: 0.375rem 0 0.625rem; + border-radius: 6px; + overflow: hidden; + cursor: default; + transition: background 150ms ease; +} +.msg-row-reply:hover { + background: rgba(59, 130, 246, 0.04); +} +.msg-row-reply-line { + width: 3px; + flex-shrink: 0; + border-radius: 2px; + background: linear-gradient(180deg, #3b82f6, #8b5cf6); + opacity: 0.6; +} +.msg-row-reply-main { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.625rem; + min-width: 0; + flex: 1; + font-size: 0.75rem; + line-height: 1.4; +} +.msg-row-reply-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.625rem; + font-weight: 700; + color: white; + background: linear-gradient(135deg, #3b82f6, #6366f1); + text-transform: uppercase; + box-shadow: 0 1px 3px rgba(59, 130, 246, 0.25); +} +.msg-row-reply-label { + flex-shrink: 0; + color: var(--text-tertiary); + font-weight: 450; + opacity: 0.8; +} +.msg-row-reply-user { + flex-shrink: 0; + font-weight: 700; + color: #3b82f6; + letter-spacing: -0.01em; +} +.msg-row-reply-snippet { + color: var(--text-tertiary); + font-style: italic; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; + opacity: 0.9; + display: inline-flex; + align-items: center; + gap: 0; +} +.msg-row-reply-snippet::before { + content: '"'; + opacity: 0.5; + margin-right: 1px; +} +.msg-row-reply-snippet::after { + content: '"'; + opacity: 0.5; + margin-left: 1px; +} + +/* ── Message Content ─────────────────────────────────── */ +.msg-card-content { + font-size: 0.875rem; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + color: var(--text-primary); +} +.msg-card-content.is-deleted { + opacity: 0.6; + color: var(--text-secondary); +} + +.msg-card-messages.separated > * + * { + margin-top: 0.625rem; + padding-top: 0.625rem; + border-top: 1px solid var(--surface-border); +} + +/* ── Actions Bar ─────────────────────────────────────── */ +.msg-card-actions { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border-top: 1px solid var(--surface-border); + background: var(--surface-base); +} + +.msg-card-actions .btn-icon-sm { + color: var(--text-tertiary); +} +.msg-card-actions .btn-icon-sm:hover { + color: var(--color-primary); +} + +/* ── Embed ───────────────────────────────────────────── */ +.msg-embed { + margin-top: var(--space-2); + padding: var(--space-3); + border-left: 3px solid var(--color-primary); + border-radius: var(--radius-sm); + background: var(--surface-overlay); +} +.msg-embed-title { + font-weight: 600; + font-size: 0.875rem; + color: var(--text-primary); + margin-bottom: var(--space-1); +} +.msg-embed-description { + font-size: 0.8125rem; + color: var(--text-secondary); + line-height: 1.5; +} +.msg-embed-fields { + display: flex; + flex-direction: column; + gap: var(--space-1); + margin-top: var(--space-2); +} +.msg-embed-field { + display: flex; + flex-direction: column; +} +.msg-embed-field-name { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 0.125rem; +} +.msg-embed-field-value { + font-size: 0.8125rem; + color: var(--text-primary); +} +.msg-embed-footer { + margin-top: var(--space-2); + font-size: 0.6875rem; + color: var(--text-tertiary); +} + +/* ── Image Grid ──────────────────────────────────────── */ +.image-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: var(--space-2); +} +.image-grid-item { + aspect-ratio: 1; + border-radius: var(--radius-md); + overflow: hidden; + cursor: pointer; + transition: all var(--transition-fast); + border: 1px solid var(--surface-border); +} +.image-grid-item:hover { + opacity: 0.85; + transform: scale(1.02); +} +.image-grid-item img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* ── Message Media ───────────────────────────────────── */ +.msg-media-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-2); +} +.msg-media-row.is-scroll { + overflow-x: auto; + flex-wrap: nowrap; +} +.msg-thumb { + width: 64px; + height: 64px; + object-fit: cover; + border-radius: var(--radius-sm); + border: 1px solid var(--surface-border); + transition: all var(--transition-fast); +} +.msg-thumb:hover { transform: scale(1.08); } +.msg-thumb-link { + display: block; + flex-shrink: 0; + overflow: hidden; + border-radius: var(--radius-sm); + border: 1px solid var(--surface-border); + transition: all var(--transition-fast); +} +.msg-thumb-link:hover { border-color: var(--color-primary); } +.msg-sticker { + width: 48px; + height: 48px; + object-fit: contain; +} +.msg-video { + height: 112px; + width: 192px; + flex-shrink: 0; + border-radius: var(--radius-sm); + border: 1px solid var(--surface-border); + object-fit: cover; + background: #000; +} +.msg-media-overflow { + display: flex; + width: 64px; + height: 64px; + align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + border: 1px solid var(--surface-border); + font-size: 0.75rem; + color: var(--text-secondary); + background: var(--surface-container); +} +.msg-media-overflow.tall { height: 112px; width: 64px; } + +/* ── Message Categories ──────────────────────────────── */ +.msg-cats { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); + margin-top: var(--space-2); +} + +/* ── AI Analysis ─────────────────────────────────────── */ +.msg-analysis { + margin-top: var(--space-2); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-sm); + font-size: 0.75rem; + border-left: 3px solid; +} +.msg-analysis.flagged { + background: rgba(239, 68, 68, 0.06); + border-color: var(--color-ai-flagged); +} +.msg-analysis.clean { + background: rgba(16, 185, 129, 0.06); + border-color: var(--color-ai-clean); +} +.msg-analysis-row { + display: flex; + align-items: flex-start; + gap: var(--space-2); +} +.msg-analysis-body { min-width: 0; flex: 1; } +.msg-analysis-summary { + display: block; + font-weight: 500; + margin-bottom: var(--space-1); +} +.msg-analysis-text { + font-size: 0.75rem; + line-height: 1.5; + white-space: pre-wrap; +} + +/* ── Message Error ───────────────────────────────────── */ +.msg-error { + margin-top: var(--space-2); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-sm); + font-size: 0.75rem; + color: var(--color-warning); + background: rgba(245, 158, 11, 0.06); +} + +/* ── Message Skeleton ────────────────────────────────── */ +.msg-skel { + display: flex; + gap: var(--space-3); + padding: var(--space-4); +} +.msg-skel-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(90deg, var(--surface-container) 25%, rgba(59, 130, 246, 0.04) 50%, var(--surface-container) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; + flex-shrink: 0; +} +.msg-skel-lines { + min-width: 0; + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-3); +} +.msg-skel-line { + height: 20px; + background: linear-gradient(90deg, var(--surface-container) 25%, rgba(59, 130, 246, 0.04) 50%, var(--surface-container) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; + border-radius: var(--radius-sm); +} +.msg-skel-badges { + display: flex; + gap: var(--space-2); +} +.msg-skel-badge { + height: 24px; + border-radius: var(--radius-pill); + background: linear-gradient(90deg, var(--surface-container) 25%, rgba(59, 130, 246, 0.04) 50%, var(--surface-container) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} + +/* ── Feed ────────────────────────────────────────────── */ +.feed-wrap { + display: flex; + flex-direction: column; + gap: var(--space-4); +} +.feed-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--space-16) var(--space-4); + text-align: center; +} +.feed-empty-title { + font-size: 0.9375rem; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: var(--space-2); +} +.feed-empty-desc { + font-size: 0.8125rem; + color: var(--text-tertiary); + max-width: 280px; + line-height: 1.5; +} +.feed-sentinel { height: var(--space-4); } +.feed-loader { margin-top: var(--space-4); text-align: center; } + +/* ── Misc ────────────────────────────────────────────── */ +.search-count { + font-size: 0.875rem; + color: var(--text-secondary); +} +.icon-spin { animation: spin 1s linear infinite; } +.custom-emoji { + display: inline-block; + height: 20px; + width: 20px; + vertical-align: middle; + object-fit: contain; +} + +/* ── Legacy aliases (components still use these) ────── */ +.panel-card { background: var(--surface-raised); border: 1px solid var(--surface-border); border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--shadow-sm); transition: all var(--transition-normal); } +.panel-card-head { padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--surface-border); } +.panel-card-title { font-size: 1rem; font-weight: 600; color: var(--text-primary); } +.panel-card-desc { font-size: 0.875rem; color: var(--text-secondary); margin-top: var(--space-1); } + +.search-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); } +.search-wrap { position: relative; flex: 1; min-width: 200px; } +.search-icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); color: var(--text-tertiary); pointer-events: none; } +.search-input { width: 100%; padding: 0.5rem 0.75rem 0.5rem 2.25rem; height: 36px; background: var(--surface-container); border: 1px solid transparent; border-radius: var(--radius-pill); color: var(--text-primary); font-family: inherit; font-size: 0.875rem; transition: all var(--transition-fast); outline: none; } +.search-input::placeholder { color: var(--text-tertiary); } +.search-input:focus { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-muted); background: var(--surface-raised); } + +.filter-group { display: flex; align-items: center; gap: 0.375rem; margin-left: auto; } +.filter-chip-v2 { padding: 0.25rem 0.75rem; border-radius: var(--radius-pill); font-size: 0.6875rem; font-weight: 500; border: 1px solid transparent; background: var(--surface-hover); color: var(--text-tertiary); cursor: pointer; transition: all var(--transition-fast); font-family: inherit; } +.filter-chip-v2:hover { color: var(--text-secondary); background: var(--surface-container); } +.filter-chip-v2.is-active { background: var(--gradient-primary); color: white; } + +.msg-row { padding: var(--space-3) 0; border-bottom: 1px solid var(--surface-border); transition: all var(--transition-fast); } +.msg-row:hover { background: var(--surface-hover); margin: 0 calc(-1 * var(--space-4)); padding-left: var(--space-4); padding-right: var(--space-4); border-radius: var(--radius-sm); } +.msg-row:last-child { border-bottom: none; } +.msg-row-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; } +.msg-row-time { font-size: 0.6875rem; color: var(--text-tertiary); font-variant-numeric: tabular-nums; min-width: 48px; } +.msg-row-badge { display: inline-flex; align-items: center; gap: 0.125rem; font-size: 0.75rem; } +.msg-row-badge.edited { color: var(--text-secondary); } +.msg-row-badge.deleted { color: var(--color-error); } +.msg-row-status { margin-left: auto; display: flex; align-items: center; gap: var(--space-1); } +.msg-row-body { font-size: 0.875rem; line-height: 1.5rem; white-space: pre-wrap; word-break: break-word; } +.msg-row-body.is-deleted { opacity: 0.6; color: var(--text-secondary); } + +.msg-card-head { display: flex; align-items: baseline; gap: var(--space-2); margin-bottom: var(--space-2); } +.msg-card-name { font-size: 0.875rem; font-weight: 600; color: var(--text-primary); } +.msg-card-messages > * + * { margin-top: 0.625rem; padding-top: 0.625rem; border-top: 1px solid var(--surface-border); } +.msg-card-messages.separated > * + * { margin-top: 0.625rem; padding-top: 0.625rem; border-top: 1px solid var(--surface-border); } + +.msg-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); } +.msg-retry-hint { font-size: 0.75rem; color: var(--text-tertiary); opacity: 0.7; } + +.msg-sticker-placeholder { display: flex; width: 48px; height: 48px; align-items: center; justify-content: center; border-radius: var(--radius-sm); border: 1px solid var(--surface-border); } + +.msg-analysis-icon { margin-top: 0.125rem; flex-shrink: 0; } + +.img-empty { display: flex; align-items: center; justify-content: center; height: 128px; color: var(--text-secondary); font-style: italic; } +/* ── Dashboard Panel ──────────────────────────────────── */ + +.dashboard-panel { + display: flex; + flex-direction: column; + gap: var(--space-6); +} + +.dashboard-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-4); +} + +/* ── Stats Overview (4-column grid) ──────────────────── */ +.dashboard-stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--space-4); +} + +.dashboard-metric-card { + padding: var(--space-5); + overflow: hidden; + height: 140px; + display: flex; + flex-direction: column; + justify-content: center; +} +.dashboard-metric-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); + border-color: rgba(59, 130, 246, 0.15); +} + +.dashboard-metric-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.dashboard-metric-label { + color: var(--text-secondary); + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.dashboard-metric-value { + margin-top: var(--space-1); + font-size: 1.75rem; + font-weight: 800; + letter-spacing: -0.03em; + color: var(--text-primary); + font-family: 'JetBrains Mono', monospace; +} + +.dashboard-metric-trend { + font-size: 0.6875rem; + font-weight: 500; + margin-top: var(--space-1); +} +.dashboard-metric-trend.positive { color: var(--color-success); } +.dashboard-metric-trend.negative { color: var(--color-error); } + +.dashboard-metric-icon { + display: flex; + align-items: center; + justify-content: center; + width: 2.75rem; + height: 2.75rem; + border-radius: var(--radius-lg); + font-size: 1.25rem; +} + +/* ── Summary Lists ───────────────────────────────────── */ +.dashboard-wide-card { grid-column: span 2; } + +.dashboard-summary-list { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.dashboard-summary-row { + display: flex; + align-items: flex-start; + gap: var(--space-3); + padding: var(--space-3); + border-bottom: 1px solid var(--surface-border); + transition: all var(--transition-fast); +} +.dashboard-summary-row:hover { + background: var(--surface-hover); +} + +.dashboard-summary-avatar { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-full); + background: var(--surface-container); + color: var(--text-secondary); + font-weight: 700; + overflow: hidden; +} +.dashboard-summary-avatar-img { + width: 100%; + height: 100%; + object-fit: cover; +} +.dashboard-channel-avatar { + color: var(--color-primary); + background: var(--color-primary-muted); +} + +.dashboard-summary-main { min-width: 0; flex: 1; } +.dashboard-summary-title { + color: var(--text-primary); + font-size: 0.875rem; + font-weight: 600; +} +.dashboard-summary-text { + margin-top: var(--space-1); + color: var(--text-secondary); + font-size: 0.8125rem; + line-height: 1.45; +} +.dashboard-summary-meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-2); + color: var(--text-tertiary); + font-size: 0.75rem; +} + +.dashboard-list-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-3); + padding: var(--space-10) var(--space-4); + text-align: center; +} + +/* ── Skeleton cells ──────────────────────────────────── */ +.dashboard-skel-row { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3); +} +.dashboard-skel-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + flex-shrink: 0; + background: linear-gradient(90deg, var(--surface-container) 25%, rgba(59, 130, 246, 0.04) 50%, var(--surface-container) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} +.dashboard-skel-lines { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-2); +} +.dashboard-skel-line { + height: 16px; + border-radius: var(--radius-sm); + background: linear-gradient(90deg, var(--surface-container) 25%, rgba(59, 130, 246, 0.04) 50%, var(--surface-container) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} + +/* ── Responsive ──────────────────────────────────────── */ +@media (max-width: 1024px) { + .dashboard-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@media (max-width: 768px) { + .dashboard-stats-grid { grid-template-columns: 1fr; } + .dashboard-wide-card { grid-column: span 1; } +} + +/* ── Legacy aliases ──────────────────────────────────── */ +.dashboard-stats { display: flex; flex-wrap: wrap; gap: var(--space-4); } +.dashboard-top-channels, .dashboard-summary-list { display: flex; flex-direction: column; gap: var(--space-2); } +.dashboard-top-channel-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--surface-container); color: var(--text-secondary); font-size: 0.875rem; transition: all var(--transition-fast); } +.dashboard-top-channel-row:hover { background: var(--surface-overlay); transform: translateX(2px); } +.dashboard-moderation-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); } +.dashboard-queue-value { font-size: 1.75rem; font-weight: 800; color: var(--text-primary); letter-spacing: -0.03em; } +.dashboard-queue-label { margin-top: var(--space-1); color: var(--text-secondary); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; } +.dashboard-list-card { overflow: hidden; } +.dashboard-list-toolbar { margin-bottom: var(--space-4); } + +.badge-secondary { background: var(--color-primary-muted); color: var(--color-primary); } +/* ── Live Panel (Bento Grid) ──────────────────────────── */ + +.live-body { + display: flex; + flex-direction: column; + gap: var(--space-6); +} + +.live-head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.live-title { + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.025em; + background: var(--gradient-brand); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.live-desc { + font-size: 0.875rem; + color: var(--text-secondary); + margin-top: var(--space-1); +} + +/* ── Bento Grid ──────────────────────────────────────── */ +.live-grid { + display: grid; + gap: var(--space-4); +} + +/* 2-column bento layout */ +.live-bento { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} + +.live-bento > .live-span-2 { + grid-column: 1 / -1; +} + +@media (max-width: 768px) { + .live-bento { grid-template-columns: 1fr; } +} + +/* ── Voice Connection Card ───────────────────────────── */ +.voice-connection { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.voice-connection-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.voice-connection-status { + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.875rem; +} + +.voice-connection-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} +.voice-connection-dot.connected { background: var(--color-success); box-shadow: 0 0 8px rgba(16, 185, 129, 0.4); } +.voice-connection-dot.connecting { background: var(--color-warning); animation: pulse-connecting 1s ease-in-out infinite; } +.voice-connection-dot.disconnected { background: var(--text-tertiary); } + +/* ── Active Speakers ─────────────────────────────────── */ +.speak-list { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.speak-item { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-sm); + transition: background var(--transition-fast); +} +.speak-item:hover { background: var(--surface-hover); } + +.speak-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--surface-container); + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 0.75rem; + color: var(--text-secondary); +} + +.speak-info { flex: 1; min-width: 0; } +.speak-name { font-size: 0.8125rem; font-weight: 500; color: var(--text-primary); } +.speak-status { font-size: 0.6875rem; color: var(--text-tertiary); } + +.speak-indicator { + display: flex; + align-items: center; + gap: 2px; +} +.speak-bar { + width: 3px; + height: 12px; + border-radius: 1px; + background: var(--color-primary); + animation: bar-pulse 0.8s ease-in-out infinite; +} +.speak-bar:nth-child(2) { animation-delay: 0.1s; } +.speak-bar:nth-child(3) { animation-delay: 0.2s; } +.speak-bar:nth-child(4) { animation-delay: 0.3s; } +.speak-bar:nth-child(5) { animation-delay: 0.4s; } + +@keyframes bar-pulse { + 0%, 100% { transform: scaleY(1); } + 50% { transform: scaleY(0.5); } +} + +/* ── Audio Visualizer ────────────────────────────────── */ +.audio-visualizer-canvas { + width: 100%; + height: 80px; + border-radius: var(--radius-sm); + background: var(--surface-container); +} + +/* ── Mic Level Meter ─────────────────────────────────── */ +.mic-level { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.mic-track { + position: relative; + height: var(--space-2); + border-radius: var(--radius-pill); + overflow: hidden; + background: var(--surface-container); +} +.mic-fill { + height: 100%; + border-radius: var(--radius-pill); + background: var(--gradient-primary); + transition: width 100ms ease; +} +.mic-clip { + position: absolute; + height: 100%; + width: 2px; + background: white; + right: 0; +} + +/* ── Music Player / Now Playing ──────────────────────── */ +.np-body { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.np-meta { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.75rem; + color: var(--text-secondary); +} + +.np-tags { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.np-sep { + border-top: 1px solid var(--surface-border); + padding-top: var(--space-3); +} + +.wave-wrap { + border: 1px solid var(--surface-border); + border-radius: var(--radius-md); + padding: var(--space-3); +} +.wave-progress { + height: var(--space-2); + border-radius: var(--radius-pill); + overflow: hidden; + background: var(--surface-container); + margin-bottom: var(--space-2); +} +.wave-bar { + height: 100%; + border-radius: var(--radius-pill); + background: var(--gradient-primary); + transition: width 200ms ease; +} +.wave-info { + display: flex; + align-items: center; + justify-content: space-between; +} +.wave-title { + font-size: 0.875rem; + font-weight: 500; + max-width: 192px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.wave-time { + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.75rem; + font-family: monospace; +} + +/* ── Screen Share Panel ──────────────────────────────── */ +.scrn-actions { + display: flex; + gap: var(--space-2); +} +.scrn-status { + display: inline-block; + padding: 2px 8px; + border-radius: var(--radius-sm); + font-size: 0.75rem; + color: var(--color-success); +} + +/* ── Recordings Panel ────────────────────────────────── */ +.rec-list { + display: flex; + flex-direction: column; + gap: var(--space-2); +} +.rec-item { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3); + border-radius: var(--radius-md); + border: 1px solid var(--surface-border); + transition: all var(--transition-fast); +} +.rec-item:hover { + background: var(--surface-hover); + border-color: var(--color-primary); + transform: translateX(4px); +} +.rec-info { flex: 1; min-width: 0; } +.rec-name { font-size: 0.875rem; font-weight: 500; } +.rec-meta { + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.75rem; + color: var(--text-secondary); + margin-top: var(--space-1); +} +.rec-actions { + display: flex; + align-items: center; + gap: 0.375rem; + flex-shrink: 0; +} +.rec-footer { margin-top: var(--space-3); text-align: center; } +.rec-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--space-8); + gap: var(--space-2); +} + +.music-body { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +/* ── Legacy aliases ──────────────────────────────────── */ +.live-panel { display: flex; flex-direction: column; gap: var(--space-6); } +.live-grid-3 { grid-template-columns: repeat(3, 1fr); } + +.recordings-sub-panel { overflow: hidden; } + +.audio-visualizer { display: flex; align-items: flex-end; justify-content: center; height: 80px; gap: 2px; padding: var(--space-2); } +.audio-visualizer-bars { display: flex; align-items: flex-end; gap: 2px; height: 100%; width: 100%; } +.audio-bar { width: 8px; background: var(--gradient-primary); border-radius: var(--radius-sm) var(--radius-sm) 0 0; transition: height 100ms ease; } + +.mic-level-meter { display: flex; flex-direction: column; gap: var(--space-2); } +.mic-row { display: flex; align-items: center; gap: var(--space-2); } + +@media (max-width: 1024px) { .live-grid-3 { grid-template-columns: repeat(2, 1fr); } } +@media (max-width: 768px) { .live-grid-3 { grid-template-columns: 1fr; } } +/* ── Polish: Particles, Theme Toggle, Mascot ──────────── */ + +/* ── Particle Background ─────────────────────────────── */ +.particle-bg { + position: fixed; + inset: 0; + overflow: hidden; + pointer-events: none; + z-index: 0; +} +.particle-orb { + position: absolute; + border-radius: 50%; + will-change: transform, filter; +} +.particle-orb:nth-child(1) { + width: 600px; height: 600px; + top: -200px; right: -150px; + background: radial-gradient(circle, rgba(59, 130, 246, 0.5) 0%, transparent 70%); + filter: blur(100px); + animation: orb-drift-enhanced 30s ease-in-out infinite, + hue-rotate-slow 12s ease-in-out infinite, + orb-pulse 8s ease-in-out infinite; +} +.particle-orb:nth-child(2) { + width: 500px; height: 500px; + bottom: -150px; left: -150px; + background: radial-gradient(circle, rgba(99, 102, 241, 0.5) 0%, transparent 70%); + filter: blur(100px); + animation: orb-drift-enhanced 35s ease-in-out infinite reverse, + hue-rotate-slow 14s ease-in-out infinite reverse, + orb-pulse 10s ease-in-out infinite; + animation-delay: -5s; +} +.particle-orb:nth-child(3) { + width: 350px; height: 350px; + top: 40%; left: 60%; + background: radial-gradient(circle, rgba(99, 102, 241, 0.4) 0%, transparent 70%); + filter: blur(100px); + animation: orb-drift-enhanced 40s ease-in-out infinite, + hue-rotate-slow 16s ease-in-out infinite, + orb-pulse 6s ease-in-out infinite; + animation-delay: -10s; +} +.particle-orb:nth-child(4) { + width: 250px; height: 250px; + top: 10%; left: 20%; + background: radial-gradient(circle, rgba(16, 185, 129, 0.4) 0%, transparent 70%); + filter: blur(100px); + animation: orb-drift-enhanced 25s ease-in-out infinite reverse, + hue-rotate-slow 18s ease-in-out infinite, + orb-pulse 12s ease-in-out infinite; + animation-delay: -3s; +} + +.particle-orb:nth-child(5) { + width: 180px; height: 180px; + top: 70%; left: 30%; + background: radial-gradient(circle, rgba(59, 130, 246, 0.35) 0%, transparent 70%); + filter: blur(80px); + animation: orb-drift-enhanced 20s ease-in-out infinite, + hue-rotate-slow 10s ease-in-out infinite, + orb-pulse 5s ease-in-out infinite; + animation-delay: -7s; +} + +@media (max-width: 768px) { + .particle-bg { display: none; } + .app-shell::after { display: none; } +} + +/* ── Theme Toggle ────────────────────────────────────── */ +.theme-toggle-btn { + width: 32px; + height: 32px; + border: 1px solid var(--surface-border); + border-radius: var(--radius-sm); + background: var(--surface-overlay); + color: var(--text-tertiary); + cursor: pointer; + font-size: 0.875rem; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); + flex-shrink: 0; + -webkit-tap-highlight-color: transparent; +} +.theme-toggle-btn:hover { + color: var(--color-primary); + border-color: var(--color-primary); +} + +/* ── Mascot Chatbot ──────────────────────────────────── */ +.mascot-widget { + position: fixed; + right: var(--space-6); + bottom: var(--space-6); + z-index: var(--z-toast); +} +.mascot-launcher { + width: 3.75rem; + height: 3.75rem; + border: none; + border-radius: 50%; + background: var(--gradient-primary); + color: white; + box-shadow: var(--shadow-lg), 0 0 24px rgba(59, 130, 246, 0.25); + cursor: pointer; + font-size: 1.5rem; + transition: all var(--transition-fast); + animation: float 4s ease-in-out infinite; +} +.mascot-launcher:hover { + transform: translateY(-3px) scale(1.05); + animation: none; +} +.mascot-panel { + width: min(24rem, calc(100vw - 2rem)); + height: min(32.5rem, calc(100vh - 7rem)); + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid var(--surface-border); + border-radius: var(--radius-lg); + background: var(--surface-raised); + box-shadow: var(--shadow-lg); + animation: fade-in-up var(--transition-bounce); +} +.mascot-panel.minimized { height: 3.75rem; } +.mascot-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + padding: var(--space-3); + background: var(--gradient-primary); + color: white; + flex-shrink: 0; +} +.mascot-header-icon { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: var(--radius-sm); + background: rgba(255,255,255,0.15); +} +.mascot-title { + font-size: 0.875rem; + font-weight: 700; + line-height: 1.15; + letter-spacing: 0.02em; +} +.mascot-subtitle { margin-top: 0.125rem; font-size: 0.6875rem; opacity: 0.7; } +.mascot-icon-button { + width: 2rem; + height: 2rem; + border: none; + border-radius: var(--radius-sm); + background: transparent; + color: white; + cursor: pointer; + transition: background var(--transition-fast); +} +.mascot-icon-button:hover { background: rgba(255,255,255,0.20); } +.mascot-messages { + flex: 1; + min-height: 0; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); +} +.mascot-message-row { + display: flex; + gap: var(--space-2); + animation: fade-in-up var(--transition-fast) both; +} +.mascot-message-row.user { justify-content: flex-end; } +.mascot-avatar { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + background: var(--surface-container); + font-size: 0.875rem; +} +.mascot-bubble { + max-width: 17.5rem; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-lg); + font-size: 0.875rem; + line-height: 1.5; + overflow-wrap: anywhere; +} +.mascot-bubble.user { + color: white; + background: var(--gradient-primary); + border-bottom-right-radius: var(--radius-sm); +} +.mascot-bubble.mascot { + color: var(--text-primary); + background: var(--surface-container); + border: 1px solid var(--surface-border); + border-bottom-left-radius: var(--radius-sm); +} +.mascot-bubble.typing { + display: flex; + gap: 0.25rem; + padding-top: 0.7rem; + padding-bottom: 0.7rem; +} +.mascot-bubble.typing span { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background: var(--text-tertiary); + animation: notification-pulse 0.8s infinite; +} +.mascot-bubble.typing span:nth-child(2) { animation-delay: 0.1s; } +.mascot-bubble.typing span:nth-child(3) { animation-delay: 0.2s; } +@keyframes notification-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} +.mascot-form { + display: flex; + gap: var(--space-2); + padding: var(--space-3); + border-top: 1px solid var(--surface-border); +} +.mascot-input { + flex: 1; + min-width: 0; + border: 1.5px solid transparent; + border-radius: var(--radius-sm); + padding: var(--space-2) var(--space-3); + background: var(--surface-container); + color: var(--text-primary); + font-size: 0.875rem; + transition: all var(--transition-fast); +} +.mascot-input:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-muted); + background: var(--surface-raised); +} +.mascot-send { + width: 2.25rem; + border: none; + border-radius: var(--radius-sm); + background: var(--gradient-primary); + color: white; + cursor: pointer; + transition: all var(--transition-fast); +} +.mascot-send:hover { box-shadow: 0 0 12px rgba(59, 130, 246, 0.25); } +.mascot-send:disabled, .mascot-input:disabled { cursor: not-allowed; opacity: 0.55; } + +/* ── Float animation ─────────────────────────────────── */ +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-8px); } +} + +/* ── Auth ────────────────────────────────────────────── */ +.auth-box { width: 400px; text-align: center; } +.auth-lock { font-size: 3rem; margin-bottom: var(--space-4); } +.auth-title { + font-size: 1.375rem; + font-weight: 700; + margin-bottom: var(--space-2); + background: var(--gradient-brand); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.auth-desc { + font-size: 0.875rem; + color: var(--text-secondary); + margin-bottom: var(--space-6); +} +.auth-form { display: flex; flex-direction: column; gap: var(--space-3); } +.auth-error { + font-size: 0.75rem; + color: var(--color-error); + padding: var(--space-2) var(--space-3); + background: rgba(239, 68, 68, 0.08); + border-radius: var(--radius-sm); +} +.auth-close-btn { + position: absolute; + top: 0; + right: 0; + background: none; + border: none; + color: var(--text-tertiary); + font-size: 1.25rem; + cursor: pointer; + padding: 0.25rem; + line-height: 1; + transition: color var(--transition-fast); +} +.auth-close-btn:hover { color: var(--text-primary); } +/* ── Animations — Live Dashboard Effects ──────────────── * + * Keyframes, applied animations, and utility classes. * + * Respects prefers-reduced-motion. * + * ──────────────────────────────────────────────────────── */ + +/* ════════════════════════════════════════════════════════ + 1. KEYFRAMES + ════════════════════════════════════════════════════════ */ + +/* ── Entry / Reveal ─────────────────────────────────── */ +@keyframes slide-in-left { + from { opacity: 0; transform: translateX(-20px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes slide-in-down { + from { opacity: 0; transform: translateY(-12px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes scale-bounce { + 0% { opacity: 0; transform: scale(0.85); } + 60% { opacity: 1; transform: scale(1.04); } + 100% { opacity: 1; transform: scale(1); } +} + +@keyframes pop-in { + 0% { opacity: 0; transform: scale(0.8); } + 70% { opacity: 1; transform: scale(1.08); } + 100% { opacity: 1; transform: scale(1); } +} + +/* ── Status / Alive ─────────────────────────────────── */ +@keyframes breathe { + 0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 4px currentColor; } + 50% { opacity: 0.6; transform: scale(1.2); box-shadow: 0 0 14px currentColor; } +} + +@keyframes breathe-soft { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +@keyframes ring-pulse { + 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } + 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } +} + +@keyframes ring-pulse-success { + 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); } + 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } +} + +@keyframes ring-pulse-error { + 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); } + 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } +} + +@keyframes processing-sweep { + 0% { background-position: -200% center; } + 100% { background-position: 200% center; } +} + +@keyframes live-indicator { + 0%, 100% { opacity: 1; box-shadow: 0 0 6px rgba(239, 68, 68, 0.4); } + 50% { opacity: 0.7; box-shadow: 0 0 16px rgba(239, 68, 68, 0.7); } +} + +/* ── Ambient / Decorative ───────────────────────────── */ +/* Slow breathing for background glow layers */ +@keyframes ambient-glow { + 0%, 100% { opacity: 0.3; } + 50% { opacity: 0.7; } +} + +/* Dynamic mesh drift — shifts gradient center positions */ +@keyframes bg-mesh-drift { + 0% { background-position: 0% 0%, 100% 100%, 50% 50%; } + 25% { background-position: 30% 20%, 70% 80%, 20% 60%; } + 50% { background-position: 60% 10%, 40% 60%, 80% 30%; } + 75% { background-position: 20% 60%, 80% 20%, 40% 80%; } + 100% { background-position: 0% 0%, 100% 100%, 50% 50%; } +} + +/* Slow hue rotation for orb glow */ +@keyframes hue-rotate-slow { + 0% { filter: hue-rotate(0deg); } + 50% { filter: hue-rotate(30deg); } + 100% { filter: hue-rotate(0deg); } +} + +/* Orb size pulsing */ +@keyframes orb-pulse { + 0%, 100% { transform: scale(1); opacity: 0.4; } + 50% { transform: scale(1.08); opacity: 0.6; } +} + +/* Expanded orb drift — wider, slower, organic */ +@keyframes orb-drift-enhanced { + 0% { transform: translate(0, 0) scale(1); } + 20% { transform: translate(80px, -50px) scale(1.1); } + 40% { transform: translate(-50px, 70px) scale(0.9); } + 60% { transform: translate(100px, 30px) scale(1.05); } + 80% { transform: translate(-70px, -60px) scale(0.95); } + 100% { transform: translate(0, 0) scale(1); } +} + +/* Grain/noise texture shifting */ +@keyframes grain-shift { + 0%, 100% { transform: translate(0, 0) rotate(0deg); } + 10% { transform: translate(-5%, -5%) rotate(0.5deg); } + 20% { transform: translate(-10%, 0%) rotate(-0.5deg); } + 30% { transform: translate(0%, 5%) rotate(1deg); } + 40% { transform: translate(5%, -3%) rotate(-0.3deg); } + 50% { transform: translate(-3%, -8%) rotate(0.8deg); } + 60% { transform: translate(8%, 3%) rotate(-0.6deg); } + 70% { transform: translate(-6%, 6%) rotate(0.4deg); } + 80% { transform: translate(4%, -6%) rotate(-0.7deg); } + 90% { transform: translate(-2%, 2%) rotate(0.2deg); } +} + +@keyframes gradient-shimmer { + 0% { background-position: 0% center; } + 100% { background-position: 200% center; } +} + +@keyframes float-variation { + 0%, 100% { transform: translateY(0) rotate(0deg); } + 33% { transform: translateY(-8px) rotate(1.5deg); } + 66% { transform: translateY(-4px) rotate(-1deg); } +} + +@keyframes fade-in-down { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ── Interactive ────────────────────────────────────── */ +@keyframes glow-border { + 0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } + 50% { box-shadow: 0 0 12px 1px rgba(59, 130, 246, 0.15); } +} + +@keyframes ripple { + 0% { transform: scale(0); opacity: 0.6; } + 100% { transform: scale(4); opacity: 0; } +} + +/* ── Bar pulses (audio / visualizer) ────────────────── */ +@keyframes bar-pulse-1 { + 0%, 100% { transform: scaleY(1); } + 50% { transform: scaleY(0.4); } +} +@keyframes bar-pulse-2 { + 0%, 100% { transform: scaleY(0.5); } + 50% { transform: scaleY(1.2); } +} +@keyframes bar-pulse-3 { + 0%, 100% { transform: scaleY(0.7); } + 50% { transform: scaleY(1); } +} + +@keyframes count-up { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + + +/* ════════════════════════════════════════════════════════ + 2. UTILITY ANIMATION CLASSES + Use these in Rust components: class="animate-breathe" + ════════════════════════════════════════════════════════ */ + +.animate-breathe { + animation: breathe 3s ease-in-out infinite; +} + +.animate-breathe-soft { + animation: breathe-soft 2s ease-in-out infinite; +} + +.animate-ring-pulse { + animation: ring-pulse 2s ease-in-out infinite; +} + +.animate-live-indicator { + animation: live-indicator 1.5s ease-in-out infinite; +} + +.animate-float { + animation: float-variation 5s ease-in-out infinite; +} + +.animate-gradient-shimmer { + background-size: 200% 100%; + animation: gradient-shimmer 4s ease-in-out infinite alternate; +} + +.animate-spin-slow { + animation: spin 3s linear infinite; +} + +.animate-pulse-connecting { + animation: pulse-connecting 1s ease-in-out infinite; +} + +.animate-scale-bounce { + animation: scale-bounce 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +.animate-pop-in { + animation: pop-in 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +.animate-fade-in-down { + animation: fade-in-down var(--transition-normal) ease-out; +} + +.animate-slide-in-left { + animation: slide-in-left var(--transition-normal) ease-out; +} + +.animate-count-up { + animation: count-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.animate-shimmer-sweep { + background: linear-gradient( + 110deg, + transparent 30%, + rgba(59, 130, 246, 0.08) 50%, + transparent 70% + ); + background-size: 200% 100%; + animation: processing-sweep 1.5s ease-in-out infinite; +} + + +/* ════════════════════════════════════════════════════════ + 3. APPLIED ANIMATIONS — auto-wired to selectors + ════════════════════════════════════════════════════════ */ + +/* ── Ambient animated mesh background ────────────────── */ +.app-shell::before { + content: ''; + position: fixed; + inset: -50%; + pointer-events: none; + z-index: -1; + background: + radial-gradient(ellipse at 20% 30%, rgba(59, 130, 246, 0.06) 0%, transparent 40%), + radial-gradient(ellipse at 80% 70%, rgba(99, 102, 241, 0.05) 0%, transparent 40%), + radial-gradient(ellipse at 40% 80%, rgba(16, 185, 129, 0.03) 0%, transparent 40%), + radial-gradient(ellipse at 60% 20%, rgba(245, 158, 11, 0.02) 0%, transparent 40%); + background-size: 200% 200%, 200% 200%, 200% 200%, 200% 200%; + animation: bg-mesh-drift 20s ease-in-out infinite alternate; + will-change: background-position; +} + +/* ── Subtle noise/grain texture overlay ─────────────── */ +.app-shell::after { + content: ''; + position: fixed; + inset: -50%; + pointer-events: none; + z-index: 0; + opacity: 0.015; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); + background-repeat: repeat; + background-size: 256px 256px; + animation: grain-shift 0.5s steps(4) infinite; + will-change: transform; +} + +/* ── Light theme mesh adjustment ────────────────────── */ +[data-theme="light"] .app-shell::before { + background: + radial-gradient(ellipse at 20% 30%, rgba(37, 99, 235, 0.04) 0%, transparent 40%), + radial-gradient(ellipse at 80% 70%, rgba(99, 102, 241, 0.04) 0%, transparent 40%), + radial-gradient(ellipse at 40% 80%, rgba(16, 185, 129, 0.03) 0%, transparent 40%), + radial-gradient(ellipse at 60% 20%, rgba(245, 158, 11, 0.02) 0%, transparent 40%); +} +[data-theme="light"] .app-shell::after { + opacity: 0.008; +} + +/* ── Gradient text shimmer (brand elements) ─────────── */ +.sidebar-brand-name, +.live-title, +.auth-title { + background-size: 200% 100%; + animation: gradient-shimmer 6s ease-in-out infinite alternate; +} + +/* ── Connected status dot: alive breath ─────────────── */ +.status-dot, +.voice-connection-dot.connected { + animation: breathe 3s ease-in-out infinite; +} + +/* Keep existing connecting animation (overrides above) */ +.status-dot.is-connecting, +.voice-connection-dot.connecting { + animation: pulse-connecting 1s ease-in-out infinite !important; +} + +.status-dot.disconnected, +.voice-connection-dot.disconnected { + animation: none !important; +} + +/* ── Live indicator (red recording dot) ─────────────── */ +.msg-card-ai-badge.flagged::before, +.msg-analysis.flagged::before { + content: ''; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-error); + margin-right: 4px; + vertical-align: middle; + animation: live-indicator 1.5s ease-in-out infinite; +} + +/* ── Particle orbs: ambient breathing overlay ───────── */ +.particle-bg { + animation: ambient-glow 6s ease-in-out infinite alternate; +} + +/* ── Live bento grid: staggered entry ───────────────── */ +.live-bento > * { + animation: fade-in-up 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.live-bento > :nth-child(1) { animation-delay: 0.03s; } +.live-bento > :nth-child(2) { animation-delay: 0.06s; } +.live-bento > :nth-child(3) { animation-delay: 0.09s; } +.live-bento > :nth-child(4) { animation-delay: 0.12s; } +.live-bento > :nth-child(5) { animation-delay: 0.15s; } +.live-bento > :nth-child(6) { animation-delay: 0.18s; } + +/* ── Dashboard metric cards: staggered entry ────────── */ +.dashboard-metric-card { + animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.dashboard-metric-card:nth-child(1) { animation-delay: 0.04s; } +.dashboard-metric-card:nth-child(2) { animation-delay: 0.08s; } +.dashboard-metric-card:nth-child(3) { animation-delay: 0.12s; } +.dashboard-metric-card:nth-child(4) { animation-delay: 0.16s; } +.dashboard-metric-card:nth-child(5) { animation-delay: 0.20s; } +.dashboard-metric-card:nth-child(6) { animation-delay: 0.24s; } +.dashboard-metric-card:nth-child(7) { animation-delay: 0.28s; } +.dashboard-metric-card:nth-child(8) { animation-delay: 0.32s; } + +/* ── Dashboard rows: staggered fade-in ──────────────── */ +.dashboard-summary-row, +.dashboard-top-channel-row { + animation: fade-in 0.4s ease-out both; +} +.dashboard-summary-row:nth-child(1), .dashboard-top-channel-row:nth-child(1) { animation-delay: 0.02s; } +.dashboard-summary-row:nth-child(2), .dashboard-top-channel-row:nth-child(2) { animation-delay: 0.04s; } +.dashboard-summary-row:nth-child(3), .dashboard-top-channel-row:nth-child(3) { animation-delay: 0.06s; } +.dashboard-summary-row:nth-child(4), .dashboard-top-channel-row:nth-child(4) { animation-delay: 0.08s; } +.dashboard-summary-row:nth-child(5), .dashboard-top-channel-row:nth-child(5) { animation-delay: 0.10s; } +.dashboard-summary-row:nth-child(6), .dashboard-top-channel-row:nth-child(6) { animation-delay: 0.12s; } +.dashboard-summary-row:nth-child(7), .dashboard-top-channel-row:nth-child(7) { animation-delay: 0.14s; } +.dashboard-summary-row:nth-child(8), .dashboard-top-channel-row:nth-child(8) { animation-delay: 0.16s; } +.dashboard-summary-row:nth-child(9), .dashboard-top-channel-row:nth-child(9) { animation-delay: 0.18s; } +.dashboard-summary-row:nth-child(10),.dashboard-top-channel-row:nth-child(10) { animation-delay: 0.20s; } + +/* ── Message cards: staggered entry ─────────────────── */ +.msg-card { + animation: fade-in-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.msg-card:nth-child(1) { animation-delay: 0.02s; } +.msg-card:nth-child(2) { animation-delay: 0.05s; } +.msg-card:nth-child(3) { animation-delay: 0.08s; } +.msg-card:nth-child(4) { animation-delay: 0.11s; } +.msg-card:nth-child(5) { animation-delay: 0.14s; } +.msg-card:nth-child(6) { animation-delay: 0.17s; } +.msg-card:nth-child(7) { animation-delay: 0.20s; } +.msg-card:nth-child(8) { animation-delay: 0.23s; } +.msg-card:nth-child(9) { animation-delay: 0.26s; } +.msg-card:nth-child(10) { animation-delay: 0.29s; } + +/* ── Recording items: slide in from left ────────────── */ +.rec-item { + animation: slide-in-left 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.rec-item:nth-child(1) { animation-delay: 0.02s; } +.rec-item:nth-child(2) { animation-delay: 0.06s; } +.rec-item:nth-child(3) { animation-delay: 0.10s; } +.rec-item:nth-child(4) { animation-delay: 0.14s; } +.rec-item:nth-child(5) { animation-delay: 0.18s; } + +/* ── Speaker items: pop in ──────────────────────────── */ +.speak-item { + animation: fade-in 0.3s ease-out both; +} +.speak-item:nth-child(1) { animation-delay: 0.02s; } +.speak-item:nth-child(2) { animation-delay: 0.05s; } +.speak-item:nth-child(3) { animation-delay: 0.08s; } +.speak-item:nth-child(4) { animation-delay: 0.11s; } +.speak-item:nth-child(5) { animation-delay: 0.14s; } + +/* ── Audio visualizer bars: individual timing ───────── */ +.audio-bar { + animation: bar-pulse-2 1.2s ease-in-out infinite; +} +.audio-bar:nth-child(odd) { + animation-name: bar-pulse-1; + animation-duration: 0.9s; +} +.audio-bar:nth-child(3n) { + animation-name: bar-pulse-3; + animation-duration: 1.5s; +} + +/* ── Interactive cards: hover glow effect ────────────── */ +.card-interactive:hover { + animation: glow-border 0.8s ease-in-out; + border-color: var(--color-primary); +} + +/* ── Nav active indicator: continuous subtle glow ───── */ +.sidebar-nav-item.is-active::after { + animation: indicator-grow 250ms cubic-bezier(0.16, 1, 0.3, 1) both, + breathe-soft 3s ease-in-out 0.3s infinite; +} + +/* ── Filter chip active: pop scale ──────────────────── */ +.filter-chip-v2.is-active { + animation: pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +/* ── AI badge processing: shimmer sweep ─────────────── */ +.msg-card-ai-badge.processing, +.status-badge-processing { + background: linear-gradient( + 110deg, + var(--color-primary-muted) 25%, + rgba(59, 130, 246, 0.35) 50%, + var(--color-primary-muted) 75% + ) !important; + background-size: 200% 100% !important; + animation: processing-sweep 1.4s ease-in-out infinite; +} +.status-badge-processing::before { + animation: ring-pulse 1.2s ease-in-out infinite !important; +} + +/* ── Image grid items: reveal ───────────────────────── */ +.image-grid-item { + animation: fade-in 0.4s ease-out both; +} +.image-grid-item:nth-child(1) { animation-delay: 0.02s; } +.image-grid-item:nth-child(2) { animation-delay: 0.05s; } +.image-grid-item:nth-child(3) { animation-delay: 0.08s; } +.image-grid-item:nth-child(4) { animation-delay: 0.11s; } +.image-grid-item:nth-child(5) { animation-delay: 0.14s; } +.image-grid-item:nth-child(6) { animation-delay: 0.17s; } +.image-grid-item:nth-child(7) { animation-delay: 0.20s; } +.image-grid-item:nth-child(8) { animation-delay: 0.23s; } + +/* ── Dashboard queue value: count-up feel ───────────── */ +.dashboard-queue-value { + animation: count-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Voice connection panel: entry ──────────────────── */ +.voice-connection { + animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Now playing: slide in ──────────────────────────── */ +.np-body { + animation: slide-in-left 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Skeleton: enhanced shimmer ─────────────────────── */ +.skeleton, +.msg-skel-avatar, +.msg-skel-line, +.msg-skel-badge, +.dashboard-skel-avatar, +.dashboard-skel-line { + background: linear-gradient( + 110deg, + var(--surface-container) 28%, + rgba(59, 130, 246, 0.06) 48%, + var(--surface-container) 68% + ) !important; + background-size: 200% 100% !important; +} + +/* ── Tab content: stronger entry ────────────────────── */ +.tab-content { + animation: fade-in-up 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +/* ── Mobile tab active: subtle indicator ────────────── */ +.mobile-tab-item.is-active { + animation: pop-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} + +/* ── Mascot launcher: enhanced float ────────────────── */ +.mascot-launcher { + animation: float-variation 5s ease-in-out infinite; +} +.mascot-launcher:hover { + animation: none !important; +} + +/* ── Empty state icons: subtle float ────────────────── */ +.empty-state-icon { + animation: float-variation 6s ease-in-out infinite; +} + +/* ── Recent message action buttons ──────────────────── */ +.btn-icon-sm:active:not(:disabled), +.btn-icon:active:not(:disabled) { + animation: ripple 0.4s ease-out; +} + +/* ── Small decorative: channel list rows ────────────── */ +.dashboard-top-channel-row:hover { + animation: none; /* Keep the existing hover translateX */ +} + + +/* ════════════════════════════════════════════════════════ + 4. RESPECT REDUCED MOTION + ════════════════════════════════════════════════════════ */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/services/frontend/frontend/src/styles/dashboard.css b/services/frontend/frontend/src/styles/dashboard.css index 94b0e40..6b31224 100644 --- a/services/frontend/frontend/src/styles/dashboard.css +++ b/services/frontend/frontend/src/styles/dashboard.css @@ -189,3 +189,16 @@ .dashboard-stats-grid { grid-template-columns: 1fr; } .dashboard-wide-card { grid-column: span 1; } } + +/* ── Legacy aliases ──────────────────────────────────── */ +.dashboard-stats { display: flex; flex-wrap: wrap; gap: var(--space-4); } +.dashboard-top-channels, .dashboard-summary-list { display: flex; flex-direction: column; gap: var(--space-2); } +.dashboard-top-channel-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--surface-container); color: var(--text-secondary); font-size: 0.875rem; transition: all var(--transition-fast); } +.dashboard-top-channel-row:hover { background: var(--surface-overlay); transform: translateX(2px); } +.dashboard-moderation-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); } +.dashboard-queue-value { font-size: 1.75rem; font-weight: 800; color: var(--text-primary); letter-spacing: -0.03em; } +.dashboard-queue-label { margin-top: var(--space-1); color: var(--text-secondary); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; } +.dashboard-list-card { overflow: hidden; } +.dashboard-list-toolbar { margin-bottom: var(--space-4); } + +.badge-secondary { background: var(--color-primary-muted); color: var(--color-primary); } diff --git a/services/frontend/frontend/src/styles/live.css b/services/frontend/frontend/src/styles/live.css index f9d3fcd..bafd3f0 100644 --- a/services/frontend/frontend/src/styles/live.css +++ b/services/frontend/frontend/src/styles/live.css @@ -298,3 +298,19 @@ flex-direction: column; gap: var(--space-2); } + +/* ── Legacy aliases ──────────────────────────────────── */ +.live-panel { display: flex; flex-direction: column; gap: var(--space-6); } +.live-grid-3 { grid-template-columns: repeat(3, 1fr); } + +.recordings-sub-panel { overflow: hidden; } + +.audio-visualizer { display: flex; align-items: flex-end; justify-content: center; height: 80px; gap: 2px; padding: var(--space-2); } +.audio-visualizer-bars { display: flex; align-items: flex-end; gap: 2px; height: 100%; width: 100%; } +.audio-bar { width: 8px; background: var(--gradient-primary); border-radius: var(--radius-sm) var(--radius-sm) 0 0; transition: height 100ms ease; } + +.mic-level-meter { display: flex; flex-direction: column; gap: var(--space-2); } +.mic-row { display: flex; align-items: center; gap: var(--space-2); } + +@media (max-width: 1024px) { .live-grid-3 { grid-template-columns: repeat(2, 1fr); } } +@media (max-width: 768px) { .live-grid-3 { grid-template-columns: 1fr; } } diff --git a/services/frontend/frontend/src/styles/messages.css b/services/frontend/frontend/src/styles/messages.css index 6a7c4d5..fce873f 100644 --- a/services/frontend/frontend/src/styles/messages.css +++ b/services/frontend/frontend/src/styles/messages.css @@ -189,6 +189,87 @@ color: var(--color-ai-pending); } +/* ── Reply indicator (Discord-style reference block) ──── */ +.msg-row-reply { + display: flex; + align-items: stretch; + margin: 0.375rem 0 0.625rem; + border-radius: 6px; + overflow: hidden; + cursor: default; + transition: background 150ms ease; +} +.msg-row-reply:hover { + background: rgba(59, 130, 246, 0.04); +} +.msg-row-reply-line { + width: 3px; + flex-shrink: 0; + border-radius: 2px; + background: linear-gradient(180deg, #3b82f6, #8b5cf6); + opacity: 0.6; +} +.msg-row-reply-main { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.625rem; + min-width: 0; + flex: 1; + font-size: 0.75rem; + line-height: 1.4; +} +.msg-row-reply-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.625rem; + font-weight: 700; + color: white; + background: linear-gradient(135deg, #3b82f6, #6366f1); + text-transform: uppercase; + box-shadow: 0 1px 3px rgba(59, 130, 246, 0.25); +} +.msg-row-reply-label { + flex-shrink: 0; + color: var(--text-tertiary); + font-weight: 450; + opacity: 0.8; +} +.msg-row-reply-user { + flex-shrink: 0; + font-weight: 700; + color: #3b82f6; + letter-spacing: -0.01em; +} +.msg-row-reply-snippet { + color: var(--text-tertiary); + font-style: italic; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; + opacity: 0.9; + display: inline-flex; + align-items: center; + gap: 0; +} +.msg-row-reply-snippet::before { + content: '"'; + opacity: 0.5; + margin-right: 1px; +} +.msg-row-reply-snippet::after { + content: '"'; + opacity: 0.5; + margin-left: 1px; +} + /* ── Message Content ─────────────────────────────────── */ .msg-card-content { font-size: 0.875rem; @@ -485,3 +566,47 @@ vertical-align: middle; object-fit: contain; } + +/* ── Legacy aliases (components still use these) ────── */ +.panel-card { background: var(--surface-raised); border: 1px solid var(--surface-border); border-radius: var(--radius-md); overflow: hidden; box-shadow: var(--shadow-sm); transition: all var(--transition-normal); } +.panel-card-head { padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--surface-border); } +.panel-card-title { font-size: 1rem; font-weight: 600; color: var(--text-primary); } +.panel-card-desc { font-size: 0.875rem; color: var(--text-secondary); margin-top: var(--space-1); } + +.search-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); } +.search-wrap { position: relative; flex: 1; min-width: 200px; } +.search-icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); color: var(--text-tertiary); pointer-events: none; } +.search-input { width: 100%; padding: 0.5rem 0.75rem 0.5rem 2.25rem; height: 36px; background: var(--surface-container); border: 1px solid transparent; border-radius: var(--radius-pill); color: var(--text-primary); font-family: inherit; font-size: 0.875rem; transition: all var(--transition-fast); outline: none; } +.search-input::placeholder { color: var(--text-tertiary); } +.search-input:focus { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-muted); background: var(--surface-raised); } + +.filter-group { display: flex; align-items: center; gap: 0.375rem; margin-left: auto; } +.filter-chip-v2 { padding: 0.25rem 0.75rem; border-radius: var(--radius-pill); font-size: 0.6875rem; font-weight: 500; border: 1px solid transparent; background: var(--surface-hover); color: var(--text-tertiary); cursor: pointer; transition: all var(--transition-fast); font-family: inherit; } +.filter-chip-v2:hover { color: var(--text-secondary); background: var(--surface-container); } +.filter-chip-v2.is-active { background: var(--gradient-primary); color: white; } + +.msg-row { padding: var(--space-3) 0; border-bottom: 1px solid var(--surface-border); transition: all var(--transition-fast); } +.msg-row:hover { background: var(--surface-hover); margin: 0 calc(-1 * var(--space-4)); padding-left: var(--space-4); padding-right: var(--space-4); border-radius: var(--radius-sm); } +.msg-row:last-child { border-bottom: none; } +.msg-row-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; } +.msg-row-time { font-size: 0.6875rem; color: var(--text-tertiary); font-variant-numeric: tabular-nums; min-width: 48px; } +.msg-row-badge { display: inline-flex; align-items: center; gap: 0.125rem; font-size: 0.75rem; } +.msg-row-badge.edited { color: var(--text-secondary); } +.msg-row-badge.deleted { color: var(--color-error); } +.msg-row-status { margin-left: auto; display: flex; align-items: center; gap: var(--space-1); } +.msg-row-body { font-size: 0.875rem; line-height: 1.5rem; white-space: pre-wrap; word-break: break-word; } +.msg-row-body.is-deleted { opacity: 0.6; color: var(--text-secondary); } + +.msg-card-head { display: flex; align-items: baseline; gap: var(--space-2); margin-bottom: var(--space-2); } +.msg-card-name { font-size: 0.875rem; font-weight: 600; color: var(--text-primary); } +.msg-card-messages > * + * { margin-top: 0.625rem; padding-top: 0.625rem; border-top: 1px solid var(--surface-border); } +.msg-card-messages.separated > * + * { margin-top: 0.625rem; padding-top: 0.625rem; border-top: 1px solid var(--surface-border); } + +.msg-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); } +.msg-retry-hint { font-size: 0.75rem; color: var(--text-tertiary); opacity: 0.7; } + +.msg-sticker-placeholder { display: flex; width: 48px; height: 48px; align-items: center; justify-content: center; border-radius: var(--radius-sm); border: 1px solid var(--surface-border); } + +.msg-analysis-icon { margin-top: 0.125rem; flex-shrink: 0; } + +.img-empty { display: flex; align-items: center; justify-content: center; height: 128px; color: var(--text-secondary); font-style: italic; } diff --git a/services/frontend/frontend/src/styles/polish.css b/services/frontend/frontend/src/styles/polish.css index d2e3416..64095fc 100644 --- a/services/frontend/frontend/src/styles/polish.css +++ b/services/frontend/frontend/src/styles/polish.css @@ -11,50 +11,63 @@ .particle-orb { position: absolute; border-radius: 50%; - opacity: 0.5; - will-change: transform; + will-change: transform, filter; } .particle-orb:nth-child(1) { width: 600px; height: 600px; top: -200px; right: -150px; - background: radial-gradient(circle, var(--color-primary) 0%, transparent 70%); + background: radial-gradient(circle, rgba(59, 130, 246, 0.5) 0%, transparent 70%); filter: blur(100px); - animation: orb-drift 25s ease-in-out infinite; + animation: orb-drift-enhanced 30s ease-in-out infinite, + hue-rotate-slow 12s ease-in-out infinite, + orb-pulse 8s ease-in-out infinite; } .particle-orb:nth-child(2) { width: 500px; height: 500px; bottom: -150px; left: -150px; - background: radial-gradient(circle, #5865f2 0%, transparent 70%); + background: radial-gradient(circle, rgba(99, 102, 241, 0.5) 0%, transparent 70%); filter: blur(100px); - animation: orb-drift 30s ease-in-out infinite reverse; + animation: orb-drift-enhanced 35s ease-in-out infinite reverse, + hue-rotate-slow 14s ease-in-out infinite reverse, + orb-pulse 10s ease-in-out infinite; + animation-delay: -5s; } .particle-orb:nth-child(3) { width: 350px; height: 350px; top: 40%; left: 60%; - background: radial-gradient(circle, #6366f1 0%, transparent 70%); + background: radial-gradient(circle, rgba(99, 102, 241, 0.4) 0%, transparent 70%); filter: blur(100px); - animation: orb-drift 35s ease-in-out infinite; - animation-delay: -8s; + animation: orb-drift-enhanced 40s ease-in-out infinite, + hue-rotate-slow 16s ease-in-out infinite, + orb-pulse 6s ease-in-out infinite; + animation-delay: -10s; } .particle-orb:nth-child(4) { width: 250px; height: 250px; top: 10%; left: 20%; - background: radial-gradient(circle, var(--color-success) 0%, transparent 70%); - opacity: 0.3; + background: radial-gradient(circle, rgba(16, 185, 129, 0.4) 0%, transparent 70%); filter: blur(100px); - animation: orb-drift 20s ease-in-out infinite; + animation: orb-drift-enhanced 25s ease-in-out infinite reverse, + hue-rotate-slow 18s ease-in-out infinite, + orb-pulse 12s ease-in-out infinite; animation-delay: -3s; } -@keyframes orb-drift { - 0% { transform: translate(0, 0); } - 25% { transform: translate(40px, -30px); } - 50% { transform: translate(-30px, 20px); } - 75% { transform: translate(20px, -40px); } - 100% { transform: translate(0, 0); } +.particle-orb:nth-child(5) { + width: 180px; height: 180px; + top: 70%; left: 30%; + background: radial-gradient(circle, rgba(59, 130, 246, 0.35) 0%, transparent 70%); + filter: blur(80px); + animation: orb-drift-enhanced 20s ease-in-out infinite, + hue-rotate-slow 10s ease-in-out infinite, + orb-pulse 5s ease-in-out infinite; + animation-delay: -7s; } -@media (max-width: 768px) { .particle-bg { display: none; } } +@media (max-width: 768px) { + .particle-bg { display: none; } + .app-shell::after { display: none; } +} /* ── Theme Toggle ────────────────────────────────────── */ .theme-toggle-btn { diff --git a/services/frontend/frontend/src/styles/reset.css b/services/frontend/frontend/src/styles/reset.css index e20b797..0a5f669 100644 --- a/services/frontend/frontend/src/styles/reset.css +++ b/services/frontend/frontend/src/styles/reset.css @@ -13,7 +13,7 @@ html { } body { - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.6; min-height: 100vh; overflow-x: hidden; diff --git a/services/frontend/frontend/src/styles/utilities.css b/services/frontend/frontend/src/styles/utilities.css index 101ec9b..ec02ea9 100644 --- a/services/frontend/frontend/src/styles/utilities.css +++ b/services/frontend/frontend/src/styles/utilities.css @@ -218,3 +218,56 @@ .grid-cols-2 { grid-template-columns: 1fr; } .grid-cols-3 { grid-template-columns: 1fr; } } + +/* ── Extra Utilities (component-used aliases) ────────── */ +.active { background: var(--gradient-primary); color: white; } +.is-active { background: var(--gradient-primary); color: white; } +.is-deleted { opacity: 0.6; } +.is-connecting { animation: pulse-connecting 1s ease-in-out infinite; } +.is-scroll { overflow-x: auto; flex-wrap: nowrap; } +.separated > * + * { margin-top: 0.625rem; padding-top: 0.625rem; border-top: 1px solid var(--surface-border); } +.tall { height: 112px; width: 64px; } +.typing { animation: pulse-dot 1.5s ease-in-out infinite; } +.mascot { font-variant-numeric: tabular-nums; } + +.ml-0\.5 { margin-left: 2px; } +.space-y-2 > * + * { margin-top: var(--space-2); } +.tracking-tight { letter-spacing: -0.025em; } +.text-foreground { color: var(--text-primary); } +.text-muted-foreground { color: var(--text-tertiary); } +.text-destructive { color: var(--color-error); } +.text-\[10px\] { font-size: 10px; } +.h-3\.5 { height: 14px; } +.w-3\.5 { width: 14px; } + +.bg-background { background: var(--surface-base); } +.bg-card { background: var(--surface-raised); } +.border-input { border-color: var(--surface-border); } + +.head-left { display: flex; align-items: center; gap: var(--space-3); } +.head-right { display: flex; align-items: center; gap: var(--space-4); margin-left: auto; } +.head-status { display: flex; align-items: center; gap: var(--space-1); font-size: 0.75rem; color: var(--text-secondary); } +.head-status-dot { width: 8px; height: 8px; border-radius: 50%; transition: all var(--transition-fast); } + +.card-bordered { border: 1px solid var(--surface-border); } +.card-elevated { box-shadow: var(--shadow-md); } + +.empty-state-icon { font-size: 3rem; margin-bottom: var(--space-4); opacity: 0.5; } + +.input-error { border-color: var(--color-error); } +.input-soft { background: var(--surface-container); border: 1px solid transparent; } + +.theme-toggle { width: 44px; height: 44px; border: 1px solid var(--surface-border); border-radius: var(--radius-md); background: var(--surface-overlay); color: var(--text-tertiary); cursor: pointer; font-size: 1.25rem; display: flex; align-items: center; justify-content: center; transition: all var(--transition-fast); } +.theme-toggle:hover { color: var(--color-primary); border-color: var(--color-primary); } + +.mascot-controls { display: flex; align-items: center; gap: var(--space-2); } +.mascot-inline { display: flex; align-items: center; gap: var(--space-1); } + +.placeholder-muted-foreground::placeholder { color: var(--text-tertiary); } + +.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +@media (max-width: 768px) { .md\:grid-cols-2 { grid-template-columns: 1fr; } } + +.bg-destructive\/15 { background: rgba(239, 68, 68, 0.15); } +.border-border\/50 { border-color: rgba(255, 255, 255, 0.03); } +[data-theme="light"] .border-border\/50 { border-color: rgba(0, 0, 0, 0.03); } diff --git a/services/frontend/shared-types/src/message.rs b/services/frontend/shared-types/src/message.rs index a363d75..d6f47d7 100644 --- a/services/frontend/shared-types/src/message.rs +++ b/services/frontend/shared-types/src/message.rs @@ -45,6 +45,29 @@ pub struct MessageMetadata { pub embeds: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, +} + +/// Information about a referenced (replied-to) message. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ReferenceInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub message_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub guild_id: Option, + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ref_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub replied_username: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub replied_user_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -197,6 +220,8 @@ pub struct MessageRecord { pub channel_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub thread_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reference_message_id: Option, pub user_id: String, pub username: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -206,6 +231,10 @@ pub struct MessageRecord { pub edited_content: Option, #[serde(rename = "type")] pub msg_type: String, // "text" | "edited" | "deleted" + #[serde(skip_serializing_if = "Option::is_none")] + pub is_reply: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_forward: Option, pub created_at: i64, #[serde(skip_serializing_if = "Option::is_none")] pub edited_at: Option,