Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
<meta name="theme-color" content="#3b82f6" />
|
<meta name="theme-color" content="#3b82f6" />
|
||||||
<title>IMPHNEN -- Discord Moderation</title>
|
<title>IMPHNEN -- Discord Moderation</title>
|
||||||
<link data-trunk rel="rust" data-crate="frontend" data-wasm="frontend.wasm" />
|
<link data-trunk rel="rust" data-crate="frontend" data-wasm="frontend.wasm" />
|
||||||
<link data-trunk rel="css" href="src/styles/main.css" />
|
<link data-trunk rel="css" href="src/styles/bundle.css" />
|
||||||
<link data-trunk rel="copy-dir" href="public/" />
|
<link data-trunk rel="copy-dir" href="public/" />
|
||||||
<!-- Inter + JetBrains Mono fonts -->
|
<!-- Inter + JetBrains Mono fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use leptos::prelude::*;
|
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 std::sync::Arc;
|
||||||
|
|
||||||
use super::message_actions::ReanalyzeButton;
|
use super::message_actions::ReanalyzeButton;
|
||||||
@@ -79,6 +79,39 @@ pub fn MessageRow(
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.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! {
|
view! {
|
||||||
<div class="msg-row">
|
<div class="msg-row">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -104,6 +137,29 @@ pub fn MessageRow(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 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! {
|
||||||
|
<div class="msg-row-reply">
|
||||||
|
<div class="msg-row-reply-line"></div>
|
||||||
|
<div class="msg-row-reply-main">
|
||||||
|
<span class="msg-row-reply-avatar">{initial}</span>
|
||||||
|
<span class="msg-row-reply-label">"Replying to"</span>
|
||||||
|
<span class="msg-row-reply-user">"@" {r_user_cloned}</span>
|
||||||
|
{(!snippet.as_deref().unwrap_or("").is_empty()).then(|| {
|
||||||
|
let s = snippet.unwrap_or_default();
|
||||||
|
view! {
|
||||||
|
<span class="msg-row-reply-snippet">{s}</span>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
{show.then(|| {
|
{show.then(|| {
|
||||||
let rendered = render_emojis(display);
|
let rendered = render_emojis(display);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -189,3 +189,16 @@
|
|||||||
.dashboard-stats-grid { grid-template-columns: 1fr; }
|
.dashboard-stats-grid { grid-template-columns: 1fr; }
|
||||||
.dashboard-wide-card { grid-column: span 1; }
|
.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); }
|
||||||
|
|||||||
@@ -298,3 +298,19 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-2);
|
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; } }
|
||||||
|
|||||||
@@ -189,6 +189,87 @@
|
|||||||
color: var(--color-ai-pending);
|
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 ─────────────────────────────────── */
|
/* ── Message Content ─────────────────────────────────── */
|
||||||
.msg-card-content {
|
.msg-card-content {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@@ -485,3 +566,47 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
object-fit: contain;
|
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; }
|
||||||
|
|||||||
@@ -11,50 +11,63 @@
|
|||||||
.particle-orb {
|
.particle-orb {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
opacity: 0.5;
|
will-change: transform, filter;
|
||||||
will-change: transform;
|
|
||||||
}
|
}
|
||||||
.particle-orb:nth-child(1) {
|
.particle-orb:nth-child(1) {
|
||||||
width: 600px; height: 600px;
|
width: 600px; height: 600px;
|
||||||
top: -200px; right: -150px;
|
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);
|
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) {
|
.particle-orb:nth-child(2) {
|
||||||
width: 500px; height: 500px;
|
width: 500px; height: 500px;
|
||||||
bottom: -150px; left: -150px;
|
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);
|
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) {
|
.particle-orb:nth-child(3) {
|
||||||
width: 350px; height: 350px;
|
width: 350px; height: 350px;
|
||||||
top: 40%; left: 60%;
|
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);
|
filter: blur(100px);
|
||||||
animation: orb-drift 35s ease-in-out infinite;
|
animation: orb-drift-enhanced 40s ease-in-out infinite,
|
||||||
animation-delay: -8s;
|
hue-rotate-slow 16s ease-in-out infinite,
|
||||||
|
orb-pulse 6s ease-in-out infinite;
|
||||||
|
animation-delay: -10s;
|
||||||
}
|
}
|
||||||
.particle-orb:nth-child(4) {
|
.particle-orb:nth-child(4) {
|
||||||
width: 250px; height: 250px;
|
width: 250px; height: 250px;
|
||||||
top: 10%; left: 20%;
|
top: 10%; left: 20%;
|
||||||
background: radial-gradient(circle, var(--color-success) 0%, transparent 70%);
|
background: radial-gradient(circle, rgba(16, 185, 129, 0.4) 0%, transparent 70%);
|
||||||
opacity: 0.3;
|
|
||||||
filter: blur(100px);
|
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;
|
animation-delay: -3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes orb-drift {
|
.particle-orb:nth-child(5) {
|
||||||
0% { transform: translate(0, 0); }
|
width: 180px; height: 180px;
|
||||||
25% { transform: translate(40px, -30px); }
|
top: 70%; left: 30%;
|
||||||
50% { transform: translate(-30px, 20px); }
|
background: radial-gradient(circle, rgba(59, 130, 246, 0.35) 0%, transparent 70%);
|
||||||
75% { transform: translate(20px, -40px); }
|
filter: blur(80px);
|
||||||
100% { transform: translate(0, 0); }
|
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 ────────────────────────────────────── */
|
||||||
.theme-toggle-btn {
|
.theme-toggle-btn {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
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;
|
line-height: 1.6;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|||||||
@@ -218,3 +218,56 @@
|
|||||||
.grid-cols-2 { grid-template-columns: 1fr; }
|
.grid-cols-2 { grid-template-columns: 1fr; }
|
||||||
.grid-cols-3 { 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); }
|
||||||
|
|||||||
@@ -45,6 +45,29 @@ pub struct MessageMetadata {
|
|||||||
pub embeds: Option<Vec<EmbedInfo>>,
|
pub embeds: Option<Vec<EmbedInfo>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub channel: Option<ChannelRef>,
|
pub channel: Option<ChannelRef>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reference: Option<ReferenceInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub channel_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub guild_id: Option<String>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub ref_type: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub content: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub replied_username: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub replied_user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
@@ -197,6 +220,8 @@ pub struct MessageRecord {
|
|||||||
pub channel_id: String,
|
pub channel_id: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub thread_id: Option<String>,
|
pub thread_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reference_message_id: Option<String>,
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -206,6 +231,10 @@ pub struct MessageRecord {
|
|||||||
pub edited_content: Option<String>,
|
pub edited_content: Option<String>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub msg_type: String, // "text" | "edited" | "deleted"
|
pub msg_type: String, // "text" | "edited" | "deleted"
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub is_reply: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub is_forward: Option<bool>,
|
||||||
pub created_at: i64,
|
pub created_at: i64,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub edited_at: Option<i64>,
|
pub edited_at: Option<i64>,
|
||||||
|
|||||||
Reference in New Issue
Block a user