Files
AR-VMS-Seaman/docs/mockups/runtime_overview.html
T
alro65 deb04c9315 sprint-0: fundaciones VMS-Sailor
Sprint 0 completo del producto VMS-Sailor (Vessel Management System
integrado para buques 30-40m). Brief de referencia en
VMS_Sailor_v2_Parte_*.md (intacto).

Core (vmssailor.core, 95.17% coverage, 99 tests verde):
- ShipCoord: sistema naval x_pp/y_cl/z_bl frozen
- Vessel, Deck, Bulkhead
- Equipment, EquipmentModel, Sensor, EquipmentSpec
- Tag, AlarmConfig, TagBinding, Scaling
- CardInstance, Bus, Topology con validacion 21 puntos I/O AR-NMEA-IO-v1.0
- Alarm, PermissiveRule, Condition
- Project agregado raiz con validacion cross-entity
- Persistencia portable .vmsproj (SQLite) con roundtrip verificable

Biblioteca curada seed (vmssailor.library):
- systems_catalog.json completo (catalogo maestro Parte 1 sec 7)
- 2 vessels: Sunseeker 76, Ferretti 850
- 2 motores: MTU 12V 2000 M96, Volvo D13-900
- 1 genset: Northern Lights M65C13
- yacht_motor_planeo.yaml (reglas heuristicas)
- TODO marcado data_source=seed_estimate - requiere validacion datasheets

Tools:
- vms-validate-library: CLI valida biblioteca completa
- vms-generate-test-project: CLI demo + verificacion roundtrip persistencia

Design System + 8 mockups HTML estaticos:
- docs/design_system.md (paleta Deep Ocean, gradientes, typography, motion)
- docs/brand/ (logo + variantes SVG)
- docs/mockups/splash, studio_main, runtime_overview,
  runtime_mimic_fuel (P&ID animado), runtime_alarms, runtime_trim (panel
  estrella con horizonte artificial), mobile_overview, mobile_trim
- docs/mockups/index.html (galeria)

Firmware (Sprint 12+ implementacion):
- firmware/ar_nmea_io_v1/src/config/pinout.h con macros GPIO

Decisiones autonomas documentadas en docs/decisions_sprint0.md.

Stack: Python 3.11 + uv + Pydantic v2 + SQLite stdlib + hatchling +
pytest 9 + ruff + mypy. Sin PySide6, FastAPI, Flutter ni firmware
funcional (entran en sprints siguientes).

Criterio de aceptacion Sprint 0: cumplido.
- uv sync: OK
- pytest: 99/99 verde
- cov vmssailor.core: 95.17% (objetivo >=80%)
- ruff: clean
- vms-validate-library: OK
- vms-generate-test-project: INTEGRIDAD OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:26:06 -04:00

775 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VMS-Sailor Runtime · M/Y Aurora · Overview</title>
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
<link rel="stylesheet" href="_tokens.css">
<style>
body { overflow-x: hidden; }
.rt {
display: grid;
grid-template-rows: 64px 1fr 36px;
grid-template-columns: 240px 1fr;
grid-template-areas:
"topbar topbar"
"sidebar main"
"ticker ticker";
height: 100vh;
}
/* Topbar */
.topbar {
grid-area: topbar;
display: flex;
align-items: center;
gap: var(--s-5);
padding: 0 var(--s-5);
background: var(--c-midnight);
border-bottom: 1px solid var(--c-steel);
}
.vessel-id {
display: flex; align-items: center; gap: var(--s-3);
}
.vessel-id img { height: 32px; }
.vessel-id h1 {
margin: 0;
font-family: var(--f-display);
font-size: 18px;
font-weight: 600;
color: var(--c-foam);
line-height: 1;
}
.vessel-id .sub {
font-size: 11px;
color: var(--c-fog);
letter-spacing: 1.5px;
text-transform: uppercase;
}
.status-pill {
display: flex; align-items: center; gap: var(--s-2);
padding: 6px 14px;
background: rgba(0,224,138,0.12);
border: 1px solid rgba(0,224,138,0.4);
border-radius: var(--r-pill);
font-size: 12px;
font-weight: 600;
color: var(--c-ok);
letter-spacing: 0.5px;
}
.top-spacer { flex: 1; }
.top-meta {
display: flex; gap: var(--s-5); align-items: center;
font-family: var(--f-mono);
font-size: 12px;
color: var(--c-fog);
}
.top-meta strong { color: var(--c-sand); }
.alarm-chip {
display: flex; align-items: center; gap: var(--s-2);
padding: 6px 14px;
background: rgba(255,176,32,0.10);
border: 1px solid rgba(255,176,32,0.4);
border-radius: var(--r-pill);
font-size: 12px;
color: var(--c-warn);
font-weight: 700;
}
.user-chip {
display: flex; align-items: center; gap: var(--s-2);
padding: 6px 12px 6px 6px;
background: var(--c-steel);
border-radius: var(--r-pill);
font-size: 12px;
}
.avatar {
width: 26px; height: 26px;
border-radius: 50%;
background: var(--g-cyan);
color: #04111F;
display: flex; align-items: center; justify-content: center;
font-weight: 700;
font-size: 12px;
}
/* Sidebar */
.sidebar {
grid-area: sidebar;
background: var(--c-midnight);
border-right: 1px solid var(--c-steel);
overflow-y: auto;
padding: var(--s-4) 0;
}
.sb-section { padding: var(--s-4) var(--s-3); }
.sb-title {
font-size: 10px; font-weight: 700; letter-spacing: 2px;
color: var(--c-fog); text-transform: uppercase;
margin-bottom: var(--s-2); padding-left: var(--s-2);
}
.nav-item {
display: flex; align-items: center; gap: var(--s-3);
padding: 10px 12px;
border-radius: var(--r-2);
color: var(--c-sand); font-size: 13px;
cursor: pointer;
transition: background 120ms;
}
.nav-item:hover { background: var(--c-steel); }
.nav-item.active {
background: linear-gradient(90deg, rgba(0,217,255,0.12), transparent);
color: var(--c-foam);
box-shadow: inset 2px 0 0 var(--c-cyan);
}
.nav-item .ic { color: var(--c-fog); }
.nav-item.active .ic { color: var(--c-cyan); }
.nav-item .count {
margin-left: auto;
font-family: var(--f-mono);
font-size: 11px;
color: var(--c-fog);
}
.nav-item .alarm-badge {
margin-left: auto;
padding: 2px 8px;
background: var(--c-emergency);
color: white;
border-radius: var(--r-pill);
font-size: 10px;
font-weight: 700;
}
/* Main */
.main {
grid-area: main;
padding: var(--s-5);
overflow-y: auto;
background: var(--g-deep-sea);
}
.page-title {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: var(--s-5);
}
.page-title h2 {
margin: 0;
font-family: var(--f-display);
font-size: 28px;
font-weight: 600;
color: var(--c-foam);
}
.page-title .sub {
color: var(--c-fog); font-size: 13px; margin-top: 4px;
}
.clock {
font-family: var(--f-mono);
font-size: 32px;
color: var(--c-foam);
letter-spacing: 1px;
text-align: right;
}
.clock .date {
font-size: 12px; color: var(--c-fog);
letter-spacing: 2px;
}
/* Grid de cards */
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: var(--s-5);
}
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-6 { grid-column: span 6; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }
.stat {
background: var(--c-midnight);
border: 1px solid var(--c-steel);
border-radius: var(--r-3);
padding: var(--s-5);
box-shadow: var(--e-2);
position: relative;
overflow: hidden;
}
.stat::before {
content: ""; position: absolute;
top: 0; left: 0; right: 0; height: 2px;
background: var(--c-iron);
}
.stat.accent-cyan::before { background: var(--g-cyan); }
.stat.accent-ok::before { background: var(--g-ok); }
.stat.accent-warn::before { background: var(--g-warn); }
.stat-header {
display: flex; justify-content: space-between; align-items: flex-start;
margin-bottom: var(--s-3);
}
.stat-label {
font-size: 11px; font-weight: 600; letter-spacing: 1.5px;
color: var(--c-fog); text-transform: uppercase;
}
.stat-value {
font-family: var(--f-mono);
font-size: 44px;
font-weight: 600;
color: var(--c-foam);
line-height: 1;
letter-spacing: -1px;
}
.stat-unit {
font-size: 16px;
color: var(--c-fog);
font-weight: 400;
margin-left: 4px;
}
.stat-trend {
display: flex; align-items: center; gap: 4px;
font-size: 12px;
font-family: var(--f-mono);
color: var(--c-fog);
margin-top: var(--s-2);
}
.stat-trend.up { color: var(--c-ok); }
.stat-trend.down { color: var(--c-warn); }
/* Engines card */
.engine-row {
display: grid;
grid-template-columns: 60px 1fr 110px;
gap: var(--s-4);
align-items: center;
padding: var(--s-3) 0;
border-bottom: 1px solid var(--c-steel);
}
.engine-row:last-child { border-bottom: none; }
.engine-label {
font-family: var(--f-mono);
font-size: 13px;
font-weight: 600;
color: var(--c-foam);
}
.engine-state {
display: inline-block;
padding: 2px 8px;
background: rgba(0,224,138,0.12);
color: var(--c-ok);
border-radius: var(--r-pill);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.5px;
margin-top: 2px;
}
.gauge-bar {
position: relative;
height: 8px;
background: var(--c-steel);
border-radius: var(--r-pill);
overflow: hidden;
}
.gauge-fill {
position: absolute;
inset: 0 auto 0 0;
background: var(--g-cyan);
border-radius: var(--r-pill);
box-shadow: 0 0 12px rgba(0,217,255,0.4);
}
.gauge-fill.warn { background: var(--g-warn); box-shadow: 0 0 12px rgba(255,176,32,0.4); }
.engine-meta {
display: flex; gap: var(--s-3);
font-size: 11px;
color: var(--c-fog);
margin-top: 4px;
font-family: var(--f-mono);
}
.engine-meta strong { color: var(--c-sand); }
.engine-rpm {
font-family: var(--f-mono);
font-size: 22px;
font-weight: 600;
color: var(--c-foam);
text-align: right;
}
.engine-rpm small { color: var(--c-fog); font-size: 11px; display: block; font-weight: 400; }
/* Roll/pitch */
.horizon {
width: 100%;
aspect-ratio: 1;
max-width: 280px;
margin: 0 auto;
position: relative;
}
.horizon-readout {
display: flex; justify-content: space-around;
margin-top: var(--s-4);
font-family: var(--f-mono);
}
.ro-block { text-align: center; }
.ro-block .lbl { color: var(--c-fog); font-size: 10px; letter-spacing: 2px; text-transform: uppercase; }
.ro-block .val { font-size: 26px; color: var(--c-foam); font-weight: 600; }
/* Recent alarms list */
.alarm-row {
display: grid;
grid-template-columns: 90px 1fr auto;
gap: var(--s-3);
padding: var(--s-3);
border-radius: var(--r-2);
align-items: center;
transition: background 120ms;
cursor: pointer;
}
.alarm-row:hover { background: var(--c-steel); }
.alarm-time {
font-family: var(--f-mono);
font-size: 11px;
color: var(--c-fog);
}
.alarm-msg {
font-size: 13px;
color: var(--c-sand);
}
.alarm-msg strong { color: var(--c-foam); }
.alarm-msg .src {
font-family: var(--f-mono);
font-size: 11px;
color: var(--c-cyan);
margin-right: 6px;
}
/* Tanks */
.tank {
display: flex; flex-direction: column; align-items: center;
gap: var(--s-2);
}
.tank-shell {
position: relative;
width: 70px;
height: 120px;
border: 2px solid var(--c-iron);
border-radius: 8px 8px 4px 4px;
overflow: hidden;
background: var(--c-abyss);
}
.tank-fill {
position: absolute;
left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, #1B7FB5 0%, #00D9FF 100%);
}
.tank-fill::before {
content: ""; position: absolute;
top: -3px; left: 0; right: 0; height: 6px;
background: rgba(0,217,255,0.5);
filter: blur(2px);
}
.tank-fill.warn { background: linear-gradient(180deg, #C0760F 0%, #FFB020 100%); }
.tank-label { font-family: var(--f-mono); font-size: 11px; color: var(--c-fog); }
.tank-pct {
font-family: var(--f-mono);
font-size: 16px;
color: var(--c-foam);
font-weight: 600;
}
.tank-row {
display: flex; gap: var(--s-5); justify-content: space-around;
align-items: flex-end;
}
/* Ticker */
.ticker {
grid-area: ticker;
display: flex;
align-items: center;
gap: var(--s-5);
padding: 0 var(--s-5);
background: var(--c-midnight);
border-top: 1px solid var(--c-steel);
font-family: var(--f-mono);
font-size: 11px;
color: var(--c-fog);
}
.ticker .sep { color: var(--c-iron); }
.ticker .pulse {
width: 8px; height: 8px;
border-radius: 50%;
background: var(--c-ok);
box-shadow: 0 0 12px rgba(0,224,138,0.7);
animation: heartbeat 2s ease-in-out infinite;
}
@keyframes heartbeat {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.4); opacity: 0.6; }
}
.tk-spacer { flex: 1; }
.ic { width: 16px; height: 16px; stroke-width: 2; stroke: currentColor; fill: none; }
</style>
</head>
<body>
<div class="app-root rt">
<header class="topbar">
<div class="vessel-id">
<img src="../brand/logo-mark.svg" alt="">
<div>
<h1>M/Y Aurora</h1>
<div class="sub">Sunseeker 76 · 23.4 m</div>
</div>
</div>
<span class="status-pill">
<span class="dot ok"></span> Normal · todo en rango
</span>
<span class="top-spacer"></span>
<span class="alarm-chip">
<svg class="ic" viewBox="0 0 24 24"><path d="M12 9v4M12 17h.01"/><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
2 alarmas activas
</span>
<span class="chip">
<span class="dot cyan"></span>
Autoridad: <strong style="color:var(--c-cyan)">PUENTE</strong>
</span>
<span class="user-chip">
<span class="avatar">A</span>
Álvaro · <span style="color:var(--c-fog)">Admin</span>
</span>
</header>
<aside class="sidebar">
<div class="sb-section">
<div class="sb-title">Vistas</div>
<div class="nav-item active">
<svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
Overview
</div>
<div class="nav-item">
<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 3v18M3 12h18"/></svg>
Mímicos
</div>
<div class="nav-item">
<svg class="ic" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/></svg>
Alarmas
<span class="alarm-badge">2</span>
</div>
<div class="nav-item">
<svg class="ic" viewBox="0 0 24 24"><polyline points="3 17 9 11 13 15 21 7"/></svg>
Trends
</div>
<div class="nav-item">
<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><polyline points="12 6 12 12 15 14"/></svg>
Log Book
</div>
<div class="nav-item">
<svg class="ic" viewBox="0 0 24 24"><path d="M6 3v18M18 3v18"/><circle cx="12" cy="12" r="3"/></svg>
Trim & Maniobra
</div>
</div>
<div class="sb-section">
<div class="sb-title">Sistemas</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M5 12h2M17 12h2M12 5v2M12 17v2"/></svg> Máquina principal</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><path d="M13 2L3 14h7v8l10-12h-7V2z"/></svg> Generación eléctrica</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><path d="M12 2v10l4 4M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0z"/></svg> Combustible</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><path d="M6 8c0-4 6-6 6-6s6 2 6 6v3a6 6 0 0 1-12 0V8z"/></svg> Refrigeración</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><polyline points="12 6 12 12 8 14"/></svg> Sentinas</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><path d="M3 12h6l3-9 3 18 3-9h3"/></svg> HVAC</div>
<div class="nav-item"><svg class="ic" viewBox="0 0 24 24"><path d="M12 2v6M12 16v6M2 12h6M16 12h6M5 5l4 4M15 15l4 4M5 19l4-4M15 9l4-4"/></svg> Iluminación</div>
</div>
<div class="sb-section">
<div class="sb-title">Soporte</div>
<div class="nav-item">
<svg class="ic" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
Auditoría VPN
</div>
</div>
</aside>
<main class="main">
<div class="page-title">
<div>
<h2>Estado general del buque</h2>
<div class="sub">Última actualización: hace 1.2 s · Sin desconexiones</div>
</div>
<div class="clock">
<div>03:42:18</div>
<div class="date">2026-05-17 · UTC-04</div>
</div>
</div>
<div class="grid">
<!-- 4 stats -->
<div class="stat col-3 accent-cyan">
<div class="stat-header">
<span class="stat-label">Combustible</span>
<span class="dot ok"></span>
</div>
<div class="stat-value">2,840<span class="stat-unit">L</span></div>
<div class="stat-trend down">▼ 18 L/h consumo medio</div>
</div>
<div class="stat col-3 accent-ok">
<div class="stat-header">
<span class="stat-label">Generación</span>
<span class="dot ok"></span>
</div>
<div class="stat-value">28.4<span class="stat-unit">kW</span></div>
<div class="stat-trend">GEN_1 · 55% carga</div>
</div>
<div class="stat col-3 accent-ok">
<div class="stat-header">
<span class="stat-label">Baterías</span>
<span class="dot ok"></span>
</div>
<div class="stat-value">27.8<span class="stat-unit">V</span></div>
<div class="stat-trend up">▲ Cargando 12 A</div>
</div>
<div class="stat col-3 accent-warn">
<div class="stat-header">
<span class="stat-label">Sentinas</span>
<span class="dot warn"></span>
</div>
<div class="stat-value">12<span class="stat-unit">%</span></div>
<div class="stat-trend down">BILGE_MID en watch</div>
</div>
<!-- Motores -->
<div class="card col-8">
<div class="stat-header">
<span class="stat-label">Máquina principal · 2× MTU 12V 2000 M96</span>
<a href="#" style="font-size:12px">Ver mímico →</a>
</div>
<div style="margin-top: var(--s-4);">
<div class="engine-row">
<div>
<div class="engine-label">ME_PORT</div>
<span class="engine-state">RUNNING</span>
</div>
<div>
<div class="gauge-bar"><div class="gauge-fill" style="width: 62%;"></div></div>
<div class="engine-meta">
<span>Aceite <strong>4.8 bar</strong></span>
<span>Coolant <strong>82°C</strong></span>
<span>Carga <strong>62%</strong></span>
<span>Horas <strong>1,284</strong></span>
</div>
</div>
<div class="engine-rpm">1,520 <small>rpm</small></div>
</div>
<div class="engine-row">
<div>
<div class="engine-label">ME_STBD</div>
<span class="engine-state">RUNNING</span>
</div>
<div>
<div class="gauge-bar"><div class="gauge-fill" style="width: 58%;"></div></div>
<div class="engine-meta">
<span>Aceite <strong>4.9 bar</strong></span>
<span>Coolant <strong>81°C</strong></span>
<span>Carga <strong>58%</strong></span>
<span>Horas <strong>1,287</strong></span>
</div>
</div>
<div class="engine-rpm">1,498 <small>rpm</small></div>
</div>
<div class="engine-row">
<div>
<div class="engine-label">GEN_1</div>
<span class="engine-state">RUNNING</span>
</div>
<div>
<div class="gauge-bar"><div class="gauge-fill warn" style="width: 88%;"></div></div>
<div class="engine-meta">
<span>L1 <strong>231 V</strong></span>
<span>Coolant <strong>89°C</strong></span>
<span>Carga <strong>88%</strong></span>
<span>Horas <strong>3,418</strong></span>
</div>
</div>
<div class="engine-rpm">1,800 <small>rpm</small></div>
</div>
</div>
</div>
<!-- Roll/pitch -->
<div class="card col-4">
<div class="stat-header">
<span class="stat-label">Actitud (NMEA 2000 · PGN 127257)</span>
<span class="badge badge-ok">SAFE</span>
</div>
<div class="horizon">
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3A6BA8"/>
<stop offset="100%" stop-color="#1B3E6E"/>
</linearGradient>
<linearGradient id="sea" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0A1A2E"/>
<stop offset="100%" stop-color="#04111F"/>
</linearGradient>
<clipPath id="circleClip">
<circle cx="100" cy="100" r="86"/>
</clipPath>
</defs>
<circle cx="100" cy="100" r="92" fill="none" stroke="#1A2B42" stroke-width="2"/>
<g clip-path="url(#circleClip)">
<g transform="rotate(-4 100 100)">
<rect x="0" y="0" width="200" height="100" fill="url(#sky)"/>
<rect x="0" y="100" width="200" height="100" fill="url(#sea)"/>
<line x1="0" y1="100" x2="200" y2="100" stroke="#00D9FF" stroke-width="1.5" opacity="0.7"/>
<g font-family="JetBrains Mono" font-size="9" fill="#E6EAF0">
<line x1="80" y1="80" x2="120" y2="80" stroke="#E6EAF0" stroke-width="1" opacity="0.6"/>
<text x="125" y="84">10°</text>
<line x1="80" y1="120" x2="120" y2="120" stroke="#E6EAF0" stroke-width="1" opacity="0.6"/>
<text x="125" y="124">10°</text>
</g>
</g>
</g>
<!-- Center cross -->
<g stroke="#00D9FF" stroke-width="2" fill="none">
<line x1="80" y1="100" x2="95" y2="100"/>
<line x1="105" y1="100" x2="120" y2="100"/>
<circle cx="100" cy="100" r="3" fill="#00D9FF"/>
</g>
<!-- Tick scale top -->
<g stroke="#7C8B9F" stroke-width="1">
<line x1="100" y1="8" x2="100" y2="16"/>
<line x1="50" y1="22" x2="56" y2="29" opacity="0.5"/>
<line x1="150" y1="22" x2="144" y2="29" opacity="0.5"/>
</g>
</svg>
</div>
<div class="horizon-readout">
<div class="ro-block">
<div class="lbl">Roll</div>
<div class="val">-4.1°</div>
</div>
<div class="ro-block">
<div class="lbl">Pitch</div>
<div class="val">+1.8°</div>
</div>
</div>
<div style="margin-top: var(--s-4); padding-top: var(--s-3); border-top: 1px solid var(--c-steel); font-size: 11px; color: var(--c-fog); display:flex; justify-content: space-between; font-family: var(--f-mono);">
<span>Envelope ±10°</span>
<span>L3 trigger 18°</span>
</div>
</div>
<!-- Tanques -->
<div class="card col-6">
<div class="stat-header">
<span class="stat-label">Tanques estructurales</span>
<a href="#" style="font-size:12px">Detalle →</a>
</div>
<div class="tank-row" style="margin-top: var(--s-4);">
<div class="tank">
<div class="tank-shell">
<div class="tank-fill" style="height: 78%;"></div>
</div>
<div class="tank-pct">78%</div>
<div class="tank-label">FUEL 1</div>
</div>
<div class="tank">
<div class="tank-shell">
<div class="tank-fill" style="height: 64%;"></div>
</div>
<div class="tank-pct">64%</div>
<div class="tank-label">FUEL 2</div>
</div>
<div class="tank">
<div class="tank-shell">
<div class="tank-fill" style="height: 91%; background: linear-gradient(180deg, #007F4E 0%, #00E08A 100%);"></div>
</div>
<div class="tank-pct">91%</div>
<div class="tank-label">WATER</div>
</div>
<div class="tank">
<div class="tank-shell">
<div class="tank-fill warn" style="height: 12%;"></div>
</div>
<div class="tank-pct" style="color: var(--c-warn);">12%</div>
<div class="tank-label">BILGE</div>
</div>
<div class="tank">
<div class="tank-shell">
<div class="tank-fill" style="height: 28%; background: linear-gradient(180deg, #5A6B7F 0%, #94A3B8 100%);"></div>
</div>
<div class="tank-pct">28%</div>
<div class="tank-label">BLACK</div>
</div>
</div>
</div>
<!-- Alarmas recientes -->
<div class="card col-6">
<div class="stat-header">
<span class="stat-label">Alarmas recientes</span>
<a href="#" style="font-size:12px">Ver todas (2) →</a>
</div>
<div style="margin-top: var(--s-3);">
<div class="alarm-row">
<span class="alarm-time">03:38:42</span>
<div class="alarm-msg">
<span class="src">GEN_1.COOLANT_TEMP</span>
<strong>89°C</strong> alta — aproximando límite 92°C
</div>
<span class="badge badge-low">LOW</span>
</div>
<div class="alarm-row">
<span class="alarm-time">03:31:17</span>
<div class="alarm-msg">
<span class="src">BILGE_MID.LEVEL</span>
Nivel <strong>12%</strong> — verificar bomba
</div>
<span class="badge badge-info">INFO</span>
</div>
<div class="alarm-row" style="opacity: 0.5;">
<span class="alarm-time">03:12:04</span>
<div class="alarm-msg">
<span class="src">ME_PORT.OIL_TEMP</span>
Recuperado a <strong>88°C</strong> · resolved
</div>
<span class="badge badge-muted">CLEARED</span>
</div>
<div class="alarm-row" style="opacity: 0.5;">
<span class="alarm-time">02:58:30</span>
<div class="alarm-msg">
<span class="src">SHORE_POWER.STATUS</span>
Transferencia a gen — desconexión muelle
</div>
<span class="badge badge-muted">CLEARED</span>
</div>
</div>
</div>
</div>
</main>
<footer class="ticker">
<span class="pulse"></span>
<span>WebSocket <strong style="color:var(--c-sand)">LIVE</strong></span>
<span class="sep">|</span>
<span>Latencia <strong style="color:var(--c-sand)">42 ms</strong></span>
<span class="sep">|</span>
<span>Driver Modbus RTU <strong style="color:var(--c-ok)">OK</strong></span>
<span class="sep">|</span>
<span>Driver NMEA 2000 <strong style="color:var(--c-ok)">OK</strong></span>
<span class="sep">|</span>
<span>Tags activos <strong style="color:var(--c-sand)">187</strong></span>
<span class="tk-spacer"></span>
<span><span class="dot cyan"></span> VPN soporte <strong>INACTIVA</strong></span>
<span class="sep">|</span>
<span>Telemetría <strong style="color:var(--c-ok)">activa</strong> (visible)</span>
<span class="sep">|</span>
<span>v0.1.0.dev0</span>
</footer>
</div>
</body>
</html>