This document contains all the necessary React components and CSS required to recreate the Command Center and Captain Console in your separate Next.js project.
You can simply copy the code blocks below into their respective files in your Next.js app/ router structure.
Append these dashboard-specific CSS classes to your global stylesheet to handle the tactical grid, radar animations, and badge coloring.
/* Dashboard UI Additions */
.dashboard-page {
min-height: 100vh;
padding: 2rem 5%;
background: radial-gradient(circle at top, #0f1b34, #081120 75%);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 1.5rem;
z-index: 2;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
}
.dashboard-header h1 {
font-size: 1.4rem;
color: var(--cyan);
}
.dashboard-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 1.5rem;
flex: 1;
}
.dashboard-panel {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.dashboard-panel h2 {
font-size: 1.1rem;
color: var(--teal);
text-transform: uppercase;
border-bottom: 1px solid var(--line);
padding-bottom: 0.5rem;
}
.data-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.data-item {
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(15, 23, 42, 0.5);
border: 1px solid rgba(148, 163, 184, 0.15);
padding: 0.85rem 1rem;
border-radius: 0.5rem;
}
.data-item-danger {
border-color: rgba(239, 68, 68, 0.4);
background: rgba(239, 68, 68, 0.05);
}
.data-item-warning {
border-color: rgba(249, 115, 22, 0.4);
background: rgba(249, 115, 22, 0.05);
}
.badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.05em;
}
.badge-safe { background: rgba(45, 212, 191, 0.15); color: var(--teal); border: 1px solid rgba(45, 212, 191, 0.3); }
.badge-warning { background: rgba(249, 115, 22, 0.15); color: var(--orange); border: 1px solid rgba(249, 115, 22, 0.3); }
.badge-danger { background: rgba(239, 68, 68, 0.15); color: var(--red); border: 1px solid rgba(239, 68, 68, 0.3); }
.map-placeholder {
flex: 1;
border: 1px solid rgba(60, 244, 255, 0.15);
border-radius: 0.5rem;
background: radial-gradient(circle at center, rgba(60, 244, 255, 0.05) 0%, transparent 70%);
display: grid;
place-items: center;
position: relative;
min-height: 300px;
}
.radar-circle {
width: 200px;
height: 200px;
border-radius: 50%;
border: 1px solid rgba(60, 244, 255, 0.3);
position: relative;
}
.radar-circle::after {
content: '';
position: absolute;
top: 50%; left: 50%;
width: 50%; height: 2px;
background: linear-gradient(90deg, var(--cyan), transparent);
transform-origin: 0 50%;
animation: spin 4s linear infinite;
}
.distress-button {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--red);
color: var(--red);
font-size: 1.2rem;
padding: 1.5rem;
border-radius: 0.75rem;
cursor: pointer;
text-transform: uppercase;
font-weight: bold;
letter-spacing: 0.1em;
transition: all 0.3s ease;
width: 100%;
}
.distress-button:hover {
background: rgba(239, 68, 68, 0.2);
box-shadow: 0 0 20px rgba(239, 68, 68, 0.4);
}
.captain-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
@media (max-width: 768px) {
.dashboard-grid, .captain-grid {
grid-template-columns: 1fr;
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}This component renders the grid and animations. (Requires framer-motion to be installed in the Next.js project).
'use client'
import { motion } from 'framer-motion'
export default function TacticalBackground() {
return (
<div className="tactical-bg" aria-hidden="true">
<div className="tactical-grid" />
<div className="radar-sweep" />
<div className="ambient-glow ambient-glow--one" />
<div className="ambient-glow ambient-glow--two" />
<div className="scanline-layer" />
<motion.div
className="particle particle--one"
animate={{ opacity: [0.2, 0.8, 0.2], y: [0, -10, 0] }}
transition={{ duration: 5, repeat: Infinity, ease: 'easeInOut' }}
/>
<motion.div
className="particle particle--two"
animate={{ opacity: [0.1, 0.6, 0.1], x: [0, 8, 0] }}
transition={{ duration: 6, repeat: Infinity, ease: 'easeInOut' }}
/>
<motion.div
className="particle particle--three"
animate={{ opacity: [0.15, 0.7, 0.15], y: [0, 14, 0] }}
transition={{ duration: 7, repeat: Infinity, ease: 'easeInOut' }}
/>
</div>
)
}The main dashboard for fleet tracking. Note that react-router-dom has been replaced with Next.js next/link.
'use client'
import Link from 'next/link'
import { motion } from 'framer-motion'
import TacticalBackground from '@/components/TacticalBackground'
const mockVessels = [
{ id: 'V-01', name: 'Nautilus Alpha', status: 'SAFE', speed: '24 knots', lat: '45.12N', lng: '12.45W' },
{ id: 'V-02', name: 'Leviathan Echo', status: 'WARNING', speed: '18 knots', lat: '42.88N', lng: '11.02W' },
{ id: 'V-03', name: 'Poseidon Vector', status: 'DISTRESS', speed: '0 knots', lat: '40.05N', lng: '14.99W' },
]
const mockIncidents = [
{ id: 'INC-99', type: 'PIRACY', severity: 'CRITICAL', location: 'Sector 4' },
{ id: 'INC-102', type: 'WEATHER', severity: 'MEDIUM', location: 'Sector 9' },
]
export default function CommandPage() {
return (
<div className="dashboard-page">
<TacticalBackground />
<motion.header
className="dashboard-header tactical-panel"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
>
<div>
<p className="eyebrow">FLEETOPS AI</p>
<h1>Command Center</h1>
</div>
<Link href="/" className="tactical-button link-button" style={{ width: 'auto' }}>
Terminate Session
</Link>
</motion.header>
<div className="dashboard-grid">
<motion.div className="dashboard-panel tactical-panel" initial={{ x: -20, opacity: 0 }} animate={{ x: 0, opacity: 1 }}>
<h2>Tactical Map</h2>
<div className="map-placeholder">
<div className="radar-circle"></div>
<p className="spline-note" style={{ top: '1rem', right: '1rem', bottom: 'auto', left: 'auto' }}>Live Telemetry: Online</p>
</div>
<h2 style={{ marginTop: '1rem' }}>Active Directives</h2>
<div className="data-list">
<div className="data-item">
<div>
<p style={{ fontWeight: 'bold' }}>REROUTE TO AVOID STORM</p>
<p className="eyebrow" style={{ marginBottom: 0, marginTop: '4px' }}>Target: Leviathan Echo</p>
</div>
<span className="badge badge-warning">PENDING ACK</span>
</div>
</div>
</motion.div>
<motion.div className="dashboard-panel tactical-panel" initial={{ x: 20, opacity: 0 }} animate={{ x: 0, opacity: 1 }}>
<h2>Fleet Status</h2>
<div className="data-list">
{mockVessels.map(v => (
<div key={v.id} className={`data-item ${v.status === 'DISTRESS' ? 'data-item-danger' : v.status === 'WARNING' ? 'data-item-warning' : ''}`}>
<div>
<p style={{ fontWeight: 'bold' }}>{v.name}</p>
<p className="eyebrow" style={{ marginBottom: 0, marginTop: '4px' }}>{v.speed} | {v.lat}, {v.lng}</p>
</div>
<span className={`badge badge-${v.status.toLowerCase()}`}>{v.status}</span>
</div>
))}
</div>
<h2 style={{ marginTop: '1.5rem' }}>Global Incidents</h2>
<div className="data-list">
{mockIncidents.map(inc => (
<div key={inc.id} className={`data-item ${inc.severity === 'CRITICAL' ? 'data-item-danger' : 'data-item-warning'}`}>
<div>
<p style={{ fontWeight: 'bold' }}>{inc.type}</p>
<p className="eyebrow" style={{ marginBottom: 0, marginTop: '4px' }}>{inc.location}</p>
</div>
<span className={`badge badge-${inc.severity === 'CRITICAL' ? 'danger' : 'warning'}`}>{inc.severity}</span>
</div>
))}
</div>
</motion.div>
</div>
</div>
)
}The isolated vessel dashboard. Also updated for Next.js routing.
'use client'
import Link from 'next/link'
import { motion } from 'framer-motion'
import { useState } from 'react'
import TacticalBackground from '@/components/TacticalBackground'
export default function CaptainPage() {
const [distressSent, setDistressSent] = useState(false)
const [acknowledged, setAcknowledged] = useState(false)
const handleDistress = () => setDistressSent(true)
const handleAck = () => setAcknowledged(true)
return (
<div className="dashboard-page">
<TacticalBackground />
<motion.header
className="dashboard-header tactical-panel"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
>
<div>
<p className="eyebrow">VESSEL V-02 | LEVIATHAN ECHO</p>
<h1>Captain Console</h1>
</div>
<Link href="/" className="tactical-button link-button" style={{ width: 'auto' }}>
Terminate Session
</Link>
</motion.header>
<div className="captain-grid">
<motion.div className="dashboard-panel tactical-panel" initial={{ x: -20, opacity: 0 }} animate={{ x: 0, opacity: 1 }}>
<h2>Vessel Telemetry</h2>
<div className="data-list">
<div className="data-item">
<span className="eyebrow" style={{ marginBottom: 0 }}>Coordinates</span>
<span style={{ fontFamily: 'Orbitron', color: 'var(--cyan)' }}>42.88N, 11.02W</span>
</div>
<div className="data-item">
<span className="eyebrow" style={{ marginBottom: 0 }}>Heading</span>
<span style={{ fontFamily: 'Orbitron', color: 'var(--cyan)' }}>274° NW</span>
</div>
<div className="data-item">
<span className="eyebrow" style={{ marginBottom: 0 }}>Speed</span>
<span style={{ fontFamily: 'Orbitron', color: 'var(--cyan)' }}>18 KNOTS</span>
</div>
<div className="data-item">
<span className="eyebrow" style={{ marginBottom: 0 }}>Hull Integrity</span>
<span style={{ fontFamily: 'Orbitron', color: 'var(--teal)' }}>98%</span>
</div>
</div>
<h2 style={{ marginTop: '1.5rem', color: 'var(--red)' }}>Emergency Broadcast</h2>
<button
className="distress-button"
onClick={handleDistress}
disabled={distressSent}
style={{ opacity: distressSent ? 0.5 : 1, cursor: distressSent ? 'not-allowed' : 'pointer' }}
>
{distressSent ? 'DISTRESS SIGNAL ACTIVE' : 'BROADCAST DISTRESS'}
</button>
</motion.div>
<motion.div className="dashboard-panel tactical-panel" initial={{ x: 20, opacity: 0 }} animate={{ x: 0, opacity: 1 }}>
<h2>Incoming Directives</h2>
<div className="data-list">
<div className={`data-item ${!acknowledged ? 'data-item-warning' : ''}`} style={{ flexDirection: 'column', alignItems: 'flex-start', gap: '1rem' }}>
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<p style={{ fontWeight: 'bold' }}>REROUTE TO AVOID STORM</p>
<span className={`badge badge-${acknowledged ? 'safe' : 'warning'}`}>
{acknowledged ? 'ACKNOWLEDGED' : 'ACTION REQUIRED'}
</span>
</div>
<p style={{ color: 'var(--muted)', fontSize: '0.9rem' }}>
New coordinates: 43.10N, 11.50W. Severe weather anomaly detected on current heading. Deviate course immediately and confirm.
</p>
{!acknowledged && (
<button className="tactical-button" onClick={handleAck}>
Acknowledge Directive
</button>
)}
</div>
</div>
</motion.div>
</div>
</div>
)
}