+
{deferredSearchQuery
? t("pages.agent.agents.no_results", "No agents found")
: t("pages.agent.agents.no_agents", "No agents yet")}
-
+
{deferredSearchQuery
? t("pages.agent.agents.no_results_hint", "Try a different search")
: t("pages.agent.agents.no_agents_hint", "Create an agent to get started")}
@@ -198,23 +207,23 @@ export function AgentsPage({ embedded = false }: AgentsPageProps = {}) {
if (!open) setAgentToDelete(null)
}}
>
-
+
{t("pages.agent.agents.confirm_delete", "Delete Agent?")}
-
+
{t(
"pages.agent.agents.confirm_delete_message",
`Are you sure you want to delete "${agentToDelete?.name}"? This action cannot be undone.`,
)}
-
+
{t("common.cancel", "Cancel")}
{t("common.delete", "Delete")}
@@ -234,7 +243,7 @@ export function AgentsPage({ embedded = false }: AgentsPageProps = {}) {
}
return (
-
+
{mainContent}
diff --git a/web/frontend/src/components/agent/cockpit/cockpit-page.tsx b/web/frontend/src/components/agent/cockpit/cockpit-page.tsx
index 15ff13705..fbedd793f 100644
--- a/web/frontend/src/components/agent/cockpit/cockpit-page.tsx
+++ b/web/frontend/src/components/agent/cockpit/cockpit-page.tsx
@@ -1,6 +1,11 @@
import dayjs from "dayjs"
-import { useMemo } from "react"
-import { IconArrowRight, IconLayoutDashboard, IconUsers, IconBrain, IconFlask } from "@tabler/icons-react"
+import { useEffect, useMemo, useState } from "react"
+import { motion } from "motion/react"
+import {
+ IconLayoutDashboard, IconUsers, IconBrain, IconFlask,
+ IconCircleDot, IconActivity, IconCpu, IconDatabase, IconBolt,
+ IconShield, IconTerminal, IconWifi, IconLock, IconTrendingUp,
+} from "@tabler/icons-react"
import { usePicoChat } from "@/hooks/use-pico-chat"
import { Badge } from "@/components/ui/badge"
@@ -13,23 +18,439 @@ import { AgentsPage } from "../agents"
import { SkillsPage } from "../skills"
import { ResearchPage } from "../research/research-page"
-function reasonLabel(reasonCode?: string) {
- switch (reasonCode) {
- case "requires_subagent":
- return "Requires subagent runtime"
- case "requires_skills":
- return "Requires skills support"
- case "requires_mcp_discovery":
- return "Requires MCP discovery"
- case "requires_linux":
- return "Linux only"
- case "requires_serial_platform":
- return "Unsupported serial platform"
- default:
- return reasonCode ?? ""
- }
+const TAB_CONFIG = [
+ { key: "tools" as const, icon: IconLayoutDashboard, label: "Tools" },
+ { key: "skills" as const, icon: IconBrain, label: "Skills" },
+ { key: "agents" as const, icon: IconUsers, label: "Agents" },
+ { key: "research" as const, icon: IconFlask, label: "Research" },
+]
+
+/* ─── 3D Holographic Circle Component ─── */
+function HolographicCircle() {
+ const [rotation, setRotation] = useState(0)
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setRotation((prev) => (prev + 0.5) % 360)
+ }, 50)
+ return () => clearInterval(interval)
+ }, [])
+
+ return (
+
+ {/* Outer Glow */}
+
+
+ {/* Rotating Rings - 3D Perspective */}
+
+ {[0, 1, 2, 3, 4].map((index) => (
+
+ ))}
+
+ {/* Inner Core */}
+
+
+ {/* Scanning Line */}
+
+
+
+ {/* Center Display */}
+
+
+ AI
+
+
Core Online
+
+ {/* Data Points around circle */}
+ {[0, 45, 90, 135, 180, 225, 270, 315].map((angle) => (
+
+ ))}
+
+
+ {/* Holographic Scan Lines */}
+
+
+
+
+ )
}
+/* ─── Particle Field Component ─── */
+function ParticleField() {
+ const particles = useMemo(
+ () =>
+ Array.from({ length: 40 }, (_, i) => ({
+ id: i,
+ x: Math.random() * 100,
+ y: Math.random() * 100,
+ size: Math.random() * 3 + 1,
+ duration: Math.random() * 10 + 10,
+ delay: Math.random() * 5,
+ })),
+ []
+ )
+
+ return (
+
+ {particles.map((p) => (
+
+ ))}
+
+ {/* Connection Lines */}
+
+
+ )
+}
+
+/* ─── Metric Card Component ─── */
+function MetricCard({ icon, label, value, trend }: { icon: React.ReactNode; label: string; value: string; trend: string }) {
+ const isPositive = trend.startsWith("+")
+
+ return (
+
+ {/* Animated Background */}
+
+
+ {/* Content */}
+
+
+ {/* Scan Line */}
+
+
+ {/* Corner Accent */}
+
+
+ )
+}
+
+/* ─── Data Panel Component ─── */
+function DataPanel({ title, items }: {
+ title: string
+ items: Array<{ label: string; status: string; progress: number }>
+}) {
+ return (
+
+ {/* Header */}
+
+
{title}
+
+ {[0, 1, 2].map((i) => (
+
+ ))}
+
+
+
+ {/* Items */}
+
+ {items.map((item, index) => (
+
+
+ {item.label}
+
+ {item.status}
+
+
+
+ {/* Progress Bar */}
+
+
+
+
+
+ ))}
+
+
+ {/* Decorative Corner */}
+
+
+ )
+}
+
+/* ─── Circular Progress Component ─── */
+function CircularProgress({ icon, label, percentage, color = "cyan" }: {
+ icon: React.ReactNode; label: string; percentage: number; color?: "cyan" | "blue" | "green"
+}) {
+ const radius = 70
+ const circumference = 2 * Math.PI * radius
+ const offset = circumference - (percentage / 100) * circumference
+
+ const colorMap = {
+ cyan: { stroke: "#00bcff", glow: "#00bcff99", bg: "#00bcff1a" },
+ blue: { stroke: "#0080ff", glow: "#0080ff99", bg: "#0080ff1a" },
+ green: { stroke: "#00ff88", glow: "#00ff8899", bg: "#00ff881a" },
+ }
+ const colors = colorMap[color]
+
+ return (
+
+
+
+
+
+ {icon}
+
+
+ {percentage}%
+
+
+
+ {label}
+
+
+
+ )
+}
+
+/* ─── System Status Grid Component ─── */
+function SystemStatusGrid() {
+ const statusItems = [
+ { icon:
, label: "CONNECTION", value: "STABLE", status: "good" },
+ { icon:
, label: "STORAGE", value: "67% FREE", status: "good" },
+ { icon:
, label: "SECURITY", value: "ENCRYPTED", status: "good" },
+ { icon:
, label: "PERFORMANCE", value: "OPTIMAL", status: "good" },
+ ]
+
+ return (
+
+ {statusItems.map((item, index) => (
+
+ {/* Hover Glow */}
+
+
+
+
+
{item.label}
+
{item.value}
+
+
+ {/* Scan Effect */}
+
+
+ ))}
+
+ )
+}
+
+/* ─── Terminal Output Component ─── */
+function TerminalOutput() {
+ return (
+
+
+
+ TERMINAL
+
+
+
+ > Initializing core systems...
+
+
+ > All systems operational
+
+
+ > Awaiting commands...
+
+
+ |
+
+
+
+ )
+}
+
+/* ═══════════════════════════════════════════════
+ MAIN COCKPIT PAGE
+ ═══════════════════════════════════════════════ */
export function CockpitPage() {
const { activeSessionId } = usePicoChat()
const {
@@ -48,141 +469,291 @@ export function CockpitPage() {
)
return (
-
- {/* Ghost Background Typography */}
-
- COCKPIT
- SYSTEM
-
+
+ {/* ── 3D Perspective Grid Background ── */}
+
- {/* Header */}
-
-
- Terminal Status
- CONNECTED / {dayjs().format('DD.MM.YYYY')}
-
-
- Runtime Epoch
- {dayjs().format('HH:mm')} GMT+1
-
-
+ {/* ── Particle Field ── */}
+
-
-
+ {/* ── Scanline Effect ── */}
+
- {/* Main Workspace */}
-
- {/* Hero Section */}
-
-
- AGENT INTERFACE
-
-
- Active control node for autonomous agents. Managing tool surfaces, memory networks.
-
+ {/* ── Corner Accents ── */}
+
+
+
+
+
+ {/* ── Main Content ── */}
+
+ {/* Header */}
+
+
+
+ AFRICA
+
+
+ ADVANCED FRAMEWORK FOR INTELLIGENT SYSTEM
+
+
+
+
+
+ SYSTEMS ONLINE
-
-
-
-
-
-
+
+ {dayjs().format("DD.MM.YYYY")} / {dayjs().format("HH:mm")} UTC
+
+
- {activeTab === "skills" &&
}
+ {/* Tab Navigation */}
+
+ {TAB_CONFIG.map(({ key, icon: Icon, label }) => (
+
+ ))}
+
- {activeTab === "agents" &&
}
+ {/* ── Main Grid Layout ── */}
+
+ {/* Left Column - Holographic Display + System Status */}
+ {activeTab === "tools" && (
+
+ {/* Main Holographic Circle */}
+
+
+
- {activeTab === "research" && }
+ {/* System Status Cards */}
+
+
+ )}
+ {/* Center Column - Main Content Area */}
+
+ {/* Tools Tab Content */}
{activeTab === "tools" && (
-
- {/* Tool Grid */}
-
-
-
-
Active Modules
-
Tool Grid
+ <>
+ {/* Top Metrics */}
+
+ }
+ label="CPU LOAD"
+ value="47%"
+ trend="+2.3%"
+ />
+ }
+ label="MEMORY"
+ value="8.2 GB"
+ trend="-1.1%"
+ />
+ }
+ label="POWER"
+ value="89%"
+ trend="+5.2%"
+ />
+ }
+ label="NETWORK"
+ value="12.4 MB/s"
+ trend="+8.7%"
+ />
+
+
+ {/* Active Protocols / Tool Status */}
+
+ items.slice(0, 4).map((tool) => ({
+ label: tool.name,
+ status: tool.status === "enabled" ? "ACTIVE" : "STANDBY",
+ progress: tool.status === "enabled" ? 94 : 30,
+ }))
+ )}
+ />
+
+ {/* Memory Network */}
+
+
-
-
- {groupedTools.flatMap(([, items]) =>
- items.map((tool) => (
-
-
-
-
- {tool.name}
-
-
- {tool.status}
-
+ {/* Tool Grid - Compact */}
+
+
+ TOOL REGISTRY
+ {filteredToolCount} Active
+
+
+ {groupedTools.flatMap(([, items]) =>
+ items.map((tool) => (
+
+
+
+
+ {tool.name}
+
+
+
toggleTool(tool.name, checked)}
+ className="data-[state=checked]:bg-[#00bcff] scale-75"
+ />
-
toggleTool(tool.name, checked)}
+
+ {tool.description}
+
+
+ {tool.status}
+
+
+ {/* Scan Line */}
+
-
- {tool.description}
-
-
- {tool.config_key}
- {(tool as any).reason_code && (
- {reasonLabel((tool as any).reason_code)}
- )}
+ ))
+ )}
+
+
+ >
+ )}
+
+ {/* Skills Tab */}
+ {activeTab === "skills" &&
}
+
+ {/* Agents Tab */}
+ {activeTab === "agents" &&
}
+
+ {/* Research Tab */}
+ {activeTab === "research" &&
}
+
+
+ {/* Right Column - Circular Stats + Terminal */}
+ {activeTab === "tools" && (
+
+ }
+ label="DEFENSE"
+ percentage={92}
+ color="cyan"
+ />
+ }
+ label="UPTIME"
+ percentage={99}
+ color="blue"
+ />
+ }
+ label="CONNECTIVITY"
+ percentage={87}
+ color="cyan"
+ />
+
+ {/* Subagent Manifest */}
+
+
+
+ Subagents
+
+
+ {sessionSubagents === null ? (
+
+
+ Loading...
+
+ ) : sessionSubagents.length === 0 ? (
+
No subagents active
+ ) : (
+ sessionSubagents.map((task) => (
+
+
+
+ {task.label || task.id}
+
+
+ {task.status}
+
+
+
+ {dayjs(task.created).format("HH:mm:ss")}
))
@@ -190,79 +761,12 @@ export function CockpitPage() {
- {/* Memory Network */}
-
-
-
- Relational Map
-
Memory Network
-
-
-
-
-
-
-
- )}
-
-
- {/* Right Sidebar */}
-
+ {/* Terminal Output */}
+
+
+ )}
-
- {/* Footer */}
-
)
-}
+}
\ No newline at end of file
diff --git a/web/frontend/src/components/agent/cockpit/memory-graph.tsx b/web/frontend/src/components/agent/cockpit/memory-graph.tsx
index cb6cfb9fd..2f92e512b 100644
--- a/web/frontend/src/components/agent/cockpit/memory-graph.tsx
+++ b/web/frontend/src/components/agent/cockpit/memory-graph.tsx
@@ -1,4 +1,5 @@
import { useMemo, useState } from "react"
+import { motion } from "motion/react"
import type {
PicoMemoryGraphEdge,
@@ -28,11 +29,11 @@ const GROUP_COLUMNS: Record
= {
}
function groupColor(group: string) {
- if (group === "memory") return "#72f0a0"
- if (group.startsWith("daily-")) return "#4dd0e1"
- if (group === "tool") return "#f8d66d"
- if (group === "media") return "#f395d6"
- return "#90e89f"
+ if (group === "memory") return "#00bcff"
+ if (group.startsWith("daily-")) return "#22d3ee"
+ if (group === "tool") return "#38bdf8"
+ if (group === "media") return "#7dd3fc"
+ return "#06b6d4"
}
function computeLayout(nodes: PicoMemoryGraphNode[]): PositionedNode[] {
@@ -76,69 +77,133 @@ export function MemoryGraph({ nodes, edges }: MemoryGraphProps) {
positionedNodes.find((node) => node.id === selectedId) ?? positionedNodes[0]
return (
-
-
+
+
+ {/* Holographic scan line overlay */}
+
+
-
+ {/* Node Detail Panel */}
+
{selectedNode ? (
<>
-
+
Node Focus
-
+
{selectedNode.label}
{selectedNode.kind}
-
+
{selectedNode.preview || "No extra preview for this node yet."}
-
-
+
+
Cluster
-
+
{selectedNode.group}
-
+
Weight
-
+
{selectedNode.weight ?? 1}
>
) : (
-
No graph data available.
+
No graph data available.
)}
)
-}
+}
\ No newline at end of file
diff --git a/web/frontend/src/components/agent/skills/skills-page.tsx b/web/frontend/src/components/agent/skills/skills-page.tsx
index 6ddcbc12b..70275f461 100644
--- a/web/frontend/src/components/agent/skills/skills-page.tsx
+++ b/web/frontend/src/components/agent/skills/skills-page.tsx
@@ -1,5 +1,6 @@
import { useDeferredValue, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
+import { motion } from "motion/react"
import type { SkillSupportItem } from "@/api/skills"
import { useCockpitSkills } from "@/hooks/use-cockpit-skills"
import { PageHeader } from "@/components/page-header"
@@ -56,7 +57,14 @@ export function SkillsPage({ embedded = false }: SkillsPageProps) {
if (isLoading) {
return (
-
Loading skills...
+
+
+ Loading skills...
+
)
}
@@ -64,33 +72,38 @@ export function SkillsPage({ embedded = false }: SkillsPageProps) {
if (isError) {
return (
-
Failed to load skills. Please try again.
+
Failed to load skills. Please try again.
)
}
const mainContent = (
-
+
+
+ {filteredSkills.length} Skills Loaded
-
+
{filteredSkills.length === 0 ? (
-
-
+
+
{deferredSearchQuery
? t("pages.agent.skills.no_results", "No skills found")
: t("pages.agent.skills.no_skills", "No skills installed")}
-
+
{deferredSearchQuery
? t("pages.agent.skills.no_results_hint", "Try a different search")
: t("pages.agent.skills.no_skills_hint", "Install skills via the CLI or import them")}
@@ -114,23 +127,23 @@ export function SkillsPage({ embedded = false }: SkillsPageProps) {
open={skillToDelete !== null}
onOpenChange={(open) => { if (!open) setSkillToDelete(null) }}
>
-
+
{t("pages.agent.skills.confirm_delete", "Delete Skill?")}
-
+
{t(
"pages.agent.skills.confirm_delete_message",
`Are you sure you want to delete "${skillToDelete?.name}"? This action cannot be undone.`,
)}
-
+
{t("common.cancel", "Cancel")}
{t("common.delete", "Delete")}
@@ -149,7 +162,7 @@ export function SkillsPage({ embedded = false }: SkillsPageProps) {
}
return (
-
+
{mainContent}
@@ -157,4 +170,4 @@ export function SkillsPage({ embedded = false }: SkillsPageProps) {
{modals}
)
-}
+}
\ No newline at end of file
diff --git a/web/frontend/src/components/chat/chat-page.tsx b/web/frontend/src/components/chat/chat-page.tsx
index 3031e9c9e..542cb9c9e 100644
--- a/web/frontend/src/components/chat/chat-page.tsx
+++ b/web/frontend/src/components/chat/chat-page.tsx
@@ -53,6 +53,30 @@ function readFileAsDataUrl(file: File): Promise
{
})
}
+/** Background Jarvis Orb — large semi-transparent orb behind the chat messages */
+function BackgroundOrb() {
+ return (
+
+ {/* Main orb glow - centered in the viewport */}
+
+ {/* Outermost ring - very subtle */}
+
+ {/* Core glow */}
+
+
+ {/* Ambient side glows */}
+
+
+
+ )
+}
+
function resolveChatInputDisabledReason({
hasDefaultModel,
connectionState,
@@ -62,46 +86,16 @@ function resolveChatInputDisabledReason({
connectionState: ConnectionState
gatewayState: GatewayState
}): ChatInputDisabledReason | null {
- if (gatewayState === "unknown") {
- return "gatewayUnknown"
- }
-
- if (gatewayState === "starting") {
- return "gatewayStarting"
- }
-
- if (gatewayState === "restarting") {
- return "gatewayRestarting"
- }
-
- if (gatewayState === "stopping") {
- return "gatewayStopping"
- }
-
- if (gatewayState === "stopped") {
- return "gatewayStopped"
- }
-
- if (gatewayState === "error") {
- return "gatewayError"
- }
-
- if (connectionState === "connecting") {
- return "websocketConnecting"
- }
-
- if (connectionState === "error") {
- return "websocketError"
- }
-
- if (connectionState === "disconnected") {
- return "websocketDisconnected"
- }
-
- if (!hasDefaultModel) {
- return "noDefaultModel"
- }
-
+ if (gatewayState === "unknown") return "gatewayUnknown"
+ if (gatewayState === "starting") return "gatewayStarting"
+ if (gatewayState === "restarting") return "gatewayRestarting"
+ if (gatewayState === "stopping") return "gatewayStopping"
+ if (gatewayState === "stopped") return "gatewayStopped"
+ if (gatewayState === "error") return "gatewayError"
+ if (connectionState === "connecting") return "websocketConnecting"
+ if (connectionState === "error") return "websocketError"
+ if (connectionState === "disconnected") return "websocketDisconnected"
+ if (!hasDefaultModel) return "noDefaultModel"
return null
}
@@ -199,19 +193,15 @@ export function ChatPage() {
// Check for permission requests in messages
useEffect(() => {
- if (permissionRequest) return // Already showing a permission prompt
-
- // Check the last few messages for request_permission tool calls
+ if (permissionRequest) return
for (let i = messages.length - 1; i >= Math.max(0, messages.length - 5); i--) {
const msg = messages[i]
if (msg.role !== "assistant") continue
if (!msg.toolCalls) continue
-
const permCall = msg.toolCalls.find(
(tc) => tc.function?.name === "request_permission"
)
if (!permCall || !permCall.function) continue
-
try {
const args = JSON.parse(permCall.function.arguments ?? "{}")
if (args.path && args.command) {
@@ -222,25 +212,16 @@ export function ChatPage() {
})
return
}
- } catch {
- // Ignore parse errors
- }
+ } catch { /* ignore */ }
}
}, [messages, permissionRequest])
- const handlePermissionGranted = () => {
- setPermissionRequest(null)
- }
-
- const handlePermissionDenied = () => {
- setPermissionRequest(null)
- }
-
+ const handlePermissionGranted = () => setPermissionRequest(null)
+ const handlePermissionDenied = () => setPermissionRequest(null)
const handleAddImages = () => {
if (!canInput) return
fileInputRef.current?.click()
}
-
const handleRemoveAttachment = (index: number) => {
setAttachments((prev) => prev.filter((_, itemIndex) => itemIndex !== index))
}
@@ -298,11 +279,11 @@ export function ChatPage() {
canInput && (Boolean(input.trim()) || attachments.length > 0)
return (
-
+
-
-
+
+
{t("chat.showAssistantDetails")}
{t("chat.newChat")}
@@ -358,9 +339,15 @@ export function ChatPage() {
-
+ {/* Background orb behind text */}
+
+
+ {/* Scan line overlay */}
+
+
+
{messages.length === 0 && !isTyping && (
)
-}
+}
\ No newline at end of file
diff --git a/web/frontend/src/components/logs/logs-page.tsx b/web/frontend/src/components/logs/logs-page.tsx
index 853da223a..1f2d2640a 100644
--- a/web/frontend/src/components/logs/logs-page.tsx
+++ b/web/frontend/src/components/logs/logs-page.tsx
@@ -26,6 +26,7 @@ export function LogsPage() {
size="sm"
onClick={clearLogs}
disabled={logs.length === 0 || clearing}
+ className="border-[rgba(0,212,255,0.3)] bg-[rgba(0,212,255,0.05)] text-[#00d4ff] hover:bg-[rgba(0,212,255,0.1)]"
>
{t("pages.logs.clear")}
diff --git a/web/frontend/src/index.css b/web/frontend/src/index.css
index fc55a3a32..752cdecc6 100644
--- a/web/frontend/src/index.css
+++ b/web/frontend/src/index.css
@@ -45,92 +45,67 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
+ --color-jarvis-cyan: #00d4ff;
+ --color-jarvis-blue: #0ea5e9;
+ --color-jarvis-light: #22d3ee;
+ --color-jarvis-surface: #0f172a;
+ --color-jarvis-glow: rgba(0, 212, 255, 0.15);
}
:root {
--radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
+ --background: #0a0e17;
+ --foreground: #e0f2fe;
+ --card: rgba(6, 20, 40, 0.7);
+ --card-foreground: #e0f2fe;
+ --popover: #111827;
+ --popover-foreground: #e0f2fe;
+ --primary: #00d4ff;
+ --primary-foreground: #0a0e17;
+ --secondary: #1e293b;
+ --secondary-foreground: #e0f2fe;
+ --muted: #1e293b;
+ --muted-foreground: #94a3b8;
+ --accent: rgba(0, 212, 255, 0.1);
+ --accent-foreground: #00d4ff;
+ --destructive: #ef4444;
+ --border: rgba(0, 212, 255, 0.15);
+ --input: rgba(0, 212, 255, 0.1);
+ --ring: #00d4ff;
+ --chart-1: #00d4ff;
+ --chart-2: #0ea5e9;
+ --chart-3: #22d3ee;
+ --chart-4: #06b6d4;
+ --chart-5: #0891b2;
+ --sidebar: #0f172a;
+ --sidebar-foreground: #e0f2fe;
+ --sidebar-primary: #00d4ff;
+ --sidebar-primary-foreground: #0a0e17;
+ --sidebar-accent: rgba(0, 212, 255, 0.1);
+ --sidebar-accent-foreground: #00d4ff;
+ --sidebar-border: rgba(0, 212, 255, 0.15);
+ --sidebar-ring: #00d4ff;
}
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.205 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
+/* Force dark mode always */
+html {
+ color-scheme: dark;
}
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: thin;
- scrollbar-color: var(--border) transparent;
+ scrollbar-color: rgba(0, 212, 255, 0.3) transparent;
}
body {
@apply bg-background text-foreground;
}
- /* WebKit Custom Scrollbar */
::-webkit-scrollbar {
- width: 6px;
- height: 6px;
+ width: 4px;
+ height: 4px;
}
::-webkit-scrollbar-track {
@@ -138,12 +113,12 @@
}
::-webkit-scrollbar-thumb {
- background: var(--border);
+ background: rgba(0, 212, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
- background: var(--muted-foreground);
+ background: rgba(0, 212, 255, 0.5);
}
}
@@ -157,25 +132,242 @@
height: calc(100svh - 3.5rem);
}
-/* Typing indicator animations */
-@keyframes shimmer {
- 0% {
- background-position: 200% 0;
- }
+/* ===== JARVIS CUSTOM ANIMATIONS ===== */
- 100% {
- background-position: -200% 0;
- }
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
}
@keyframes fadeSlideIn {
- 0% {
- opacity: 0;
- transform: translateY(4px);
- }
-
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
+ 0% { opacity: 0; transform: translateY(4px); }
+ 100% { opacity: 1; transform: translateY(0); }
}
+
+@keyframes pulse-glow {
+ 0%, 100% { box-shadow: 0 0 5px rgba(0, 212, 255, 0.2), 0 0 10px rgba(0, 212, 255, 0.1); }
+ 50% { box-shadow: 0 0 15px rgba(0, 212, 255, 0.4), 0 0 30px rgba(0, 212, 255, 0.15); }
+}
+
+@keyframes pulse-glow-text {
+ 0%, 100% { text-shadow: 0 0 5px rgba(0, 212, 255, 0.4); }
+ 50% { text-shadow: 0 0 20px rgba(0, 212, 255, 0.7), 0 0 40px rgba(0, 212, 255, 0.3); }
+}
+
+@keyframes breathe {
+ 0%, 100% { transform: scale(1); opacity: 0.8; }
+ 50% { transform: scale(1.05); opacity: 1; }
+}
+
+@keyframes orb-rotate {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+@keyframes orb-rotate-reverse {
+ from { transform: rotate(360deg); }
+ to { transform: rotate(0deg); }
+}
+
+@keyframes typing-dot {
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
+ 30% { transform: translateY(-6px); opacity: 1; }
+}
+
+@keyframes hud-corner-flash {
+ 0%, 90%, 100% { opacity: 0.5; }
+ 95% { opacity: 1; }
+}
+
+@keyframes scan-line {
+ 0% { top: -2px; }
+ 100% { top: 100%; }
+}
+
+@keyframes float-up {
+ 0% { opacity: 0; transform: translateY(12px); }
+ 100% { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes ring-pulse {
+ 0%, 100% { stroke-opacity: 0.3; }
+ 50% { stroke-opacity: 0.8; }
+}
+
+@keyframes voiceBar {
+ 0% { height: 4px; opacity: 0.5; }
+ 100% { height: 16px; opacity: 1; }
+}
+
+@keyframes scan {
+ 0% { background-position: 0 0; }
+ 100% { background-position: 0 100%; }
+}
+
+@keyframes holographic-pulse {
+ 0%, 100% { opacity: 0.6; }
+ 50% { opacity: 1; }
+}
+
+/* ===== JARVIS UTILITY CLASSES ===== */
+
+.glass-panel {
+ background: rgba(6, 20, 40, 0.6);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid rgba(0, 212, 255, 0.15);
+}
+
+.glass-panel-strong {
+ background: rgba(6, 20, 40, 0.85);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ border: 1px solid rgba(0, 212, 255, 0.2);
+}
+
+.glow-border {
+ box-shadow: 0 0 5px rgba(0, 212, 255, 0.15), inset 0 0 5px rgba(0, 212, 255, 0.03);
+}
+
+.glow-border-active {
+ box-shadow: 0 0 10px rgba(0, 212, 255, 0.25), 0 0 20px rgba(0, 212, 255, 0.1), inset 0 0 8px rgba(0, 212, 255, 0.05);
+ border-color: rgba(0, 212, 255, 0.35);
+}
+
+.hud-corners {
+ position: relative;
+}
+
+.hud-corners::before,
+.hud-corners::after {
+ content: '';
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ border-color: rgba(0, 212, 255, 0.5);
+ animation: hud-corner-flash 4s ease-in-out infinite;
+ pointer-events: none;
+}
+
+.hud-corners::before {
+ top: -1px;
+ left: -1px;
+ border-top: 2px solid;
+ border-left: 2px solid;
+}
+
+.hud-corners::after {
+ bottom: -1px;
+ right: -1px;
+ border-bottom: 2px solid;
+ border-right: 2px solid;
+}
+
+.glow-text {
+ text-shadow: 0 0 10px rgba(0, 212, 255, 0.4), 0 0 20px rgba(0, 212, 255, 0.15);
+}
+
+.glow-text-pulse {
+ animation: pulse-glow-text 3s ease-in-out infinite;
+}
+
+.animate-breathe {
+ animation: breathe 4s ease-in-out infinite;
+}
+
+.animate-pulse-glow {
+ animation: pulse-glow 3s ease-in-out infinite;
+}
+
+.animate-float-up {
+ animation: float-up 0.4s ease-out forwards;
+}
+
+/* ===== HEX GRID BACKGROUND ===== */
+
+.hex-grid-bg {
+ background-image:
+ linear-gradient(30deg, rgba(0, 212, 255, 0.02) 12%, transparent 12.5%, transparent 87%, rgba(0, 212, 255, 0.02) 87.5%),
+ linear-gradient(150deg, rgba(0, 212, 255, 0.02) 12%, transparent 12.5%, transparent 87%, rgba(0, 212, 255, 0.02) 87.5%),
+ linear-gradient(30deg, rgba(0, 212, 255, 0.02) 12%, transparent 12.5%, transparent 87%, rgba(0, 212, 255, 0.02) 87.5%),
+ linear-gradient(150deg, rgba(0, 212, 255, 0.02) 12%, transparent 12.5%, transparent 87%, rgba(0, 212, 255, 0.02) 87.5%),
+ linear-gradient(60deg, rgba(0, 212, 255, 0.015) 25%, transparent 25.5%, transparent 75%, rgba(0, 212, 255, 0.015) 75%),
+ linear-gradient(60deg, rgba(0, 212, 255, 0.015) 25%, transparent 25.5%, transparent 75%, rgba(0, 212, 255, 0.015) 75%);
+ background-size: 40px 70px;
+ background-position: 0 0, 0 0, 20px 35px, 20px 35px, 0 0, 20px 35px;
+}
+
+/* ===== SCAN LINE ===== */
+
+.scan-line-overlay {
+ position: relative;
+ overflow: hidden;
+}
+
+.scan-line-overlay::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.1), transparent);
+ animation: scan-line 8s linear infinite;
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* Markdown in chat messages - Jarvis style */
+.jarvis-markdown h1, .jarvis-markdown h2, .jarvis-markdown h3 {
+ color: #00d4ff;
+ font-weight: 600;
+ margin-top: 1em;
+ margin-bottom: 0.5em;
+}
+
+.jarvis-markdown p {
+ margin-bottom: 0.5em;
+ line-height: 1.7;
+}
+
+.jarvis-markdown code {
+ background: rgba(0, 212, 255, 0.08);
+ border: 1px solid rgba(0, 212, 255, 0.12);
+ border-radius: 3px;
+ padding: 0.1em 0.4em;
+ font-size: 0.875em;
+ color: #22d3ee;
+}
+
+.jarvis-markdown pre {
+ background: rgba(0, 0, 0, 0.4) !important;
+ border: 1px solid rgba(0, 212, 255, 0.12) !important;
+ border-radius: 6px !important;
+ padding: 1em !important;
+ overflow-x: auto;
+ margin: 0.75em 0 !important;
+}
+
+.jarvis-markdown pre code {
+ background: none !important;
+ border: none !important;
+ padding: 0 !important;
+ color: #e0f2fe !important;
+}
+
+.jarvis-markdown a {
+ color: #00d4ff;
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+.jarvis-markdown strong {
+ color: #e0f2fe;
+ font-weight: 600;
+}
+
+.jarvis-markdown blockquote {
+ border-left: 3px solid rgba(0, 212, 255, 0.3);
+ padding-left: 1em;
+ color: #94a3b8;
+ margin: 0.75em 0;
+}
\ No newline at end of file