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>
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
/* VMS-Sailor Design Tokens - canonical reference for all UI sprints. */
|
||||
|
||||
:root {
|
||||
/* ---- Colors: Deep Ocean ---- */
|
||||
--c-abyss: #04111F;
|
||||
--c-midnight: #0A1A2E;
|
||||
--c-steel: #1A2B42;
|
||||
--c-iron: #2C3E5C;
|
||||
--c-fog: #7C8B9F;
|
||||
--c-sand: #E6EAF0;
|
||||
--c-foam: #F2F5F9;
|
||||
--c-cyan: #00D9FF;
|
||||
--c-cyan-deep: #1B7FB5;
|
||||
--c-horizon: #5BC0EB;
|
||||
|
||||
/* ---- Semantic ---- */
|
||||
--c-ok: #00E08A;
|
||||
--c-info: #5BC0EB;
|
||||
--c-warn: #FFB020;
|
||||
--c-high: #FF8030;
|
||||
--c-emergency: #FF3B47;
|
||||
--c-emergency-deep: #A11220;
|
||||
|
||||
/* ---- Gradients ---- */
|
||||
--g-deep-sea: linear-gradient(135deg, #04111F 0%, #0A1A2E 60%, #1A2B42 100%);
|
||||
--g-horizon: linear-gradient(180deg, #04111F 0%, #1B3E6E 60%, #3A6BA8 90%, #5BC0EB 100%);
|
||||
--g-cyan-glow: radial-gradient(circle at 50% 50%, rgba(0,217,255,0.4) 0%, transparent 70%);
|
||||
--g-glass: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
|
||||
--g-glass-edge: linear-gradient(135deg, rgba(255,255,255,0.18), rgba(255,255,255,0.02));
|
||||
--g-emergency: linear-gradient(135deg, #FF3B47, #A11220);
|
||||
--g-warn: linear-gradient(135deg, #FFB020, #C0760F);
|
||||
--g-cyan: linear-gradient(135deg, #00D9FF 0%, #5BC0EB 50%, #1B7FB5 100%);
|
||||
--g-ok: linear-gradient(135deg, #00E08A, #007F4E);
|
||||
|
||||
/* ---- Spacing 4px base ---- */
|
||||
--s-1: 4px;
|
||||
--s-2: 8px;
|
||||
--s-3: 12px;
|
||||
--s-4: 16px;
|
||||
--s-5: 24px;
|
||||
--s-6: 32px;
|
||||
--s-7: 48px;
|
||||
--s-8: 64px;
|
||||
--s-9: 96px;
|
||||
|
||||
/* ---- Radius ---- */
|
||||
--r-1: 4px;
|
||||
--r-2: 8px;
|
||||
--r-3: 12px;
|
||||
--r-4: 16px;
|
||||
--r-5: 24px;
|
||||
--r-pill: 9999px;
|
||||
|
||||
/* ---- Elevation ---- */
|
||||
--e-1: 0 1px 3px rgba(0,0,0,0.32);
|
||||
--e-2: 0 4px 12px rgba(0,0,0,0.40);
|
||||
--e-3: 0 8px 24px rgba(0,0,0,0.50);
|
||||
--e-4: 0 16px 48px rgba(0,0,0,0.60);
|
||||
--e-5: 0 32px 80px rgba(0,0,0,0.70);
|
||||
--glow-cyan: 0 0 24px rgba(0,217,255,0.45);
|
||||
--glow-warn: 0 0 24px rgba(255,176,32,0.45);
|
||||
--glow-emergency: 0 0 32px rgba(255,59,71,0.55);
|
||||
--inner-stroke: inset 0 0 0 1px rgba(255,255,255,0.06);
|
||||
|
||||
/* ---- Typography families ---- */
|
||||
--f-display: "Space Grotesk", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
--f-ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
--f-mono: "JetBrains Mono", "Cascadia Mono", Consolas, "Courier New", monospace;
|
||||
|
||||
/* ---- Motion ---- */
|
||||
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--ease-out: cubic-bezier(0.0, 0, 0.2, 1);
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
/* ---- Reset ---- */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--g-deep-sea);
|
||||
color: var(--c-sand);
|
||||
font-family: var(--f-ui);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
min-height: 100vh;
|
||||
}
|
||||
body::before {
|
||||
/* subtle horizon glow at the top */
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: radial-gradient(ellipse at 20% -20%, rgba(0,217,255,0.10), transparent 50%),
|
||||
radial-gradient(ellipse at 80% 110%, rgba(91,192,235,0.06), transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
button { font-family: inherit; cursor: pointer; }
|
||||
a { color: var(--c-cyan); text-decoration: none; }
|
||||
|
||||
/* ---- Utility classes ---- */
|
||||
.mono { font-family: var(--f-mono); }
|
||||
.display { font-family: var(--f-display); }
|
||||
.overline {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2.4px;
|
||||
text-transform: uppercase;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
.caption {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.4px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
|
||||
/* ---- Cards ---- */
|
||||
.card {
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-3);
|
||||
padding: var(--s-5);
|
||||
box-shadow: var(--e-2);
|
||||
}
|
||||
.card-glass {
|
||||
background: var(--g-glass);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: var(--r-3);
|
||||
backdrop-filter: blur(16px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.4);
|
||||
box-shadow: var(--e-3), var(--inner-stroke);
|
||||
padding: var(--s-5);
|
||||
}
|
||||
|
||||
/* ---- Buttons ---- */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--s-2);
|
||||
min-width: 96px;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: var(--r-2);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.2px;
|
||||
transition: transform 120ms var(--ease-standard),
|
||||
box-shadow 120ms var(--ease-standard),
|
||||
background 120ms var(--ease-standard);
|
||||
}
|
||||
.btn:hover { transform: translateY(-1px); }
|
||||
.btn:active { transform: translateY(0); }
|
||||
.btn-primary {
|
||||
background: var(--g-cyan);
|
||||
color: #04111F;
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--c-sand);
|
||||
border: 1px solid var(--c-iron);
|
||||
}
|
||||
.btn-secondary:hover { background: var(--c-steel); }
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--c-cyan);
|
||||
min-width: 0;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.btn-danger {
|
||||
background: var(--g-emergency);
|
||||
color: var(--c-foam);
|
||||
box-shadow: var(--glow-emergency);
|
||||
}
|
||||
.btn-icon {
|
||||
min-width: 0;
|
||||
padding: 8px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-2);
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.btn-icon:hover { background: var(--c-steel); }
|
||||
|
||||
/* ---- Badges ---- */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--r-pill);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.badge-emergency { background: var(--c-emergency); color: #FFFFFF; box-shadow: inset 0 0 0 1px #FF8090; }
|
||||
.badge-high { background: var(--c-high); color: #04111F; box-shadow: inset 0 0 0 1px #FFA060; }
|
||||
.badge-low { background: var(--c-warn); color: #04111F; box-shadow: inset 0 0 0 1px #FFCB60; }
|
||||
.badge-info { background: var(--c-info); color: #04111F; box-shadow: inset 0 0 0 1px #8DDBF2; }
|
||||
.badge-ok { background: var(--c-ok); color: #04111F; box-shadow: inset 0 0 0 1px #5BEFB8; }
|
||||
.badge-muted { background: var(--c-steel); color: var(--c-fog); }
|
||||
|
||||
/* ---- Tag (chip) ---- */
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background: var(--c-steel);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-pill);
|
||||
font-size: 12px;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
|
||||
/* ---- Pulse animation for active critical alarms ---- */
|
||||
@keyframes pulseEmergency {
|
||||
0%, 100% { box-shadow: var(--glow-emergency); }
|
||||
50% { box-shadow: 0 0 48px rgba(255,59,71,0.85); }
|
||||
}
|
||||
.pulse-emergency { animation: pulseEmergency 1200ms ease-in-out infinite; }
|
||||
|
||||
@keyframes pulseWarn {
|
||||
0%, 100% { box-shadow: var(--glow-warn); }
|
||||
50% { box-shadow: 0 0 40px rgba(255,176,32,0.7); }
|
||||
}
|
||||
.pulse-warn { animation: pulseWarn 1600ms ease-in-out infinite; }
|
||||
|
||||
/* ---- Dot indicator ---- */
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--c-fog);
|
||||
}
|
||||
.dot.ok { background: var(--c-ok); box-shadow: 0 0 8px rgba(0,224,138,0.6); }
|
||||
.dot.warn { background: var(--c-warn); box-shadow: 0 0 8px rgba(255,176,32,0.6); }
|
||||
.dot.emergency {
|
||||
background: var(--c-emergency);
|
||||
box-shadow: 0 0 12px rgba(255,59,71,0.8);
|
||||
animation: pulseEmergency 1200ms ease-in-out infinite;
|
||||
}
|
||||
.dot.cyan { background: var(--c-cyan); box-shadow: 0 0 8px rgba(0,217,255,0.6); }
|
||||
|
||||
/* ---- App shell common ---- */
|
||||
.app-root {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor · Galería de mockups</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
.hero {
|
||||
padding: var(--s-9) var(--s-7) var(--s-7);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero::before {
|
||||
content: ""; position: absolute;
|
||||
top: -120px; left: 50%; transform: translateX(-50%);
|
||||
width: 800px; height: 800px;
|
||||
background: radial-gradient(circle, rgba(0,217,255,0.18), transparent 60%);
|
||||
filter: blur(40px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.hero img { width: 120px; height: 120px; position: relative; z-index: 2; filter: drop-shadow(0 8px 32px rgba(0,217,255,0.3)); }
|
||||
.hero h1 {
|
||||
font-family: var(--f-display);
|
||||
font-size: 72px;
|
||||
font-weight: 700;
|
||||
margin: var(--s-4) 0 var(--s-2);
|
||||
letter-spacing: -2px;
|
||||
color: var(--c-foam);
|
||||
position: relative; z-index: 2;
|
||||
}
|
||||
.hero h1 .accent { color: var(--c-cyan); font-weight: 300; }
|
||||
.hero h1 .light { font-weight: 400; }
|
||||
.hero .tagline {
|
||||
font-size: 14px;
|
||||
letter-spacing: 4px;
|
||||
text-transform: uppercase;
|
||||
color: var(--c-horizon);
|
||||
position: relative; z-index: 2;
|
||||
margin-bottom: var(--s-3);
|
||||
}
|
||||
.hero p {
|
||||
max-width: 640px;
|
||||
margin: var(--s-4) auto 0;
|
||||
color: var(--c-sand);
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
position: relative; z-index: 2;
|
||||
}
|
||||
.badges {
|
||||
display: flex; justify-content: center;
|
||||
gap: var(--s-3);
|
||||
margin-top: var(--s-6);
|
||||
flex-wrap: wrap;
|
||||
position: relative; z-index: 2;
|
||||
}
|
||||
.grid {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--s-7) var(--s-9);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||
gap: var(--s-5);
|
||||
}
|
||||
.card-mockup {
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--e-2);
|
||||
transition: transform 200ms var(--ease-standard),
|
||||
box-shadow 200ms var(--ease-standard),
|
||||
border-color 200ms var(--ease-standard);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.card-mockup:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: var(--c-cyan);
|
||||
box-shadow: var(--e-4), var(--glow-cyan);
|
||||
}
|
||||
.preview {
|
||||
aspect-ratio: 16 / 10;
|
||||
background: var(--c-abyss);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.preview .iframe-wrap {
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: top left;
|
||||
pointer-events: none;
|
||||
}
|
||||
.preview iframe {
|
||||
width: 100%; height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
.preview .label {
|
||||
position: absolute;
|
||||
top: var(--s-3); left: var(--s-3);
|
||||
padding: 4px 10px;
|
||||
background: rgba(4,17,31,0.9);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-pill);
|
||||
font-family: var(--f-mono);
|
||||
font-size: 10px;
|
||||
color: var(--c-cyan);
|
||||
letter-spacing: 1px;
|
||||
z-index: 2;
|
||||
}
|
||||
.info {
|
||||
padding: var(--s-4) var(--s-5);
|
||||
}
|
||||
.info h3 {
|
||||
font-family: var(--f-display);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 var(--s-2);
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.info p {
|
||||
margin: 0;
|
||||
color: var(--c-fog);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.sprint-tag {
|
||||
display: inline-block;
|
||||
margin-top: var(--s-3);
|
||||
padding: 2px 10px;
|
||||
background: var(--c-steel);
|
||||
border-radius: var(--r-pill);
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
color: var(--c-cyan);
|
||||
}
|
||||
.footer {
|
||||
padding: var(--s-6) var(--s-7);
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--c-steel);
|
||||
color: var(--c-fog);
|
||||
font-size: 12px;
|
||||
font-family: var(--f-mono);
|
||||
}
|
||||
.footer strong { color: var(--c-sand); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root">
|
||||
|
||||
<header class="hero">
|
||||
<img src="../brand/logo-mark.svg" alt="VMS-Sailor">
|
||||
<p class="tagline">Vessel Management System</p>
|
||||
<h1>VMS<span class="accent"> · </span><span class="light">Sailor</span></h1>
|
||||
<p>
|
||||
Sprint 0 visual reference. 8 mockups HTML estáticos que muestran el aspecto futuro de Studio, Runtime desktop y Mobile.
|
||||
Mismo design system (tokens en <code>_tokens.css</code>) aplicará en Sprint 1 (PySide6), Sprint 6 (PySide6) y Sprint 11 (Flutter).
|
||||
</p>
|
||||
<div class="badges">
|
||||
<span class="chip"><span class="dot ok"></span> 99/99 tests · 95% cov</span>
|
||||
<span class="chip"><span class="dot cyan"></span> Python 3.11 · uv</span>
|
||||
<span class="chip"><span class="dot cyan"></span> Pydantic v2</span>
|
||||
<span class="chip"><span class="dot cyan"></span> SQLite portable</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="grid">
|
||||
|
||||
<a href="splash.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">SPLASH</span>
|
||||
<div class="iframe-wrap"><iframe src="splash.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Splash · Bienvenida</h3>
|
||||
<p>Pantalla de arranque con logo, olas animadas, loading scan y certificaciones (IEC, IACS, NMEA 2000).</p>
|
||||
<span class="sprint-tag">Studio + Runtime · todos los sprints</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="studio_main.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">STUDIO</span>
|
||||
<div class="iframe-wrap"><iframe src="studio_main.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Studio · Topología</h3>
|
||||
<p>Shell del Studio con wizard 8 pasos, sidebar de sistemas habilitados, canvas con silueta del buque y inspector de tarjetas AR-NMEA-IO.</p>
|
||||
<span class="sprint-tag">Sprint 1-3</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="runtime_overview.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">RUNTIME · OVERVIEW</span>
|
||||
<div class="iframe-wrap"><iframe src="runtime_overview.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Dashboard del buque</h3>
|
||||
<p>Vista general con estado de motores, gensets, tanques, alarmas recientes y horizonte artificial con roll/pitch en vivo.</p>
|
||||
<span class="sprint-tag">Sprint 6</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="runtime_mimic_fuel.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">MÍMICO · COMBUSTIBLE</span>
|
||||
<div class="iframe-wrap"><iframe src="runtime_mimic_fuel.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>P&ID animado</h3>
|
||||
<p>Mímico del sistema de combustible con flujo animado en tuberías, bomba rotando, tanques con gradiente de nivel.</p>
|
||||
<span class="sprint-tag">Sprint 3 + 6</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="runtime_alarms.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">ALARMAS</span>
|
||||
<div class="iframe-wrap"><iframe src="runtime_alarms.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Panel de alarmas</h3>
|
||||
<p>Lista cronológica con prioridades IMO (Emergency / High / Low / Info), ACK por alarma, filtros y escalación.</p>
|
||||
<span class="sprint-tag">Sprint 4 + 6</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="runtime_trim.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">⭐ TRIM & MANIOBRA</span>
|
||||
<div class="iframe-wrap"><iframe src="runtime_trim.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Trim & Maniobra</h3>
|
||||
<p>Pantalla destacada. Horizonte artificial con bandas de seguridad, 4 sliders de trim, predicción de envelope, botón reset emergencia y modo manual owner.</p>
|
||||
<span class="sprint-tag">Sprint 8</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="mobile_overview.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">MOBILE · OVERVIEW</span>
|
||||
<div class="iframe-wrap"><iframe src="mobile_overview.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Mobile · iOS overview</h3>
|
||||
<p>App nativa Flutter con dashboard del buque, motores, horizonte mini y alarmas. WiFi local del buque.</p>
|
||||
<span class="sprint-tag">Sprint 11</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="mobile_trim.html" class="card-mockup">
|
||||
<div class="preview">
|
||||
<span class="label">MOBILE · TRIM</span>
|
||||
<div class="iframe-wrap"><iframe src="mobile_trim.html" loading="lazy" scrolling="no"></iframe></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>Mobile · Trim del owner</h3>
|
||||
<p>Panel destacado en móvil. Sliders táctiles grandes, FaceID para modo manual, botón reset emergencia siempre visible.</p>
|
||||
<span class="sprint-tag">Sprint 11</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<strong>VMS-Sailor</strong> v0.1.0.dev0 · Sprint 0 · 2026<br>
|
||||
Propiedad intelectual de Álvaro · Todos los derechos reservados
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,582 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor Mobile · Overview</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body {
|
||||
background:
|
||||
radial-gradient(ellipse at top, rgba(0,217,255,0.10), transparent 60%),
|
||||
radial-gradient(ellipse at bottom, rgba(91,192,235,0.06), transparent 60%),
|
||||
var(--g-deep-sea);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--s-7) var(--s-5);
|
||||
gap: var(--s-7);
|
||||
}
|
||||
|
||||
.phone {
|
||||
width: 390px;
|
||||
height: 844px;
|
||||
background: #000;
|
||||
border-radius: 56px;
|
||||
padding: 12px;
|
||||
box-shadow:
|
||||
var(--e-5),
|
||||
0 0 0 2px rgba(255,255,255,0.05),
|
||||
0 0 0 14px #1a1a1a;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.phone::before {
|
||||
/* Dynamic Island */
|
||||
content: ""; position: absolute;
|
||||
top: 16px; left: 50%; transform: translateX(-50%);
|
||||
width: 124px; height: 36px;
|
||||
background: #000; border-radius: 18px;
|
||||
z-index: 10;
|
||||
}
|
||||
.screen {
|
||||
width: 100%; height: 100%;
|
||||
background: var(--c-abyss);
|
||||
border-radius: 44px;
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.ios-status {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 16px 32px 6px;
|
||||
font-family: var(--f-ui);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
height: 56px;
|
||||
}
|
||||
.ios-status .indicators {
|
||||
display: flex; gap: 6px; align-items: center;
|
||||
}
|
||||
.ios-bars { display: inline-flex; align-items: flex-end; gap: 1.5px; height: 11px; }
|
||||
.ios-bars i { width: 3px; background: var(--c-foam); border-radius: 1px; }
|
||||
.ios-bars i:nth-child(1) { height: 4px; }
|
||||
.ios-bars i:nth-child(2) { height: 6px; }
|
||||
.ios-bars i:nth-child(3) { height: 8px; }
|
||||
.ios-bars i:nth-child(4) { height: 11px; }
|
||||
.ios-battery { width: 25px; height: 12px; border: 1px solid var(--c-foam); border-radius: 3px; padding: 1px; position: relative; }
|
||||
.ios-battery::after { content: ""; position: absolute; top: 3px; right: -3px; width: 2px; height: 6px; background: var(--c-foam); border-radius: 1px; }
|
||||
.ios-battery div { width: 88%; height: 100%; background: var(--c-foam); border-radius: 1px; }
|
||||
|
||||
/* App header */
|
||||
.m-header {
|
||||
padding: var(--s-3) var(--s-5);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
}
|
||||
.m-header .left {
|
||||
display: flex; align-items: center; gap: var(--s-2);
|
||||
}
|
||||
.m-header img { height: 24px; }
|
||||
.m-header .vname {
|
||||
font-family: var(--f-display);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
line-height: 1;
|
||||
}
|
||||
.m-header .vsub {
|
||||
font-size: 10px;
|
||||
color: var(--c-fog);
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.m-header .alarm-icon {
|
||||
position: relative;
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,176,32,0.14);
|
||||
border: 1px solid rgba(255,176,32,0.4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: var(--c-warn);
|
||||
}
|
||||
.m-header .alarm-icon::after {
|
||||
content: "2"; position: absolute;
|
||||
top: -2px; right: -2px;
|
||||
width: 18px; height: 18px;
|
||||
background: var(--c-emergency);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 2px solid var(--c-abyss);
|
||||
}
|
||||
|
||||
.m-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--s-4) var(--s-4) var(--s-9);
|
||||
display: flex; flex-direction: column; gap: var(--s-4);
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: var(--s-5);
|
||||
background: linear-gradient(135deg, rgba(0,217,255,0.18), rgba(0,217,255,0.02));
|
||||
border: 1px solid rgba(0,217,255,0.3);
|
||||
border-radius: var(--r-4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero::before {
|
||||
content: ""; position: absolute;
|
||||
top: -30px; right: -30px;
|
||||
width: 160px; height: 160px;
|
||||
background: radial-gradient(circle, rgba(0,217,255,0.35), transparent 70%);
|
||||
filter: blur(12px);
|
||||
}
|
||||
.hero .label {
|
||||
font-size: 10px;
|
||||
color: var(--c-cyan);
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
.hero .value {
|
||||
font-family: var(--f-display);
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
color: var(--c-foam);
|
||||
margin-top: 4px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
.hero .extra {
|
||||
display: flex; gap: var(--s-4); margin-top: var(--s-3);
|
||||
font-family: var(--f-mono); font-size: 12px;
|
||||
}
|
||||
.hero .extra div span { color: var(--c-fog); display: block; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; }
|
||||
.hero .extra div strong { color: var(--c-foam); font-size: 14px; }
|
||||
|
||||
.row-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--s-3);
|
||||
}
|
||||
.stat-mini {
|
||||
padding: var(--s-3);
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-3);
|
||||
}
|
||||
.stat-mini .lbl {
|
||||
font-size: 9px;
|
||||
color: var(--c-fog);
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
.stat-mini .val {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
margin-top: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
.stat-mini .val .u {
|
||||
font-size: 11px;
|
||||
color: var(--c-fog);
|
||||
margin-left: 4px;
|
||||
}
|
||||
.stat-mini .footer {
|
||||
display: flex; gap: 6px; align-items: center;
|
||||
font-size: 10px; color: var(--c-fog);
|
||||
margin-top: var(--s-2);
|
||||
font-family: var(--f-mono);
|
||||
}
|
||||
.stat-mini.warn { border-color: rgba(255,176,32,0.5); background: rgba(255,176,32,0.04); }
|
||||
.stat-mini.warn .val { color: var(--c-warn); }
|
||||
|
||||
.panel {
|
||||
padding: var(--s-4);
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-3);
|
||||
}
|
||||
.panel h4 {
|
||||
margin: 0 0 var(--s-3);
|
||||
font-size: 13px;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
color: var(--c-fog);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.engine-mini {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--s-2);
|
||||
padding: var(--s-2) 0;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
}
|
||||
.engine-mini:last-child { border-bottom: none; }
|
||||
.engine-mini .n {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 12px;
|
||||
color: var(--c-foam);
|
||||
font-weight: 600;
|
||||
}
|
||||
.engine-mini .meta {
|
||||
display: flex; gap: var(--s-3);
|
||||
font-family: var(--f-mono); font-size: 10px; color: var(--c-fog);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.engine-mini .rpm {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
text-align: right;
|
||||
}
|
||||
.engine-mini .rpm small { color: var(--c-fog); font-size: 10px; display: block; font-weight: 400; }
|
||||
|
||||
.alarm-mini {
|
||||
display: grid;
|
||||
grid-template-columns: 8px 1fr auto;
|
||||
gap: var(--s-2);
|
||||
align-items: center;
|
||||
padding: var(--s-2) 0;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
}
|
||||
.alarm-mini:last-child { border-bottom: none; }
|
||||
.alarm-mini .ttl {
|
||||
font-size: 12px; color: var(--c-foam); font-weight: 600;
|
||||
}
|
||||
.alarm-mini .ttl .src {
|
||||
color: var(--c-cyan); font-family: var(--f-mono); font-size: 10px; display: block; margin-top: 2px;
|
||||
}
|
||||
.alarm-mini .when {
|
||||
font-family: var(--f-mono); font-size: 10px; color: var(--c-fog);
|
||||
}
|
||||
.alarm-mini .bar {
|
||||
width: 4px; height: 100%; min-height: 32px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Horizon mini */
|
||||
.horizon-mini { display: flex; justify-content: center; padding: var(--s-3); }
|
||||
.horizon-mini svg { width: 100%; max-width: 200px; aspect-ratio: 1; }
|
||||
.att-readout-row {
|
||||
display: grid; grid-template-columns: 1fr 1fr;
|
||||
gap: var(--s-3); margin-top: var(--s-2);
|
||||
}
|
||||
.att-mini {
|
||||
padding: var(--s-2);
|
||||
background: var(--c-abyss);
|
||||
border-radius: var(--r-2);
|
||||
text-align: center;
|
||||
font-family: var(--f-mono);
|
||||
}
|
||||
.att-mini .lbl { font-size: 9px; color: var(--c-fog); letter-spacing: 1.5px; text-transform: uppercase; }
|
||||
.att-mini .val { font-size: 20px; color: var(--c-foam); font-weight: 600; margin-top: 4px; }
|
||||
|
||||
/* Bottom tab bar */
|
||||
.tab-bar {
|
||||
position: absolute;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
background: rgba(10,26,46,0.92);
|
||||
border-top: 1px solid var(--c-steel);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
display: flex;
|
||||
padding: var(--s-2) var(--s-3) calc(var(--s-3) + 18px);
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 4px;
|
||||
color: var(--c-fog);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab.active { color: var(--c-cyan); }
|
||||
.tab .ic {
|
||||
width: 22px; height: 22px;
|
||||
stroke: currentColor; fill: none; stroke-width: 2;
|
||||
}
|
||||
.tab .badge {
|
||||
position: absolute;
|
||||
transform: translate(14px, -2px);
|
||||
background: var(--c-emergency);
|
||||
color: #FFFFFF;
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
padding: 1px 5px;
|
||||
border-radius: var(--r-pill);
|
||||
}
|
||||
|
||||
/* Side panel description */
|
||||
.desc-panel {
|
||||
max-width: 320px;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.desc-panel h2 {
|
||||
font-family: var(--f-display);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 var(--s-3);
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.desc-panel p {
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.desc-panel ul { list-style: none; padding: 0; }
|
||||
.desc-panel li {
|
||||
padding: var(--s-3) 0;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
display: flex; gap: var(--s-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
.desc-panel li::before {
|
||||
content: "✓";
|
||||
color: var(--c-cyan);
|
||||
font-weight: 700;
|
||||
width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ic { stroke: currentColor; fill: none; stroke-width: 2; width: 18px; height: 18px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="phone">
|
||||
<div class="screen">
|
||||
<!-- iOS status bar -->
|
||||
<div class="ios-status">
|
||||
<span>03:42</span>
|
||||
<div class="indicators">
|
||||
<span class="ios-bars"><i></i><i></i><i></i><i></i></span>
|
||||
<svg width="16" height="11" viewBox="0 0 16 11" fill="none" style="color: var(--c-foam);"><path d="M8 4a5 5 0 0 1 5 5M8 1a8 8 0 0 1 8 8M8 7a2 2 0 0 1 2 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
<span class="ios-battery"><div></div></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App header -->
|
||||
<div class="m-header">
|
||||
<div class="left">
|
||||
<img src="../brand/logo-mark.svg" alt="">
|
||||
<div>
|
||||
<div class="vname">M/Y Aurora</div>
|
||||
<div class="vsub">Sunseeker 76</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alarm-icon">
|
||||
<svg class="ic" viewBox="0 0 24 24" stroke-width="2.2"><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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="m-body">
|
||||
|
||||
<div class="hero">
|
||||
<div class="label">⚡ Estado general</div>
|
||||
<div class="value">Normal · todo en rango</div>
|
||||
<div class="extra">
|
||||
<div><span>Velocidad</span><strong>12.4 kn</strong></div>
|
||||
<div><span>Rumbo</span><strong>148°</strong></div>
|
||||
<div><span>Profundidad</span><strong>34 m</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-2">
|
||||
<div class="stat-mini">
|
||||
<div class="lbl">Combustible</div>
|
||||
<div class="val">2,840<span class="u">L</span></div>
|
||||
<div class="footer">▼ 18 L/h · 158 h</div>
|
||||
</div>
|
||||
<div class="stat-mini">
|
||||
<div class="lbl">Baterías</div>
|
||||
<div class="val">27.8<span class="u">V</span></div>
|
||||
<div class="footer">▲ Cargando 12 A</div>
|
||||
</div>
|
||||
<div class="stat-mini">
|
||||
<div class="lbl">Gen</div>
|
||||
<div class="val">28.4<span class="u">kW</span></div>
|
||||
<div class="footer">GEN_1 · 55%</div>
|
||||
</div>
|
||||
<div class="stat-mini warn">
|
||||
<div class="lbl">Sentinas</div>
|
||||
<div class="val">12<span class="u">%</span></div>
|
||||
<div class="footer">⚠ BILGE_MID watch</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h4>Máquinas</h4>
|
||||
<div class="engine-mini">
|
||||
<div>
|
||||
<div class="n">ME_PORT</div>
|
||||
<div class="meta">
|
||||
<span>Aceite 4.8 bar</span>
|
||||
<span>82°C</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpm">1,520 <small>rpm</small></div>
|
||||
</div>
|
||||
<div class="engine-mini">
|
||||
<div>
|
||||
<div class="n">ME_STBD</div>
|
||||
<div class="meta">
|
||||
<span>Aceite 4.9 bar</span>
|
||||
<span>81°C</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpm">1,498 <small>rpm</small></div>
|
||||
</div>
|
||||
<div class="engine-mini">
|
||||
<div>
|
||||
<div class="n" style="color: var(--c-warn);">GEN_1</div>
|
||||
<div class="meta">
|
||||
<span>L1 231 V</span>
|
||||
<span style="color: var(--c-warn);">89°C ▲</span>
|
||||
<span>88%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpm">1,800 <small>rpm</small></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h4>Actitud</h4>
|
||||
<div class="horizon-mini">
|
||||
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="msky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3A6BA8"/>
|
||||
<stop offset="100%" stop-color="#1B3E6E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="msea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#0A1A2E"/>
|
||||
<stop offset="100%" stop-color="#04111F"/>
|
||||
</linearGradient>
|
||||
<clipPath id="mclip"><circle cx="100" cy="100" r="85"/></clipPath>
|
||||
</defs>
|
||||
<circle cx="100" cy="100" r="90" fill="none" stroke="#1A2B42" stroke-width="2"/>
|
||||
<g clip-path="url(#mclip)">
|
||||
<g transform="rotate(-4 100 100)">
|
||||
<rect x="0" y="0" width="200" height="100" fill="url(#msky)"/>
|
||||
<rect x="0" y="100" width="200" height="100" fill="url(#msea)"/>
|
||||
<line x1="0" y1="100" x2="200" y2="100" stroke="#00D9FF" stroke-width="2" opacity="0.9"/>
|
||||
<line x1="80" y1="80" x2="120" y2="80" stroke="#E6EAF0" stroke-width="1" opacity="0.6"/>
|
||||
<line x1="80" y1="120" x2="120" y2="120" stroke="#E6EAF0" stroke-width="1" opacity="0.6"/>
|
||||
</g>
|
||||
</g>
|
||||
<g stroke="#00D9FF" stroke-width="2.5" fill="none">
|
||||
<line x1="78" y1="100" x2="92" y2="100"/>
|
||||
<line x1="108" y1="100" x2="122" y2="100"/>
|
||||
</g>
|
||||
<circle cx="100" cy="100" r="3" fill="#00D9FF"/>
|
||||
<!-- Safe top marker -->
|
||||
<path d="M 100 12 L 96 4 L 104 4 Z" fill="#00E08A"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="att-readout-row">
|
||||
<div class="att-mini">
|
||||
<div class="lbl">Roll</div>
|
||||
<div class="val">-4.1°</div>
|
||||
</div>
|
||||
<div class="att-mini">
|
||||
<div class="lbl">Pitch</div>
|
||||
<div class="val">+1.8°</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h4>Alarmas recientes</h4>
|
||||
<div class="alarm-mini">
|
||||
<div class="bar" style="background: var(--c-warn);"></div>
|
||||
<div>
|
||||
<div class="ttl">
|
||||
Coolant 89°C ▲
|
||||
<span class="src">GEN_1.COOLANT_TEMP</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="when">5m</div>
|
||||
</div>
|
||||
<div class="alarm-mini">
|
||||
<div class="bar" style="background: var(--c-info);"></div>
|
||||
<div>
|
||||
<div class="ttl">
|
||||
Bilge 12%
|
||||
<span class="src">BILGE_MID.LEVEL</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="when">12m</div>
|
||||
</div>
|
||||
<div class="alarm-mini" style="opacity:0.5;">
|
||||
<div class="bar" style="background: var(--c-iron);"></div>
|
||||
<div>
|
||||
<div class="ttl">
|
||||
Resuelto · oil temp 88°C
|
||||
<span class="src">ME_PORT.OIL_TEMP</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="when">31m</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab bar -->
|
||||
<nav class="tab-bar">
|
||||
<div class="tab 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="tab">
|
||||
<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3v18"/></svg>
|
||||
Mímicos
|
||||
</div>
|
||||
<div class="tab">
|
||||
<span class="badge">2</span>
|
||||
<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"/></svg>
|
||||
Alarmas
|
||||
</div>
|
||||
<div class="tab">
|
||||
<svg class="ic" viewBox="0 0 24 24"><path d="M6 3v18M18 3v18"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
Trim
|
||||
</div>
|
||||
<div class="tab">
|
||||
<svg class="ic" viewBox="0 0 24 24"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>
|
||||
Más
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="desc-panel">
|
||||
<span class="overline">Mockup móvil</span>
|
||||
<h2>VMS-Sailor Mobile</h2>
|
||||
<p>App nativa Flutter para iOS y Android. <strong>Solo WiFi local del buque</strong> — nunca expuesta a internet. Autenticación TOTP + biométrico del dispositivo.</p>
|
||||
<ul>
|
||||
<li>Enrollment por QR desde estación principal</li>
|
||||
<li>Permissives evaluados en el servidor — el móvil es solo UI</li>
|
||||
<li>Notificaciones push <em>locales</em> (sin Firebase ni APNs externos)</li>
|
||||
<li>Modo offline degradado con últimos valores cached</li>
|
||||
<li>Acciones de propulsión <strong>solo desde estación fija</strong></li>
|
||||
</ul>
|
||||
<p style="font-family: var(--f-mono); font-size: 12px; color: var(--c-fog); margin-top: var(--s-5);">
|
||||
Sprint 11 · Build iOS via TestFlight · Android APK firmado distribuido directo.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,570 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor Mobile · Trim</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body {
|
||||
background:
|
||||
radial-gradient(ellipse at top, rgba(255,59,71,0.10), transparent 60%),
|
||||
radial-gradient(ellipse at bottom, rgba(0,217,255,0.10), transparent 60%),
|
||||
var(--g-deep-sea);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--s-7) var(--s-5);
|
||||
gap: var(--s-7);
|
||||
}
|
||||
.phone {
|
||||
width: 390px;
|
||||
height: 844px;
|
||||
background: #000;
|
||||
border-radius: 56px;
|
||||
padding: 12px;
|
||||
box-shadow: var(--e-5), 0 0 0 2px rgba(255,255,255,0.05), 0 0 0 14px #1a1a1a;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.phone::before {
|
||||
content: ""; position: absolute;
|
||||
top: 16px; left: 50%; transform: translateX(-50%);
|
||||
width: 124px; height: 36px;
|
||||
background: #000; border-radius: 18px; z-index: 10;
|
||||
}
|
||||
.screen {
|
||||
width: 100%; height: 100%;
|
||||
background: var(--c-abyss);
|
||||
border-radius: 44px;
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.ios-status {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 16px 32px 6px;
|
||||
font-size: 14px; font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
height: 56px;
|
||||
}
|
||||
.indicators { display: flex; gap: 6px; align-items: center; }
|
||||
.ios-bars { display: inline-flex; align-items: flex-end; gap: 1.5px; height: 11px; }
|
||||
.ios-bars i { width: 3px; background: var(--c-foam); border-radius: 1px; }
|
||||
.ios-bars i:nth-child(1) { height: 4px; }
|
||||
.ios-bars i:nth-child(2) { height: 6px; }
|
||||
.ios-bars i:nth-child(3) { height: 8px; }
|
||||
.ios-bars i:nth-child(4) { height: 11px; }
|
||||
.ios-battery { width: 25px; height: 12px; border: 1px solid var(--c-foam); border-radius: 3px; padding: 1px; position: relative; }
|
||||
.ios-battery::after { content: ""; position: absolute; top: 3px; right: -3px; width: 2px; height: 6px; background: var(--c-foam); border-radius: 1px; }
|
||||
.ios-battery div { width: 88%; height: 100%; background: var(--c-foam); border-radius: 1px; }
|
||||
|
||||
/* Header */
|
||||
.m-head {
|
||||
padding: var(--s-3) var(--s-5);
|
||||
display: flex; align-items: center; gap: var(--s-3);
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
}
|
||||
.back {
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 50%;
|
||||
background: var(--c-steel);
|
||||
border: 1px solid var(--c-iron);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.m-head h1 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
font-family: var(--f-display);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.m-head h1 small {
|
||||
display: block;
|
||||
font-family: var(--f-ui);
|
||||
font-size: 10px;
|
||||
color: var(--c-fog);
|
||||
font-weight: 400;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.m-head .badge-ok {
|
||||
padding: 4px 10px;
|
||||
background: rgba(0,224,138,0.14);
|
||||
border: 1px solid rgba(0,224,138,0.4);
|
||||
border-radius: var(--r-pill);
|
||||
color: var(--c-ok);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--s-4) var(--s-4) calc(var(--s-9) + 12px);
|
||||
display: flex; flex-direction: column; gap: var(--s-4);
|
||||
}
|
||||
|
||||
/* Big horizon */
|
||||
.horizon-card {
|
||||
padding: var(--s-4) var(--s-4);
|
||||
background: linear-gradient(180deg, rgba(91,192,235,0.08), rgba(91,192,235,0.0)),
|
||||
var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.horizon-card svg {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
aspect-ratio: 1;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.att-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--s-3);
|
||||
margin-top: var(--s-4);
|
||||
}
|
||||
.att-cell {
|
||||
padding: var(--s-3);
|
||||
background: var(--c-abyss);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-3);
|
||||
text-align: center;
|
||||
}
|
||||
.att-cell .l { font-size: 10px; color: var(--c-fog); letter-spacing: 1.5px; text-transform: uppercase; font-weight: 700; }
|
||||
.att-cell .v {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 34px;
|
||||
color: var(--c-foam);
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
letter-spacing: -1px;
|
||||
line-height: 1;
|
||||
}
|
||||
.att-cell .s { font-size: 10px; color: var(--c-fog); margin-top: 4px; font-family: var(--f-mono); }
|
||||
|
||||
/* Sliders */
|
||||
.sliders-card {
|
||||
padding: var(--s-4);
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
}
|
||||
.sliders-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--s-4);
|
||||
margin-top: var(--s-3);
|
||||
}
|
||||
.slider-block {
|
||||
display: flex; flex-direction: column; align-items: center; gap: var(--s-2);
|
||||
}
|
||||
.slider-block .name {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
color: var(--c-cyan);
|
||||
font-weight: 700;
|
||||
}
|
||||
.v-slider {
|
||||
width: 38px;
|
||||
height: 200px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(255,59,71,0.10) 0%,
|
||||
rgba(255,176,32,0.10) 20%,
|
||||
rgba(0,224,138,0.10) 40%,
|
||||
rgba(0,224,138,0.10) 60%,
|
||||
rgba(255,176,32,0.10) 80%,
|
||||
rgba(255,59,71,0.10) 100%
|
||||
);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-pill);
|
||||
box-shadow: inset 0 2px 8px rgba(0,0,0,0.4);
|
||||
position: relative;
|
||||
}
|
||||
.v-slider::after {
|
||||
content: ""; position: absolute;
|
||||
left: -4px; right: -4px; top: 50%;
|
||||
height: 1px; background: var(--c-cyan); opacity: 0.5;
|
||||
}
|
||||
.v-handle {
|
||||
position: absolute;
|
||||
left: 50%; transform: translateX(-50%);
|
||||
width: 54px; height: 28px;
|
||||
background: var(--g-cyan);
|
||||
border-radius: var(--r-2);
|
||||
box-shadow: var(--glow-cyan), 0 4px 8px rgba(0,0,0,0.5);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #04111F;
|
||||
font-family: var(--f-mono);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.slider-block .pct {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
}
|
||||
|
||||
/* Envelope */
|
||||
.env-card {
|
||||
padding: var(--s-4);
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
}
|
||||
.env-card h4 {
|
||||
font-size: 11px; letter-spacing: 1.5px; color: var(--c-fog);
|
||||
text-transform: uppercase; font-weight: 700;
|
||||
margin: 0 0 var(--s-3);
|
||||
}
|
||||
.env-bar {
|
||||
height: 14px;
|
||||
background: linear-gradient(90deg,
|
||||
#FF3B47 0%, #FF3B47 12%,
|
||||
#FF8030 12%, #FF8030 25%,
|
||||
#FFB020 25%, #FFB020 38%,
|
||||
#00E08A 38%, #00E08A 62%,
|
||||
#FFB020 62%, #FFB020 75%,
|
||||
#FF8030 75%, #FF8030 88%,
|
||||
#FF3B47 88%, #FF3B47 100%
|
||||
);
|
||||
border-radius: var(--r-pill);
|
||||
position: relative;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
|
||||
}
|
||||
.env-pointer {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 47%;
|
||||
transform: translateX(-50%);
|
||||
width: 0; height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-top: 12px solid var(--c-foam);
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6));
|
||||
}
|
||||
.env-scale {
|
||||
display: flex; justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
font-family: var(--f-mono); font-size: 9px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
.env-msg {
|
||||
margin-top: var(--s-3);
|
||||
font-size: 12px;
|
||||
color: var(--c-ok);
|
||||
font-family: var(--f-mono);
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
|
||||
/* Owner manual toggle */
|
||||
.manual-card {
|
||||
padding: var(--s-4);
|
||||
background: var(--c-abyss);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-3);
|
||||
display: flex; align-items: center; gap: var(--s-3);
|
||||
}
|
||||
.manual-card .label { flex: 1; }
|
||||
.manual-card .ttl { font-size: 13px; color: var(--c-foam); font-weight: 600; }
|
||||
.manual-card .desc { font-size: 11px; color: var(--c-fog); margin-top: 2px; }
|
||||
.toggle {
|
||||
width: 52px; height: 30px;
|
||||
border-radius: var(--r-pill);
|
||||
background: var(--c-steel);
|
||||
border: 1px solid var(--c-iron);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle::after {
|
||||
content: ""; position: absolute;
|
||||
left: 3px; top: 3px;
|
||||
width: 22px; height: 22px;
|
||||
background: var(--c-fog);
|
||||
border-radius: 50%;
|
||||
transition: all 200ms;
|
||||
}
|
||||
.biometric {
|
||||
font-size: 10px;
|
||||
color: var(--c-cyan);
|
||||
margin-top: 6px;
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
}
|
||||
|
||||
/* Emergency button */
|
||||
.em-btn {
|
||||
background: var(--g-emergency);
|
||||
border: none;
|
||||
border-radius: var(--r-4);
|
||||
padding: var(--s-5);
|
||||
color: var(--c-foam);
|
||||
font-family: var(--f-display);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--glow-emergency);
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: var(--s-2);
|
||||
}
|
||||
.em-btn .ic-big { width: 36px; height: 36px; stroke: currentColor; fill: none; stroke-width: 2.5; }
|
||||
.em-btn .sub { font-size: 11px; font-weight: 400; letter-spacing: 0.5px; text-transform: none; opacity: 0.9; }
|
||||
|
||||
.tab-bar {
|
||||
position: absolute;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
background: rgba(10,26,46,0.92);
|
||||
border-top: 1px solid var(--c-steel);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
display: flex;
|
||||
padding: var(--s-2) var(--s-3) calc(var(--s-3) + 18px);
|
||||
}
|
||||
.tab {
|
||||
flex: 1; display: flex; flex-direction: column; align-items: center;
|
||||
gap: 4px; padding: 6px 4px;
|
||||
color: var(--c-fog); font-size: 10px; font-weight: 600;
|
||||
}
|
||||
.tab.active { color: var(--c-cyan); }
|
||||
.tab .ic { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; }
|
||||
|
||||
.desc-panel {
|
||||
max-width: 320px;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.desc-panel h2 {
|
||||
font-family: var(--f-display);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 var(--s-3);
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.desc-panel p {
|
||||
font-size: 14px; line-height: 1.7;
|
||||
}
|
||||
.desc-panel .quote {
|
||||
padding: var(--s-4);
|
||||
border-left: 3px solid var(--c-cyan);
|
||||
background: rgba(0,217,255,0.05);
|
||||
border-radius: 0 var(--r-3) var(--r-3) 0;
|
||||
font-style: italic;
|
||||
color: var(--c-foam);
|
||||
margin-top: var(--s-5);
|
||||
}
|
||||
.ic { stroke: currentColor; fill: none; stroke-width: 2; width: 18px; height: 18px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="phone">
|
||||
<div class="screen">
|
||||
<div class="ios-status">
|
||||
<span>03:42</span>
|
||||
<div class="indicators">
|
||||
<span class="ios-bars"><i></i><i></i><i></i><i></i></span>
|
||||
<svg width="16" height="11" viewBox="0 0 16 11" style="color: var(--c-foam);"><path d="M8 4a5 5 0 0 1 5 5M8 1a8 8 0 0 1 8 8M8 7a2 2 0 0 1 2 2" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round"/></svg>
|
||||
<span class="ios-battery"><div></div></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-head">
|
||||
<div class="back">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
</div>
|
||||
<h1>
|
||||
Trim & Maniobra
|
||||
<small>Authority MÁQUINAS</small>
|
||||
</h1>
|
||||
<span class="badge-ok">SAFE</span>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<div class="horizon-card">
|
||||
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="msky2" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#5BC0EB"/>
|
||||
<stop offset="100%" stop-color="#1B3E6E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="msea2" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#0A1A2E"/>
|
||||
<stop offset="100%" stop-color="#04111F"/>
|
||||
</linearGradient>
|
||||
<clipPath id="mclip2"><circle cx="150" cy="150" r="128"/></clipPath>
|
||||
</defs>
|
||||
<!-- Color bands roll scale -->
|
||||
<g fill="none" stroke-width="5" stroke-linecap="butt">
|
||||
<path d="M 100 32 A 128 128 0 0 1 200 32" stroke="#00E08A" opacity="0.7"/>
|
||||
<path d="M 60 55 A 128 128 0 0 1 100 32" stroke="#FFB020" opacity="0.65"/>
|
||||
<path d="M 200 32 A 128 128 0 0 1 240 55" stroke="#FFB020" opacity="0.65"/>
|
||||
<path d="M 36 90 A 128 128 0 0 1 60 55" stroke="#FF8030" opacity="0.65"/>
|
||||
<path d="M 240 55 A 128 128 0 0 1 264 90" stroke="#FF8030" opacity="0.65"/>
|
||||
<path d="M 22 150 A 128 128 0 0 1 36 90" stroke="#FF3B47" opacity="0.75"/>
|
||||
<path d="M 264 90 A 128 128 0 0 1 278 150" stroke="#FF3B47" opacity="0.75"/>
|
||||
</g>
|
||||
<circle cx="150" cy="150" r="140" fill="none" stroke="#1A2B42" stroke-width="2"/>
|
||||
<g clip-path="url(#mclip2)">
|
||||
<g transform="rotate(-4 150 150)">
|
||||
<g transform="translate(0,8)">
|
||||
<rect x="0" y="-100" width="300" height="250" fill="url(#msky2)"/>
|
||||
<rect x="0" y="150" width="300" height="200" fill="url(#msea2)"/>
|
||||
<line x1="0" y1="150" x2="300" y2="150" stroke="#00D9FF" stroke-width="2.5"/>
|
||||
<line x1="0" y1="150" x2="300" y2="150" stroke="#00D9FF" stroke-width="5" opacity="0.35"/>
|
||||
<g font-family="JetBrains Mono" font-size="11" fill="#E6EAF0" font-weight="600">
|
||||
<line x1="115" y1="130" x2="185" y2="130" stroke="#E6EAF0" stroke-width="1.4"/>
|
||||
<text x="105" y="134" text-anchor="end">10</text>
|
||||
<text x="195" y="134">10</text>
|
||||
<line x1="125" y1="115" x2="175" y2="115" stroke="#E6EAF0" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="115" y1="170" x2="185" y2="170" stroke="#E6EAF0" stroke-width="1.4"/>
|
||||
<text x="105" y="174" text-anchor="end">10</text>
|
||||
<text x="195" y="174">10</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<!-- Boat symbol -->
|
||||
<g stroke="#00D9FF" stroke-width="3" fill="none" stroke-linecap="round">
|
||||
<line x1="100" y1="150" x2="130" y2="150"/>
|
||||
<line x1="170" y1="150" x2="200" y2="150"/>
|
||||
<line x1="130" y1="150" x2="130" y2="158"/>
|
||||
<line x1="170" y1="150" x2="170" y2="158"/>
|
||||
</g>
|
||||
<circle cx="150" cy="150" r="3.5" fill="#00D9FF"/>
|
||||
<!-- Top reference -->
|
||||
<path d="M 150 24 L 144 14 L 156 14 Z" fill="#FFB020"/>
|
||||
<!-- Live roll triangle on outer -->
|
||||
<g transform="translate(150,150) rotate(-4)">
|
||||
<path d="M 0 -140 L -5 -148 L 5 -148 Z" fill="#00D9FF"/>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="att-row">
|
||||
<div class="att-cell">
|
||||
<div class="l">Roll</div>
|
||||
<div class="v">-4.1°</div>
|
||||
<div class="s">babor</div>
|
||||
</div>
|
||||
<div class="att-cell">
|
||||
<div class="l">Pitch</div>
|
||||
<div class="v">+1.8°</div>
|
||||
<div class="s">popa abajo</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sliders-card">
|
||||
<h4 style="font-size:11px; letter-spacing:1.5px; color:var(--c-fog); margin:0; text-transform:uppercase; font-weight:700;">Sliders de trim</h4>
|
||||
<div class="sliders-grid">
|
||||
<div class="slider-block">
|
||||
<span class="name">ME_PORT</span>
|
||||
<div class="v-slider"><div class="v-handle" style="top: 38%;">+12</div></div>
|
||||
<span class="pct">+12%</span>
|
||||
</div>
|
||||
<div class="slider-block">
|
||||
<span class="name">ME_STBD</span>
|
||||
<div class="v-slider"><div class="v-handle" style="top: 42%;">+8</div></div>
|
||||
<span class="pct">+8%</span>
|
||||
</div>
|
||||
<div class="slider-block">
|
||||
<span class="name">TAB_PORT</span>
|
||||
<div class="v-slider"><div class="v-handle" style="top: 65%;">−30</div></div>
|
||||
<span class="pct">−30%</span>
|
||||
</div>
|
||||
<div class="slider-block">
|
||||
<span class="name">TAB_STBD</span>
|
||||
<div class="v-slider"><div class="v-handle" style="top: 50%;">0</div></div>
|
||||
<span class="pct">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="env-card">
|
||||
<h4>Safety envelope</h4>
|
||||
<div class="env-bar">
|
||||
<div class="env-pointer" style="left: 46%;"></div>
|
||||
</div>
|
||||
<div class="env-scale">
|
||||
<span>−18°</span>
|
||||
<span>−12°</span>
|
||||
<span>−8°</span>
|
||||
<span>0°</span>
|
||||
<span>+8°</span>
|
||||
<span>+12°</span>
|
||||
<span>+18°</span>
|
||||
</div>
|
||||
<div class="env-msg">
|
||||
<svg class="ic" viewBox="0 0 24 24" style="color: var(--c-ok);"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
Roll dentro del rango seguro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="manual-card">
|
||||
<div class="label">
|
||||
<div class="ttl">Modo manual del owner</div>
|
||||
<div class="desc">Desactiva L1/L2. Mantiene L3 + envelope ±10°.</div>
|
||||
<div class="biometric">
|
||||
<svg class="ic" viewBox="0 0 24 24" style="width:12px;height:12px;"><path d="M12 2a4 4 0 0 0-4 4v6a4 4 0 0 0 8 0V6a4 4 0 0 0-4-4z"/><path d="M5 18s2 3 7 3 7-3 7-3"/></svg>
|
||||
Requiere FaceID + TOTP
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle"></div>
|
||||
</div>
|
||||
|
||||
<button class="em-btn pulse-emergency">
|
||||
<svg class="ic-big" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v6M12 16h.01"/></svg>
|
||||
RESET TRIMS
|
||||
<span class="sub">Lleva todos a neutral · acción cubierta por log book</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<nav class="tab-bar">
|
||||
<div class="tab">
|
||||
<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="tab">
|
||||
<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3v18"/></svg>
|
||||
Mímicos
|
||||
</div>
|
||||
<div class="tab">
|
||||
<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"/></svg>
|
||||
Alarmas
|
||||
</div>
|
||||
<div class="tab active">
|
||||
<svg class="ic" viewBox="0 0 24 24"><path d="M6 3v18M18 3v18"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
Trim
|
||||
</div>
|
||||
<div class="tab">
|
||||
<svg class="ic" viewBox="0 0 24 24"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>
|
||||
Más
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="desc-panel">
|
||||
<span class="overline">Panel destacado</span>
|
||||
<h2>Trim desde la palma de la mano</h2>
|
||||
<p>El owner ajusta el trim desde el flybridge mientras conduce. Botones grandes pensados para manos mojadas y guantes. Touch-targets ≥44pt.</p>
|
||||
<p>El sistema mantiene <strong>L3 + envelope ±10°</strong> incluso en modo manual. La predicción del envelope bloquea movimientos peligrosos antes de ejecutarse.</p>
|
||||
<div class="quote">
|
||||
"El RESET es físico en consola + virtual en cada UI. Un toque, sin doble confirmación — es emergencia."
|
||||
<div style="margin-top: 6px; font-style: normal; font-size: 11px; color: var(--c-fog); letter-spacing: 1px;">— Parte 1 sec 8, Reset de Emergencia</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,490 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor · Panel de alarmas</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body { overflow: hidden; }
|
||||
.rt {
|
||||
display: grid;
|
||||
grid-template-rows: 56px 1fr 32px;
|
||||
grid-template-columns: 240px 1fr;
|
||||
grid-template-areas:
|
||||
"topbar topbar"
|
||||
"sidebar main"
|
||||
"ticker ticker";
|
||||
height: 100vh;
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.breadcrumb { display: flex; align-items: center; gap: var(--s-3); font-size: 13px; color: var(--c-fog); }
|
||||
.breadcrumb img { height: 28px; }
|
||||
.breadcrumb strong { color: var(--c-foam); font-family: var(--f-display); font-size: 16px; }
|
||||
.breadcrumb .sep { opacity: 0.5; }
|
||||
.breadcrumb .system { color: var(--c-cyan); font-weight: 600; }
|
||||
.top-spacer { flex: 1; }
|
||||
.stats-strip {
|
||||
display: flex; gap: var(--s-5); align-items: center;
|
||||
font-family: var(--f-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
.stats-strip .item { display: flex; gap: 6px; align-items: baseline; }
|
||||
.stats-strip .num { font-size: 18px; font-weight: 600; color: var(--c-foam); }
|
||||
.stats-strip .num.emergency { color: var(--c-emergency); }
|
||||
.stats-strip .num.high { color: var(--c-high); }
|
||||
.stats-strip .num.low { color: var(--c-warn); }
|
||||
.stats-strip .num.info { color: var(--c-info); }
|
||||
.stats-strip .lbl { color: var(--c-fog); text-transform: uppercase; letter-spacing: 1.5px; font-size: 10px; font-weight: 700;}
|
||||
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
background: var(--c-midnight);
|
||||
border-right: 1px solid var(--c-steel);
|
||||
padding: var(--s-4) 0;
|
||||
}
|
||||
.sb-title { font-size: 10px; font-weight: 700; letter-spacing: 2px;
|
||||
color: var(--c-fog); text-transform: uppercase;
|
||||
padding: var(--s-3) var(--s-4) var(--s-2); }
|
||||
.nav-item {
|
||||
display: flex; align-items: center; gap: var(--s-2);
|
||||
padding: 10px 16px;
|
||||
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(255,176,32,0.14), transparent);
|
||||
color: var(--c-foam);
|
||||
box-shadow: inset 2px 0 0 var(--c-warn);
|
||||
}
|
||||
.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;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
overflow: hidden;
|
||||
background: var(--g-deep-sea);
|
||||
}
|
||||
.summary {
|
||||
padding: var(--s-5);
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr 1fr 1fr 1fr;
|
||||
gap: var(--s-4);
|
||||
}
|
||||
.sum-emergency {
|
||||
padding: var(--s-5);
|
||||
border-radius: var(--r-3);
|
||||
background: linear-gradient(135deg, rgba(255,59,71,0.18), rgba(255,59,71,0.04));
|
||||
border: 1px solid rgba(255,59,71,0.45);
|
||||
box-shadow: var(--glow-emergency);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sum-emergency::before {
|
||||
content: ""; position: absolute;
|
||||
top: 0; left: 0; right: 0; height: 3px;
|
||||
background: var(--g-emergency);
|
||||
}
|
||||
.sum-card {
|
||||
padding: var(--s-5);
|
||||
border-radius: var(--r-3);
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sum-card::before {
|
||||
content: ""; position: absolute;
|
||||
top: 0; left: 0; right: 0; height: 3px;
|
||||
background: var(--c-iron);
|
||||
}
|
||||
.sum-card.high::before { background: var(--g-warn); filter: hue-rotate(-15deg); }
|
||||
.sum-card.low::before { background: var(--g-warn); }
|
||||
.sum-card.info::before { background: var(--c-info); }
|
||||
.sum-card.ok::before { background: var(--g-ok); }
|
||||
.sum-card .lbl {
|
||||
font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
|
||||
color: var(--c-fog); font-weight: 700;
|
||||
}
|
||||
.sum-card .num {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 56px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: var(--c-foam);
|
||||
letter-spacing: -2px;
|
||||
margin: var(--s-3) 0 0;
|
||||
}
|
||||
.sum-card .sub { font-size: 11px; color: var(--c-fog); margin-top: 6px; }
|
||||
.sum-emergency .num { color: var(--c-emergency); }
|
||||
.sum-card.high .num { color: var(--c-high); }
|
||||
.sum-card.low .num { color: var(--c-warn); }
|
||||
.sum-card.info .num { color: var(--c-info); }
|
||||
.sum-card.ok .num { color: var(--c-ok); }
|
||||
|
||||
/* Filters */
|
||||
.filters {
|
||||
padding: 0 var(--s-5) var(--s-3);
|
||||
display: flex; gap: var(--s-2); align-items: center;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
}
|
||||
.filter-pill {
|
||||
padding: 6px 14px;
|
||||
border-radius: var(--r-pill);
|
||||
background: var(--c-steel);
|
||||
border: 1px solid transparent;
|
||||
color: var(--c-sand);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 120ms;
|
||||
}
|
||||
.filter-pill:hover { background: var(--c-iron); }
|
||||
.filter-pill.active {
|
||||
background: rgba(0,217,255,0.12);
|
||||
border-color: var(--c-cyan);
|
||||
color: var(--c-cyan);
|
||||
}
|
||||
.filter-pill .count {
|
||||
margin-left: 6px;
|
||||
padding: 1px 6px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: var(--r-pill);
|
||||
font-size: 10px;
|
||||
font-family: var(--f-mono);
|
||||
}
|
||||
.filter-spacer { flex: 1; }
|
||||
.search {
|
||||
width: 280px;
|
||||
padding: 8px 14px;
|
||||
background: var(--c-steel);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-pill);
|
||||
color: var(--c-foam);
|
||||
font-size: 12px;
|
||||
outline: none;
|
||||
}
|
||||
.search:focus { border-color: var(--c-cyan); box-shadow: 0 0 0 3px rgba(0,217,255,0.18); }
|
||||
|
||||
/* Alarm list */
|
||||
.alarm-list {
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
}
|
||||
.alarm-row {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 120px 2fr 1fr auto;
|
||||
gap: var(--s-4);
|
||||
padding: var(--s-4) var(--s-5);
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
transition: background 120ms;
|
||||
}
|
||||
.alarm-row:hover { background: rgba(255,255,255,0.02); }
|
||||
.alarm-row.emergency {
|
||||
background: linear-gradient(90deg, rgba(255,59,71,0.08), transparent 40%);
|
||||
border-left: 3px solid var(--c-emergency);
|
||||
}
|
||||
.alarm-row.high {
|
||||
background: linear-gradient(90deg, rgba(255,128,48,0.06), transparent 40%);
|
||||
border-left: 3px solid var(--c-high);
|
||||
}
|
||||
.alarm-row.low {
|
||||
background: linear-gradient(90deg, rgba(255,176,32,0.04), transparent 40%);
|
||||
border-left: 3px solid var(--c-warn);
|
||||
}
|
||||
.alarm-row.info { border-left: 3px solid var(--c-info); }
|
||||
.alarm-row.cleared { opacity: 0.55; }
|
||||
.a-time {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 12px;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.a-time .ago { font-size: 10px; color: var(--c-fog); display: block; margin-top: 2px; }
|
||||
.a-tag {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 12px;
|
||||
color: var(--c-cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
.a-tag .sys { display: block; color: var(--c-fog); font-size: 10px; margin-top: 2px; font-weight: 400; }
|
||||
.a-msg {
|
||||
font-size: 14px;
|
||||
color: var(--c-foam);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.a-msg .detail { display: block; color: var(--c-fog); font-size: 12px; margin-top: 2px; font-family: var(--f-mono); }
|
||||
.a-state { display: flex; flex-direction: column; gap: 4px; }
|
||||
.a-actions { display: flex; gap: var(--s-2); }
|
||||
.btn-sm {
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
border-radius: var(--r-2);
|
||||
border: 1px solid var(--c-iron);
|
||||
background: transparent;
|
||||
color: var(--c-sand);
|
||||
cursor: pointer;
|
||||
transition: all 120ms;
|
||||
}
|
||||
.btn-sm:hover { background: var(--c-steel); }
|
||||
.btn-sm.primary { background: var(--g-cyan); border: none; color: #04111F; font-weight: 600; }
|
||||
.btn-sm.danger { background: var(--g-emergency); border: none; color: var(--c-foam); }
|
||||
|
||||
.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-emergency);
|
||||
box-shadow: var(--glow-emergency);
|
||||
animation: heartbeat 1s ease-in-out infinite;
|
||||
}
|
||||
@keyframes heartbeat {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.5); opacity: 0.5; }
|
||||
}
|
||||
.tk-spacer { flex: 1; }
|
||||
.ic { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }
|
||||
|
||||
/* Pulse glow on critical row */
|
||||
@keyframes pulseRow {
|
||||
0%, 100% { background-color: rgba(255,59,71,0.08); }
|
||||
50% { background-color: rgba(255,59,71,0.16); }
|
||||
}
|
||||
.alarm-row.emergency.active { animation: pulseRow 1500ms ease-in-out infinite; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root rt">
|
||||
|
||||
<header class="topbar">
|
||||
<div class="breadcrumb">
|
||||
<img src="../brand/logo-mark.svg" alt="">
|
||||
<strong>M/Y Aurora</strong>
|
||||
<span class="sep">/</span>
|
||||
<span class="system">Alarmas</span>
|
||||
</div>
|
||||
<div class="top-spacer"></div>
|
||||
<div class="stats-strip">
|
||||
<div class="item"><span class="num emergency">1</span><span class="lbl">Emergency</span></div>
|
||||
<div class="item"><span class="num high">0</span><span class="lbl">High</span></div>
|
||||
<div class="item"><span class="num low">2</span><span class="lbl">Low</span></div>
|
||||
<div class="item"><span class="num info">1</span><span class="lbl">Info</span></div>
|
||||
</div>
|
||||
<button class="btn btn-secondary">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>
|
||||
Limpiar resueltas
|
||||
</button>
|
||||
<button class="btn btn-primary">
|
||||
<svg class="ic" viewBox="0 0 24 24" style="color:#04111F"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
ACK todas (no críticas)
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="sb-title">Filtros rápidos</div>
|
||||
<div class="nav-item active">
|
||||
🔔 Activas no ACK
|
||||
<span class="alarm-badge">4</span>
|
||||
</div>
|
||||
<div class="nav-item">📋 ACK pendientes resolución <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">1</span></div>
|
||||
<div class="nav-item">✓ Resueltas 24h <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">18</span></div>
|
||||
<div class="nav-item">📜 Histórico completo</div>
|
||||
|
||||
<div class="sb-title">Por prioridad</div>
|
||||
<div class="nav-item"><span class="dot emergency"></span> Emergency <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">1</span></div>
|
||||
<div class="nav-item"><span class="dot warn"></span> High <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">0</span></div>
|
||||
<div class="nav-item"><span class="dot warn" style="background: var(--c-warn)"></span> Low <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">2</span></div>
|
||||
<div class="nav-item"><span class="dot cyan"></span> Info <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">1</span></div>
|
||||
|
||||
<div class="sb-title">Por sistema</div>
|
||||
<div class="nav-item">⚙ Máquina principal <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">1</span></div>
|
||||
<div class="nav-item">⚡ Generación <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">2</span></div>
|
||||
<div class="nav-item">🛁 Sentinas <span style="margin-left:auto; font-family: var(--f-mono); color: var(--c-fog); font-size: 11px;">1</span></div>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
<div class="summary">
|
||||
<div class="sum-emergency pulse-emergency">
|
||||
<div class="lbl" style="color: var(--c-emergency);">🚨 Acción inmediata</div>
|
||||
<div class="num">1</div>
|
||||
<div class="sub" style="color: var(--c-sand);">ME_PORT.OIL_PRESS < 1.5 bar</div>
|
||||
</div>
|
||||
<div class="sum-card high">
|
||||
<div class="lbl">High</div>
|
||||
<div class="num">0</div>
|
||||
<div class="sub">Sin alarmas alta</div>
|
||||
</div>
|
||||
<div class="sum-card low">
|
||||
<div class="lbl">Low</div>
|
||||
<div class="num">2</div>
|
||||
<div class="sub">GEN coolant · BILGE</div>
|
||||
</div>
|
||||
<div class="sum-card info">
|
||||
<div class="lbl">Info</div>
|
||||
<div class="num">1</div>
|
||||
<div class="sub">Shore power offline</div>
|
||||
</div>
|
||||
<div class="sum-card ok">
|
||||
<div class="lbl">MTBF 7 días</div>
|
||||
<div class="num">42<span style="font-size:18px;color:var(--c-fog);"> h</span></div>
|
||||
<div class="sub">▲ +6h vs semana ant.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<button class="filter-pill active">Todas <span class="count">4</span></button>
|
||||
<button class="filter-pill">Sin ACK <span class="count">4</span></button>
|
||||
<button class="filter-pill">Emergency <span class="count">1</span></button>
|
||||
<button class="filter-pill">High</button>
|
||||
<button class="filter-pill">Low <span class="count">2</span></button>
|
||||
<button class="filter-pill">Info <span class="count">1</span></button>
|
||||
<div class="filter-spacer"></div>
|
||||
<input class="search" placeholder="🔍 Buscar tag, mensaje o sistema…">
|
||||
</div>
|
||||
|
||||
<div class="alarm-list">
|
||||
|
||||
<div class="alarm-row emergency active">
|
||||
<div class="a-time">03:43:01<div class="ago">hace 12 s</div></div>
|
||||
<div class="a-tag">ME_PORT.OIL_PRESS<div class="sys">Máquina principal</div></div>
|
||||
<div class="a-msg">
|
||||
Presión aceite crítica baja — <strong>1.2 bar</strong>
|
||||
<span class="detail">threshold < 1.5 bar · hysteresis 0.2 · delay 2 s</span>
|
||||
</div>
|
||||
<div class="a-state">
|
||||
<span class="badge badge-emergency">EMERGENCY</span>
|
||||
<span class="caption" style="color:var(--c-emergency)">⚠ ACTIVE · sin ACK</span>
|
||||
</div>
|
||||
<div class="a-actions">
|
||||
<button class="btn-sm danger">ACK</button>
|
||||
<button class="btn-sm">Detalle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarm-row low">
|
||||
<div class="a-time">03:38:42<div class="ago">hace 5 min</div></div>
|
||||
<div class="a-tag">GEN_1.COOLANT_TEMP<div class="sys">Generación</div></div>
|
||||
<div class="a-msg">
|
||||
Temperatura refrigerante alta — <strong>89°C</strong>
|
||||
<span class="detail">threshold > 88°C · approaching emergency > 92°C</span>
|
||||
</div>
|
||||
<div class="a-state">
|
||||
<span class="badge badge-low">LOW</span>
|
||||
<span class="caption">ACTIVE · sin ACK</span>
|
||||
</div>
|
||||
<div class="a-actions">
|
||||
<button class="btn-sm primary">ACK</button>
|
||||
<button class="btn-sm">Detalle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarm-row low">
|
||||
<div class="a-time">03:31:17<div class="ago">hace 12 min</div></div>
|
||||
<div class="a-tag">BILGE_MID.LEVEL<div class="sys">Sentinas</div></div>
|
||||
<div class="a-msg">
|
||||
Nivel sentina central — <strong>12%</strong>
|
||||
<span class="detail">threshold > 10% · verificar pump cycle</span>
|
||||
</div>
|
||||
<div class="a-state">
|
||||
<span class="badge badge-low">LOW</span>
|
||||
<span class="caption">ACTIVE · sin ACK</span>
|
||||
</div>
|
||||
<div class="a-actions">
|
||||
<button class="btn-sm primary">ACK</button>
|
||||
<button class="btn-sm">Detalle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarm-row info">
|
||||
<div class="a-time">02:58:30<div class="ago">hace 45 min</div></div>
|
||||
<div class="a-tag">SHORE_POWER.STATUS<div class="sys">Generación</div></div>
|
||||
<div class="a-msg">
|
||||
Transferencia a generador — <strong>desconexión muelle</strong>
|
||||
<span class="detail">automatic transfer switch · GEN_1 picked up load</span>
|
||||
</div>
|
||||
<div class="a-state">
|
||||
<span class="badge badge-info">INFO</span>
|
||||
<span class="caption">ACTIVE · sin ACK</span>
|
||||
</div>
|
||||
<div class="a-actions">
|
||||
<button class="btn-sm primary">ACK</button>
|
||||
<button class="btn-sm">Detalle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarm-row cleared">
|
||||
<div class="a-time">03:12:04<div class="ago">hace 31 min</div></div>
|
||||
<div class="a-tag">ME_PORT.OIL_TEMP<div class="sys">Máquina principal</div></div>
|
||||
<div class="a-msg">
|
||||
Temperatura aceite recuperada — <strong>88°C</strong>
|
||||
<span class="detail">cleared at 03:18 · ACK por Carlos · duración 6 min</span>
|
||||
</div>
|
||||
<div class="a-state">
|
||||
<span class="badge badge-muted">CLEARED</span>
|
||||
<span class="caption" style="color:var(--c-ok)">✓ resolved</span>
|
||||
</div>
|
||||
<div class="a-actions">
|
||||
<button class="btn-sm">Detalle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarm-row cleared">
|
||||
<div class="a-time">02:42:11<div class="ago">hace 1 h</div></div>
|
||||
<div class="a-tag">GEN_1.VOLTAGE_L1<div class="sys">Generación</div></div>
|
||||
<div class="a-msg">
|
||||
Tensión L1 normalizada — <strong>231 V</strong>
|
||||
<span class="detail">cleared at 02:45 · ACK por sistema · duración 3 min</span>
|
||||
</div>
|
||||
<div class="a-state">
|
||||
<span class="badge badge-muted">CLEARED</span>
|
||||
<span class="caption" style="color:var(--c-ok)">✓ resolved</span>
|
||||
</div>
|
||||
<div class="a-actions">
|
||||
<button class="btn-sm">Detalle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="ticker">
|
||||
<span class="pulse"></span>
|
||||
<span style="color: var(--c-emergency); font-weight: 700;">ALARMA CRÍTICA SIN ACK · ME_PORT.OIL_PRESS</span>
|
||||
<span class="sep">|</span>
|
||||
<span>Próxima escalación en 4:48</span>
|
||||
<span class="tk-spacer"></span>
|
||||
<span>Operador: Álvaro</span>
|
||||
<span class="sep">|</span>
|
||||
<span>v0.1.0.dev0</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,548 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor · Mímico Combustible</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body { overflow: hidden; }
|
||||
.rt {
|
||||
display: grid;
|
||||
grid-template-rows: 56px 1fr 32px;
|
||||
grid-template-columns: 240px 1fr 320px;
|
||||
grid-template-areas:
|
||||
"topbar topbar topbar"
|
||||
"sidebar canvas inspector"
|
||||
"ticker ticker ticker";
|
||||
height: 100vh;
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.breadcrumb {
|
||||
display: flex; align-items: center; gap: var(--s-3);
|
||||
font-size: 13px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
.breadcrumb img { height: 28px; }
|
||||
.breadcrumb strong { color: var(--c-foam); font-family: var(--f-display); font-size: 16px; }
|
||||
.breadcrumb .sep { opacity: 0.5; }
|
||||
.breadcrumb .system { color: var(--c-cyan); font-weight: 600; }
|
||||
.topbar .top-spacer { flex: 1; }
|
||||
.topbar .actions { display: flex; gap: var(--s-2); align-items: center; }
|
||||
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
background: var(--c-midnight);
|
||||
border-right: 1px solid var(--c-steel);
|
||||
padding: var(--s-4) 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.sb-title { font-size: 10px; font-weight: 700; letter-spacing: 2px;
|
||||
color: var(--c-fog); text-transform: uppercase;
|
||||
padding: var(--s-4) var(--s-4) var(--s-2); }
|
||||
.nav-item {
|
||||
display: flex; align-items: center; gap: var(--s-2);
|
||||
padding: 10px 16px;
|
||||
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);
|
||||
}
|
||||
|
||||
/* Canvas mimic */
|
||||
.canvas {
|
||||
grid-area: canvas;
|
||||
position: relative;
|
||||
background:
|
||||
radial-gradient(circle at 30% 20%, rgba(0,217,255,0.04), transparent 60%),
|
||||
var(--c-abyss);
|
||||
overflow: hidden;
|
||||
}
|
||||
.canvas-bg-grid {
|
||||
position: absolute; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(91,192,235,0.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(91,192,235,0.04) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
.mimic-header {
|
||||
position: absolute;
|
||||
top: var(--s-4); left: var(--s-5);
|
||||
z-index: 3;
|
||||
}
|
||||
.mimic-header h2 {
|
||||
margin: 0;
|
||||
font-family: var(--f-display);
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.mimic-header .sub {
|
||||
font-size: 12px;
|
||||
color: var(--c-fog);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.canvas-actions {
|
||||
position: absolute;
|
||||
top: var(--s-4); right: var(--s-5);
|
||||
display: flex; gap: var(--s-2);
|
||||
z-index: 3;
|
||||
}
|
||||
.legend {
|
||||
position: absolute;
|
||||
bottom: var(--s-5); left: var(--s-5);
|
||||
padding: var(--s-3) var(--s-4);
|
||||
background: var(--g-glass);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: var(--r-3);
|
||||
display: flex; gap: var(--s-5);
|
||||
font-size: 11px;
|
||||
color: var(--c-fog);
|
||||
z-index: 3;
|
||||
}
|
||||
.legend .swatch {
|
||||
display: inline-block; width: 10px; height: 10px;
|
||||
border-radius: 2px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mimic-svg {
|
||||
position: absolute;
|
||||
inset: 80px 0 60px 0;
|
||||
width: 100%; height: calc(100% - 140px);
|
||||
}
|
||||
|
||||
.inspector {
|
||||
grid-area: inspector;
|
||||
background: var(--c-midnight);
|
||||
border-left: 1px solid var(--c-steel);
|
||||
padding: var(--s-5);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.insp-tag {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 14px;
|
||||
color: var(--c-cyan);
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.insp-name {
|
||||
font-family: var(--f-display);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
margin: 0 0 var(--s-3);
|
||||
}
|
||||
.insp-value {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 56px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
letter-spacing: -2px;
|
||||
line-height: 1;
|
||||
}
|
||||
.insp-value .unit { font-size: 18px; color: var(--c-fog); margin-left: 6px; font-weight: 400; }
|
||||
.insp-trend {
|
||||
margin-top: var(--s-3);
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
}
|
||||
.insp-row {
|
||||
display: grid;
|
||||
grid-template-columns: 110px 1fr;
|
||||
gap: var(--s-3);
|
||||
padding: var(--s-2) 0;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
}
|
||||
.insp-row:last-child { border-bottom: none; }
|
||||
.insp-row .k { color: var(--c-fog); }
|
||||
.insp-row .v { font-family: var(--f-mono); color: var(--c-foam); }
|
||||
|
||||
.alarm-section {
|
||||
margin-top: var(--s-4);
|
||||
padding: var(--s-3);
|
||||
background: rgba(255,176,32,0.06);
|
||||
border: 1px solid rgba(255,176,32,0.25);
|
||||
border-radius: var(--r-2);
|
||||
}
|
||||
.alarm-section .ttl { font-size: 11px; letter-spacing: 1.5px; color: var(--c-warn); font-weight: 700; text-transform: uppercase; }
|
||||
|
||||
/* Flow animations */
|
||||
@keyframes flowDash { to { stroke-dashoffset: -40; } }
|
||||
.flow { stroke-dasharray: 8 8; animation: flowDash 1.6s linear infinite; }
|
||||
.flow.fast { animation-duration: 0.8s; }
|
||||
|
||||
@keyframes pumpSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||
.pump-rotor { animation: pumpSpin 0.7s linear infinite; transform-origin: center; transform-box: fill-box; }
|
||||
|
||||
.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: currentColor; fill: none; stroke-width: 2; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root rt">
|
||||
|
||||
<header class="topbar">
|
||||
<div class="breadcrumb">
|
||||
<img src="../brand/logo-mark.svg" alt="">
|
||||
<strong>M/Y Aurora</strong>
|
||||
<span class="sep">/</span>
|
||||
<span>Mímicos</span>
|
||||
<span class="sep">/</span>
|
||||
<span class="system">Combustible</span>
|
||||
</div>
|
||||
<span class="top-spacer"></span>
|
||||
<span class="chip"><span class="dot ok"></span> 6 tags · 4 OK · 1 watch · 0 alarmas</span>
|
||||
<span class="chip">Autoridad: <strong style="color:var(--c-cyan);margin-left:4px;">MÁQUINAS</strong></span>
|
||||
<div class="actions">
|
||||
<button class="btn-icon" title="Pausa updates"><svg class="ic" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg></button>
|
||||
<button class="btn btn-secondary">Trends</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="sb-title">Sistemas</div>
|
||||
<div class="nav-item">⚙ Máquina principal</div>
|
||||
<div class="nav-item">⚡ Generación</div>
|
||||
<div class="nav-item active">⛽ Combustible</div>
|
||||
<div class="nav-item">💧 Refrigeración FW</div>
|
||||
<div class="nav-item">🌊 Refrigeración SW</div>
|
||||
<div class="nav-item">🛁 Sentinas</div>
|
||||
<div class="nav-item">💨 Aire arranque</div>
|
||||
<div class="nav-item">🌬 HVAC</div>
|
||||
<div class="nav-item">💡 Iluminación</div>
|
||||
<div class="sb-title">Combustible</div>
|
||||
<div class="nav-item">📋 Resumen sistema</div>
|
||||
<div class="nav-item">🔄 Transferencia tanques</div>
|
||||
<div class="nav-item">🧪 Calidad y filtros</div>
|
||||
<div class="nav-item">📊 Consumo histórico</div>
|
||||
</aside>
|
||||
|
||||
<main class="canvas">
|
||||
<div class="canvas-bg-grid"></div>
|
||||
|
||||
<div class="mimic-header">
|
||||
<h2>Sistema de combustible</h2>
|
||||
<div class="sub">2 tanques estructurales · 2 motores principales · 1 genset · Diesel marino MDO</div>
|
||||
</div>
|
||||
|
||||
<div class="canvas-actions">
|
||||
<button class="btn-icon" title="Centrar"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/></svg></button>
|
||||
<button class="btn-icon" title="Imprimir P&ID"><svg class="ic" viewBox="0 0 24 24"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg></button>
|
||||
<button class="btn btn-secondary">Calidad de filtros</button>
|
||||
</div>
|
||||
|
||||
<svg class="mimic-svg" viewBox="0 0 1100 680" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="tank" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#0A1A2E"/>
|
||||
<stop offset="100%" stop-color="#1A2B42"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fuelFill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#1B7FB5"/>
|
||||
<stop offset="100%" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fuelFill2" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#1B7FB5"/>
|
||||
<stop offset="100%" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="pumpGlow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="rgba(0,217,255,0.5)"/>
|
||||
<stop offset="100%" stop-color="transparent"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- ============ TITLE LABELS ============ -->
|
||||
<g font-family="JetBrains Mono" font-size="10" fill="#7C8B9F" letter-spacing="1">
|
||||
<text x="40" y="40">P&ID · FUEL</text>
|
||||
<text x="40" y="56">Rev 1.0 · Sprint 0</text>
|
||||
</g>
|
||||
|
||||
<!-- ============ TANKS ============ -->
|
||||
<!-- Tank 1 -->
|
||||
<g transform="translate(80,140)">
|
||||
<rect x="0" y="0" width="180" height="280" rx="10" fill="url(#tank)" stroke="#2C3E5C" stroke-width="2"/>
|
||||
<rect x="6" y="68" width="168" height="206" rx="6" fill="url(#fuelFill)" opacity="0.85"/>
|
||||
<rect x="6" y="64" width="168" height="8" fill="#00D9FF" opacity="0.4" filter="blur(2px)"/>
|
||||
<text x="90" y="-12" text-anchor="middle" font-family="JetBrains Mono" font-size="13" font-weight="700" fill="#E6EAF0">TANK_FUEL_1</text>
|
||||
<text x="90" y="320" text-anchor="middle" font-family="Inter" font-size="11" fill="#7C8B9F">Capacidad 3500 L</text>
|
||||
|
||||
<!-- Level meter on tank side -->
|
||||
<line x1="-12" y1="0" x2="-12" y2="280" stroke="#7C8B9F" stroke-width="1"/>
|
||||
<g font-family="JetBrains Mono" font-size="9" fill="#7C8B9F" text-anchor="end">
|
||||
<line x1="-18" y1="0" x2="-12" y2="0"/><text x="-22" y="4">100%</text>
|
||||
<line x1="-18" y1="70" x2="-12" y2="70"/><text x="-22" y="74">75%</text>
|
||||
<line x1="-18" y1="140" x2="-12" y2="140"/><text x="-22" y="144">50%</text>
|
||||
<line x1="-18" y1="210" x2="-12" y2="210"/><text x="-22" y="214">25%</text>
|
||||
<line x1="-18" y1="280" x2="-12" y2="280"/><text x="-22" y="284">0%</text>
|
||||
</g>
|
||||
|
||||
<!-- Level value -->
|
||||
<g transform="translate(90,150)">
|
||||
<rect x="-50" y="-26" width="100" height="52" rx="8" fill="rgba(4,17,31,0.85)" stroke="#00D9FF" stroke-width="1.5"/>
|
||||
<text text-anchor="middle" y="0" font-family="JetBrains Mono" font-size="24" font-weight="700" fill="#F2F5F9">78%</text>
|
||||
<text text-anchor="middle" y="18" font-family="Inter" font-size="10" fill="#7C8B9F">2,730 L</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Tank 2 -->
|
||||
<g transform="translate(300,140)">
|
||||
<rect x="0" y="0" width="180" height="280" rx="10" fill="url(#tank)" stroke="#2C3E5C" stroke-width="2"/>
|
||||
<rect x="6" y="105" width="168" height="169" rx="6" fill="url(#fuelFill2)" opacity="0.85"/>
|
||||
<rect x="6" y="101" width="168" height="8" fill="#00D9FF" opacity="0.4" filter="blur(2px)"/>
|
||||
<text x="90" y="-12" text-anchor="middle" font-family="JetBrains Mono" font-size="13" font-weight="700" fill="#E6EAF0">TANK_FUEL_2</text>
|
||||
<text x="90" y="320" text-anchor="middle" font-family="Inter" font-size="11" fill="#7C8B9F">Capacidad 3500 L</text>
|
||||
|
||||
<g transform="translate(90,170)">
|
||||
<rect x="-50" y="-26" width="100" height="52" rx="8" fill="rgba(4,17,31,0.85)" stroke="#00D9FF" stroke-width="1.5"/>
|
||||
<text text-anchor="middle" y="0" font-family="JetBrains Mono" font-size="24" font-weight="700" fill="#F2F5F9">64%</text>
|
||||
<text text-anchor="middle" y="18" font-family="Inter" font-size="10" fill="#7C8B9F">2,240 L</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ============ PIPES from tanks ============ -->
|
||||
<g stroke="#00D9FF" stroke-width="2.5" fill="none">
|
||||
<!-- Tank 1 outlet to manifold -->
|
||||
<path d="M 260 380 L 280 380 L 280 480 L 540 480" class="flow"/>
|
||||
<!-- Tank 2 outlet to manifold -->
|
||||
<path d="M 480 380 L 480 470 L 540 470" class="flow"/>
|
||||
</g>
|
||||
|
||||
<!-- ============ VALVES ============ -->
|
||||
<!-- Valve T1 outlet -->
|
||||
<g transform="translate(260,380)">
|
||||
<path d="M 0 -8 L 16 0 L 0 8 L -16 0 Z" fill="#00E08A" stroke="#04111F" stroke-width="1.5"/>
|
||||
<line x1="0" y1="-10" x2="0" y2="-18" stroke="#7C8B9F" stroke-width="2"/>
|
||||
<circle cx="0" cy="-22" r="4" fill="#00E08A"/>
|
||||
<text x="0" y="22" text-anchor="middle" font-family="JetBrains Mono" font-size="9" fill="#7C8B9F">V_T1_OUT</text>
|
||||
</g>
|
||||
<!-- Valve T2 outlet -->
|
||||
<g transform="translate(480,380)">
|
||||
<path d="M 0 -8 L 16 0 L 0 8 L -16 0 Z" fill="#00E08A" stroke="#04111F" stroke-width="1.5"/>
|
||||
<line x1="0" y1="-10" x2="0" y2="-18" stroke="#7C8B9F" stroke-width="2"/>
|
||||
<circle cx="0" cy="-22" r="4" fill="#00E08A"/>
|
||||
<text x="0" y="22" text-anchor="middle" font-family="JetBrains Mono" font-size="9" fill="#7C8B9F">V_T2_OUT</text>
|
||||
</g>
|
||||
|
||||
<!-- ============ FILTER ============ -->
|
||||
<g transform="translate(540,460)">
|
||||
<rect x="0" y="0" width="50" height="40" rx="4" fill="#1A2B42" stroke="#00D9FF" stroke-width="2"/>
|
||||
<line x1="0" y1="10" x2="50" y2="10" stroke="#7C8B9F"/>
|
||||
<line x1="0" y1="20" x2="50" y2="20" stroke="#7C8B9F"/>
|
||||
<line x1="0" y1="30" x2="50" y2="30" stroke="#7C8B9F"/>
|
||||
<text x="25" y="55" text-anchor="middle" font-family="JetBrains Mono" font-size="9" fill="#7C8B9F">FILTER 30µm</text>
|
||||
<text x="25" y="-6" text-anchor="middle" font-family="JetBrains Mono" font-size="8" fill="#00E08A">Δp 0.4 bar</text>
|
||||
</g>
|
||||
|
||||
<!-- ============ MAIN PUMP ============ -->
|
||||
<g transform="translate(660,480)">
|
||||
<circle cx="0" cy="0" r="32" fill="url(#pumpGlow)" />
|
||||
<circle cx="0" cy="0" r="24" fill="#1A2B42" stroke="#00D9FF" stroke-width="2.5"/>
|
||||
<g class="pump-rotor">
|
||||
<path d="M -16 0 L 16 0 M 0 -16 L 0 16" stroke="#00D9FF" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle r="4" fill="#00D9FF"/>
|
||||
</g>
|
||||
<text x="0" y="48" text-anchor="middle" font-family="JetBrains Mono" font-size="10" font-weight="700" fill="#F2F5F9">PUMP_MAIN</text>
|
||||
<text x="0" y="62" text-anchor="middle" font-family="JetBrains Mono" font-size="9" fill="#00E08A">RUNNING · 2.8 bar</text>
|
||||
</g>
|
||||
|
||||
<!-- Pipe pump to manifold up -->
|
||||
<g stroke="#00D9FF" stroke-width="2.5" fill="none">
|
||||
<path d="M 590 480 L 630 480" class="flow fast"/>
|
||||
<path d="M 692 480 L 720 480 L 720 220" class="flow fast"/>
|
||||
</g>
|
||||
|
||||
<!-- ============ MANIFOLD to engines (top) ============ -->
|
||||
<g stroke="#00D9FF" stroke-width="2.5" fill="none">
|
||||
<line x1="720" y1="220" x2="980" y2="220" class="flow"/>
|
||||
<!-- Branch to ME_PORT -->
|
||||
<line x1="780" y1="220" x2="780" y2="160" class="flow"/>
|
||||
<!-- Branch to ME_STBD -->
|
||||
<line x1="860" y1="220" x2="860" y2="160" class="flow"/>
|
||||
<!-- Branch to GEN_1 -->
|
||||
<line x1="940" y1="220" x2="940" y2="320" class="flow"/>
|
||||
</g>
|
||||
|
||||
<!-- ============ ENGINES (consumers) ============ -->
|
||||
<g transform="translate(720,80)">
|
||||
<rect x="0" y="0" width="120" height="60" rx="8" fill="#0A1A2E" stroke="#00E08A" stroke-width="2"/>
|
||||
<text x="60" y="22" text-anchor="middle" font-family="JetBrains Mono" font-size="13" font-weight="700" fill="#F2F5F9">ME_PORT</text>
|
||||
<text x="60" y="40" text-anchor="middle" font-family="JetBrains Mono" font-size="10" fill="#00E08A">1,520 rpm · 62%</text>
|
||||
<text x="60" y="54" text-anchor="middle" font-family="Inter" font-size="9" fill="#7C8B9F">Consumo 14.2 L/h</text>
|
||||
</g>
|
||||
<g transform="translate(800,80)">
|
||||
<rect x="0" y="0" width="120" height="60" rx="8" fill="#0A1A2E" stroke="#00E08A" stroke-width="2"/>
|
||||
<text x="60" y="22" text-anchor="middle" font-family="JetBrains Mono" font-size="13" font-weight="700" fill="#F2F5F9">ME_STBD</text>
|
||||
<text x="60" y="40" text-anchor="middle" font-family="JetBrains Mono" font-size="10" fill="#00E08A">1,498 rpm · 58%</text>
|
||||
<text x="60" y="54" text-anchor="middle" font-family="Inter" font-size="9" fill="#7C8B9F">Consumo 13.4 L/h</text>
|
||||
</g>
|
||||
<g transform="translate(880,340)">
|
||||
<rect x="0" y="0" width="120" height="60" rx="8" fill="#0A1A2E" stroke="#FFB020" stroke-width="2"/>
|
||||
<text x="60" y="22" text-anchor="middle" font-family="JetBrains Mono" font-size="13" font-weight="700" fill="#F2F5F9">GEN_1</text>
|
||||
<text x="60" y="40" text-anchor="middle" font-family="JetBrains Mono" font-size="10" fill="#FFB020">1,800 rpm · 88%</text>
|
||||
<text x="60" y="54" text-anchor="middle" font-family="Inter" font-size="9" fill="#7C8B9F">Consumo 16.3 L/h</text>
|
||||
</g>
|
||||
|
||||
<!-- Engine box labels (inlet valves) -->
|
||||
<g transform="translate(780,160)">
|
||||
<circle r="6" fill="#00E08A" stroke="#04111F" stroke-width="1.5"/>
|
||||
</g>
|
||||
<g transform="translate(860,160)">
|
||||
<circle r="6" fill="#00E08A" stroke="#04111F" stroke-width="1.5"/>
|
||||
</g>
|
||||
<g transform="translate(940,320)">
|
||||
<circle r="6" fill="#00E08A" stroke="#04111F" stroke-width="1.5"/>
|
||||
</g>
|
||||
|
||||
<!-- ============ DAY TANK ============ -->
|
||||
<g transform="translate(560,320)">
|
||||
<rect x="0" y="0" width="80" height="100" rx="6" fill="url(#tank)" stroke="#2C3E5C" stroke-width="2"/>
|
||||
<rect x="4" y="36" width="72" height="62" rx="3" fill="url(#fuelFill)" opacity="0.85"/>
|
||||
<rect x="4" y="34" width="72" height="6" fill="#00D9FF" opacity="0.4" filter="blur(1.5px)"/>
|
||||
<text x="40" y="-8" text-anchor="middle" font-family="JetBrains Mono" font-size="11" font-weight="700" fill="#E6EAF0">DAY TANK</text>
|
||||
<text x="40" y="118" text-anchor="middle" font-family="JetBrains Mono" font-size="11" fill="#F2F5F9">64%</text>
|
||||
</g>
|
||||
|
||||
<!-- ============ RETURN LINE ============ -->
|
||||
<g stroke="#5BC0EB" stroke-width="1.8" fill="none" stroke-dasharray="3 3" opacity="0.7">
|
||||
<path d="M 720 110 L 1040 110 L 1040 510 L 280 510 L 280 410"/>
|
||||
</g>
|
||||
<text x="900" y="105" font-family="JetBrains Mono" font-size="9" fill="#5BC0EB">RETURN LINE</text>
|
||||
|
||||
<!-- ============ PRESSURE GAUGE ============ -->
|
||||
<g transform="translate(660,420)">
|
||||
<circle r="22" fill="#0A1A2E" stroke="#00D9FF" stroke-width="2"/>
|
||||
<text text-anchor="middle" y="-4" font-family="JetBrains Mono" font-size="11" font-weight="700" fill="#F2F5F9">2.8</text>
|
||||
<text text-anchor="middle" y="10" font-family="JetBrains Mono" font-size="9" fill="#7C8B9F">bar</text>
|
||||
<path d="M 0 0 L 12 -8" stroke="#00D9FF" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
|
||||
<!-- ============ FUEL CONSUMPTION SUMMARY ============ -->
|
||||
<g transform="translate(40,520)">
|
||||
<rect x="0" y="0" width="280" height="120" rx="10" fill="rgba(4,17,31,0.7)" stroke="#1A2B42" stroke-width="1.5"/>
|
||||
<text x="16" y="22" font-family="Inter" font-size="10" font-weight="700" fill="#7C8B9F" letter-spacing="1.5">CONSUMO TOTAL ÚLTIMA HORA</text>
|
||||
<text x="16" y="64" font-family="JetBrains Mono" font-size="36" font-weight="700" fill="#F2F5F9">43.9 <tspan font-size="14" fill="#7C8B9F">L/h</tspan></text>
|
||||
<line x1="16" y1="78" x2="264" y2="78" stroke="#1A2B42"/>
|
||||
<g font-family="JetBrains Mono" font-size="10" fill="#7C8B9F">
|
||||
<text x="16" y="98">ME_PORT 14.2</text>
|
||||
<text x="100" y="98">ME_STBD 13.4</text>
|
||||
<text x="186" y="98">GEN_1 16.3</text>
|
||||
</g>
|
||||
<text x="16" y="112" font-family="Inter" font-size="10" fill="#00E08A">Autonomía a régimen: 113 h</text>
|
||||
</g>
|
||||
|
||||
<!-- Pulsing alarm marker (info) on filter pressure approaching limit -->
|
||||
<g transform="translate(570,440)">
|
||||
<circle r="12" fill="rgba(91,192,235,0.18)" stroke="#5BC0EB" stroke-width="1.5">
|
||||
<animate attributeName="r" values="10;14;10" dur="2s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="1;0.4;1" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text text-anchor="middle" y="3" font-family="Inter" font-size="9" font-weight="700" fill="#5BC0EB">i</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="legend">
|
||||
<span><span class="swatch" style="background: #00E08A;"></span> Válvula abierta</span>
|
||||
<span><span class="swatch" style="background: #FF8030;"></span> Válvula cerrada</span>
|
||||
<span><span class="swatch" style="background: #00D9FF;"></span> Flujo activo</span>
|
||||
<span><span class="swatch" style="background: transparent; border: 1px dashed #5BC0EB;"></span> Retorno</span>
|
||||
<span><span class="swatch" style="background: rgba(91,192,235,0.3); border: 1px solid #5BC0EB;"></span> Info / watch</span>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="inspector">
|
||||
<div class="insp-tag">TANK_FUEL_1.LEVEL</div>
|
||||
<h3 class="insp-name">Nivel tanque combustible 1</h3>
|
||||
|
||||
<div class="insp-value">78<span class="unit">%</span></div>
|
||||
|
||||
<svg class="insp-trend" viewBox="0 0 280 60" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="tArea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="rgba(0,217,255,0.45)"/>
|
||||
<stop offset="100%" stop-color="rgba(0,217,255,0)"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M 0 30 L 30 28 L 60 33 L 90 26 L 120 24 L 150 22 L 180 19 L 210 14 L 240 13 L 280 12 L 280 60 L 0 60 Z" fill="url(#tArea)"/>
|
||||
<path d="M 0 30 L 30 28 L 60 33 L 90 26 L 120 24 L 150 22 L 180 19 L 210 14 L 240 13 L 280 12" stroke="#00D9FF" stroke-width="2" fill="none"/>
|
||||
<circle cx="280" cy="12" r="4" fill="#00D9FF"/>
|
||||
</svg>
|
||||
<div style="display: flex; justify-content: space-between; font-family: var(--f-mono); font-size: 10px; color: var(--c-fog); margin-top: 4px;">
|
||||
<span>-6h</span><span>-3h</span><span>now</span>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: var(--s-5);">
|
||||
<div class="insp-row"><span class="k">Volumen</span><span class="v">2,730 L</span></div>
|
||||
<div class="insp-row"><span class="k">Capacidad</span><span class="v">3,500 L</span></div>
|
||||
<div class="insp-row"><span class="k">Range normal</span><span class="v">10 – 100%</span></div>
|
||||
<div class="insp-row"><span class="k">Calidad</span><span class="v" style="color:var(--c-ok)">GOOD</span></div>
|
||||
<div class="insp-row"><span class="k">Última lectura</span><span class="v">42 ms</span></div>
|
||||
<div class="insp-row"><span class="k">Protocolo</span><span class="v">MODBUS_RTU</span></div>
|
||||
<div class="insp-row"><span class="k">Tarjeta</span><span class="v">card_004 / AI3</span></div>
|
||||
<div class="insp-row"><span class="k">Filtro</span><span class="v">MOVING_AVG · 8</span></div>
|
||||
<div class="insp-row"><span class="k">Historizando</span><span class="v" style="color:var(--c-ok)">SÍ · 1s</span></div>
|
||||
</div>
|
||||
|
||||
<div class="alarm-section">
|
||||
<div class="ttl">2 alarmas configuradas</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; font-family: var(--f-mono); color: var(--c-sand);">
|
||||
<div>LOW < 15% <span style="color:var(--c-warn)">LOW</span></div>
|
||||
<div>LOW_LOW < 5% <span style="color:var(--c-emergency)">EMERGENCY</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: var(--s-5);">
|
||||
<button class="btn btn-secondary" style="width: 100%;">Abrir trend completo</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<footer class="ticker">
|
||||
<span class="pulse"></span>
|
||||
<span>WebSocket LIVE</span>
|
||||
<span class="sep">|</span>
|
||||
<span>187 tags activos</span>
|
||||
<span class="sep">|</span>
|
||||
<span>2 alarmas</span>
|
||||
<span class="tk-spacer"></span>
|
||||
<span>v0.1.0.dev0 · Sprint 0</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,774 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,763 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor · Trim & Maniobra</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body { overflow: hidden; }
|
||||
.rt {
|
||||
display: grid;
|
||||
grid-template-rows: 56px 1fr 32px;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas:
|
||||
"topbar"
|
||||
"main"
|
||||
"ticker";
|
||||
height: 100vh;
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.breadcrumb { display: flex; align-items: center; gap: var(--s-3); font-size: 13px; color: var(--c-fog); }
|
||||
.breadcrumb img { height: 28px; }
|
||||
.breadcrumb strong { color: var(--c-foam); font-family: var(--f-display); font-size: 16px; }
|
||||
.breadcrumb .sep { opacity: 0.5; }
|
||||
.breadcrumb .system { color: var(--c-cyan); font-weight: 600; }
|
||||
.top-spacer { flex: 1; }
|
||||
.mode-banner {
|
||||
padding: 8px 20px;
|
||||
background: linear-gradient(135deg, rgba(0,224,138,0.14), rgba(0,224,138,0.04));
|
||||
border: 1px solid rgba(0,224,138,0.5);
|
||||
border-radius: var(--r-pill);
|
||||
color: var(--c-ok);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
display: flex; align-items: center; gap: var(--s-2);
|
||||
}
|
||||
|
||||
.main {
|
||||
grid-area: main;
|
||||
padding: var(--s-5);
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr 1fr;
|
||||
gap: var(--s-5);
|
||||
overflow: hidden;
|
||||
background: var(--g-deep-sea);
|
||||
}
|
||||
|
||||
/* ------- Horizon / attitude card ------- */
|
||||
.horizon-card {
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
padding: var(--s-5);
|
||||
box-shadow: var(--e-3);
|
||||
display: flex; flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.horizon-card::before {
|
||||
content: ""; position: absolute;
|
||||
top: 0; left: 0; right: 0; height: 3px;
|
||||
background: var(--g-cyan);
|
||||
}
|
||||
.card-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: var(--s-3);
|
||||
}
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-family: var(--f-display);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.card-header .meta {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
|
||||
.horizon-wrap {
|
||||
flex: 1;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: var(--s-4) 0;
|
||||
}
|
||||
.horizon-svg {
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
aspect-ratio: 1;
|
||||
filter: drop-shadow(0 12px 32px rgba(0,217,255,0.18));
|
||||
}
|
||||
|
||||
.att-readout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--s-4);
|
||||
margin-top: var(--s-3);
|
||||
}
|
||||
.att-box {
|
||||
background: var(--c-abyss);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-3);
|
||||
padding: var(--s-3);
|
||||
text-align: center;
|
||||
}
|
||||
.att-box .lbl {
|
||||
font-size: 10px; letter-spacing: 2px;
|
||||
color: var(--c-fog); text-transform: uppercase; font-weight: 700;
|
||||
}
|
||||
.att-box .val {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 42px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
letter-spacing: -1px;
|
||||
line-height: 1;
|
||||
margin-top: var(--s-2);
|
||||
}
|
||||
.att-box .sub { color: var(--c-fog); font-size: 11px; margin-top: 4px; font-family: var(--f-mono); }
|
||||
.att-box.warn { border-color: rgba(255,176,32,0.5); background: rgba(255,176,32,0.05); }
|
||||
.att-box.warn .val { color: var(--c-warn); }
|
||||
.att-box.warn .lbl { color: var(--c-warn); }
|
||||
|
||||
.levels {
|
||||
margin-top: var(--s-4);
|
||||
padding-top: var(--s-4);
|
||||
border-top: 1px solid var(--c-steel);
|
||||
display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3);
|
||||
}
|
||||
.level {
|
||||
padding: var(--s-3);
|
||||
border-radius: var(--r-2);
|
||||
text-align: center;
|
||||
font-family: var(--f-mono);
|
||||
}
|
||||
.level .num { font-size: 22px; font-weight: 700; line-height: 1; }
|
||||
.level .name { font-size: 10px; letter-spacing: 1.5px; color: var(--c-fog); text-transform: uppercase; margin-top: 4px; }
|
||||
.level .thr { font-size: 10px; color: var(--c-sand); margin-top: 4px; }
|
||||
.level.l1 { background: rgba(255,176,32,0.08); border: 1px solid rgba(255,176,32,0.3); }
|
||||
.level.l1 .num { color: var(--c-warn); }
|
||||
.level.l2 { background: rgba(255,128,48,0.08); border: 1px solid rgba(255,128,48,0.3); }
|
||||
.level.l2 .num { color: var(--c-high); }
|
||||
.level.l3 { background: rgba(255,59,71,0.10); border: 1px solid rgba(255,59,71,0.4); }
|
||||
.level.l3 .num { color: var(--c-emergency); }
|
||||
.level.active::after { content: " ◀"; color: var(--c-foam); }
|
||||
|
||||
/* ------- Trim sliders ------- */
|
||||
.trim-card {
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
padding: var(--s-5);
|
||||
box-shadow: var(--e-3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.trim-card::before {
|
||||
content: ""; position: absolute;
|
||||
top: 0; left: 0; right: 0; height: 3px;
|
||||
background: var(--g-cyan);
|
||||
}
|
||||
|
||||
.sliders {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--s-3);
|
||||
margin-top: var(--s-3);
|
||||
}
|
||||
.slider-col {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: var(--s-2);
|
||||
}
|
||||
.slider-col .name {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
color: var(--c-cyan);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.slider-track {
|
||||
width: 32px;
|
||||
height: 320px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(255,59,71,0.08) 0%,
|
||||
rgba(255,176,32,0.08) 20%,
|
||||
rgba(0,224,138,0.08) 40%,
|
||||
rgba(0,224,138,0.08) 60%,
|
||||
rgba(255,176,32,0.08) 80%,
|
||||
rgba(255,59,71,0.08) 100%
|
||||
);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-pill);
|
||||
position: relative;
|
||||
box-shadow: inset 0 2px 8px rgba(0,0,0,0.4);
|
||||
}
|
||||
.slider-track::before {
|
||||
content: "0"; position: absolute;
|
||||
right: -22px; top: 50%; transform: translateY(-50%);
|
||||
font-family: var(--f-mono); font-size: 9px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
.slider-track::after {
|
||||
content: ""; position: absolute;
|
||||
left: -4px; right: -4px; top: 50%;
|
||||
height: 1px; background: var(--c-cyan); opacity: 0.4;
|
||||
}
|
||||
.slider-handle {
|
||||
position: absolute;
|
||||
left: 50%; transform: translateX(-50%);
|
||||
width: 48px; height: 22px;
|
||||
background: var(--g-cyan);
|
||||
border-radius: var(--r-2);
|
||||
box-shadow: var(--glow-cyan), 0 4px 8px rgba(0,0,0,0.4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
color: #04111F;
|
||||
font-weight: 700;
|
||||
cursor: grab;
|
||||
}
|
||||
.slider-col .pct {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 16px;
|
||||
color: var(--c-foam);
|
||||
font-weight: 600;
|
||||
}
|
||||
.slider-col .range {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 10px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
.slider-col .buttons {
|
||||
display: flex; gap: 4px;
|
||||
}
|
||||
.slider-col .buttons button {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: var(--r-2);
|
||||
background: var(--c-steel);
|
||||
border: 1px solid var(--c-iron);
|
||||
color: var(--c-sand);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
.slider-col .buttons button:hover { background: var(--c-iron); }
|
||||
|
||||
.reset-card {
|
||||
background: var(--c-midnight);
|
||||
border: 1px solid var(--c-steel);
|
||||
border-radius: var(--r-4);
|
||||
padding: var(--s-5);
|
||||
box-shadow: var(--e-3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.reset-card::before {
|
||||
content: ""; position: absolute;
|
||||
top: 0; left: 0; right: 0; height: 3px;
|
||||
background: var(--g-emergency);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
flex: 1;
|
||||
margin: var(--s-4) 0;
|
||||
background: var(--g-emergency);
|
||||
border: none;
|
||||
border-radius: var(--r-4);
|
||||
padding: var(--s-7);
|
||||
color: var(--c-foam);
|
||||
font-family: var(--f-display);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--glow-emergency);
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: var(--s-3);
|
||||
transition: all 180ms var(--ease-standard);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.reset-btn::after {
|
||||
content: "";
|
||||
position: absolute; inset: 0;
|
||||
background: radial-gradient(circle at center, rgba(255,255,255,0.18), transparent 60%);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
.reset-btn:hover::after { opacity: 1; }
|
||||
.reset-btn:hover { transform: scale(1.02); }
|
||||
.reset-btn:active { transform: scale(0.99); }
|
||||
.reset-btn .ic-big {
|
||||
width: 56px; height: 56px;
|
||||
stroke: currentColor; fill: none; stroke-width: 2.5;
|
||||
}
|
||||
.reset-btn .sub {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 1px;
|
||||
text-transform: none;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.owner-toggle {
|
||||
padding: var(--s-3) var(--s-4);
|
||||
background: var(--c-abyss);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-3);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: var(--s-3);
|
||||
margin-top: var(--s-3);
|
||||
}
|
||||
.owner-toggle .label {
|
||||
flex: 1;
|
||||
}
|
||||
.owner-toggle .label .ttl {
|
||||
font-size: 13px;
|
||||
color: var(--c-foam);
|
||||
font-weight: 600;
|
||||
}
|
||||
.owner-toggle .label .desc {
|
||||
font-size: 11px;
|
||||
color: var(--c-fog);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.toggle {
|
||||
width: 52px; height: 28px;
|
||||
border-radius: var(--r-pill);
|
||||
background: var(--c-steel);
|
||||
border: 1px solid var(--c-iron);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle::after {
|
||||
content: ""; position: absolute;
|
||||
left: 3px; top: 3px;
|
||||
width: 20px; height: 20px;
|
||||
background: var(--c-fog);
|
||||
border-radius: 50%;
|
||||
transition: all 200ms var(--ease-standard);
|
||||
}
|
||||
.toggle.on { background: var(--g-cyan); border-color: transparent; }
|
||||
.toggle.on::after { left: 27px; background: #04111F; }
|
||||
|
||||
.envelope-card {
|
||||
margin-top: var(--s-3);
|
||||
padding: var(--s-4);
|
||||
background: var(--c-abyss);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-3);
|
||||
}
|
||||
.envelope-card .ttl {
|
||||
font-size: 11px; letter-spacing: 1.5px; color: var(--c-fog);
|
||||
text-transform: uppercase; font-weight: 700;
|
||||
margin-bottom: var(--s-3);
|
||||
}
|
||||
.envelope-bar {
|
||||
height: 12px;
|
||||
background: linear-gradient(90deg,
|
||||
#FF3B47 0%, #FF3B47 12%,
|
||||
#FF8030 12%, #FF8030 25%,
|
||||
#FFB020 25%, #FFB020 38%,
|
||||
#00E08A 38%, #00E08A 62%,
|
||||
#FFB020 62%, #FFB020 75%,
|
||||
#FF8030 75%, #FF8030 88%,
|
||||
#FF3B47 88%, #FF3B47 100%
|
||||
);
|
||||
border-radius: var(--r-pill);
|
||||
position: relative;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
|
||||
}
|
||||
.envelope-pointer {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 47%;
|
||||
transform: translateX(-50%);
|
||||
width: 0; height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 10px solid var(--c-foam);
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6));
|
||||
}
|
||||
.envelope-scale {
|
||||
display: flex; justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
font-family: var(--f-mono);
|
||||
font-size: 9px;
|
||||
color: var(--c-fog);
|
||||
}
|
||||
.envelope-msg {
|
||||
margin-top: var(--s-3);
|
||||
font-size: 11px;
|
||||
color: var(--c-ok);
|
||||
font-family: var(--f-mono);
|
||||
}
|
||||
|
||||
.predict-card {
|
||||
margin-top: var(--s-3);
|
||||
padding: var(--s-3);
|
||||
background: rgba(0,217,255,0.04);
|
||||
border: 1px solid rgba(0,217,255,0.25);
|
||||
border-radius: var(--r-3);
|
||||
font-size: 12px;
|
||||
}
|
||||
.predict-card .ttl { color: var(--c-cyan); font-weight: 700; letter-spacing: 1px; font-size: 10px; text-transform: uppercase; }
|
||||
.predict-card .body { color: var(--c-sand); margin-top: 6px; line-height: 1.5; }
|
||||
.predict-card .body strong { color: var(--c-foam); }
|
||||
|
||||
.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 .pulse {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
background: var(--c-cyan);
|
||||
box-shadow: 0 0 12px rgba(0,217,255,0.7);
|
||||
animation: heart 1.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes heart {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.4); opacity: 0.5; }
|
||||
}
|
||||
.tk-spacer { flex: 1; }
|
||||
.ic { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root rt">
|
||||
|
||||
<header class="topbar">
|
||||
<div class="breadcrumb">
|
||||
<img src="../brand/logo-mark.svg" alt="">
|
||||
<strong>M/Y Aurora</strong>
|
||||
<span class="sep">/</span>
|
||||
<span class="system">Trim & Maniobra</span>
|
||||
</div>
|
||||
<div class="top-spacer"></div>
|
||||
<span class="mode-banner">
|
||||
<span class="dot ok"></span> ENVELOPE ACTIVO · ±10°
|
||||
</span>
|
||||
<span class="chip">Roll Safety <strong style="color:var(--c-ok); margin-left:4px;">L1 monitor</strong></span>
|
||||
<button class="btn btn-secondary">Calibración</button>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
|
||||
<!-- ===== HORIZON CARD ===== -->
|
||||
<section class="horizon-card">
|
||||
<div class="card-header">
|
||||
<h3>Actitud del buque</h3>
|
||||
<span class="meta">NMEA 2000 · PGN 127257 · 10 Hz · ←AR-ECDIS</span>
|
||||
</div>
|
||||
|
||||
<div class="horizon-wrap">
|
||||
<svg class="horizon-svg" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#5BC0EB"/>
|
||||
<stop offset="40%" stop-color="#3A6BA8"/>
|
||||
<stop offset="100%" stop-color="#1B3E6E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="seaGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#0A1A2E"/>
|
||||
<stop offset="100%" stop-color="#04111F"/>
|
||||
</linearGradient>
|
||||
<clipPath id="horizonClip">
|
||||
<circle cx="200" cy="200" r="170"/>
|
||||
</clipPath>
|
||||
<linearGradient id="ringGrad" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#00D9FF"/>
|
||||
<stop offset="50%" stop-color="#5BC0EB"/>
|
||||
<stop offset="100%" stop-color="#1B7FB5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Outer ring -->
|
||||
<circle cx="200" cy="200" r="185" fill="none" stroke="url(#ringGrad)" stroke-width="2.5" opacity="0.7"/>
|
||||
|
||||
<!-- Roll scale arcs (color bands) -->
|
||||
<g fill="none" stroke-width="6" stroke-linecap="butt">
|
||||
<!-- safe -8 to +8 (green) -->
|
||||
<path d="M 130 35 A 170 170 0 0 1 270 35" stroke="#00E08A" opacity="0.7"/>
|
||||
<!-- warn 8-12 left -->
|
||||
<path d="M 75 70 A 170 170 0 0 1 130 35" stroke="#FFB020" opacity="0.65"/>
|
||||
<!-- warn 8-12 right -->
|
||||
<path d="M 270 35 A 170 170 0 0 1 325 70" stroke="#FFB020" opacity="0.65"/>
|
||||
<!-- high 12-18 left -->
|
||||
<path d="M 45 110 A 170 170 0 0 1 75 70" stroke="#FF8030" opacity="0.65"/>
|
||||
<!-- high 12-18 right -->
|
||||
<path d="M 325 70 A 170 170 0 0 1 355 110" stroke="#FF8030" opacity="0.65"/>
|
||||
<!-- emergency >18 left -->
|
||||
<path d="M 30 200 A 170 170 0 0 1 45 110" stroke="#FF3B47" opacity="0.75"/>
|
||||
<!-- emergency >18 right -->
|
||||
<path d="M 355 110 A 170 170 0 0 1 370 200" stroke="#FF3B47" opacity="0.75"/>
|
||||
</g>
|
||||
|
||||
<!-- Scale ticks every 5° -->
|
||||
<g stroke="#E6EAF0" stroke-width="1.5" stroke-linecap="round">
|
||||
<g transform="translate(200,200)">
|
||||
<g><line x1="0" y1="-180" x2="0" y2="-168"/><text font-family="JetBrains Mono" font-size="10" fill="#E6EAF0" x="0" y="-150" text-anchor="middle">0</text></g>
|
||||
<g transform="rotate(15)"><line x1="0" y1="-180" x2="0" y2="-172"/></g>
|
||||
<g transform="rotate(30)"><line x1="0" y1="-180" x2="0" y2="-172"/></g>
|
||||
<g transform="rotate(45)"><line x1="0" y1="-180" x2="0" y2="-172"/></g>
|
||||
<g transform="rotate(-15)"><line x1="0" y1="-180" x2="0" y2="-172"/></g>
|
||||
<g transform="rotate(-30)"><line x1="0" y1="-180" x2="0" y2="-172"/></g>
|
||||
<g transform="rotate(-45)"><line x1="0" y1="-180" x2="0" y2="-172"/></g>
|
||||
<g transform="rotate(-10)"><line x1="0" y1="-180" x2="0" y2="-168"/><text font-family="JetBrains Mono" font-size="10" fill="#E6EAF0" transform="rotate(10)" x="-32" y="-160">10</text></g>
|
||||
<g transform="rotate(10)"><line x1="0" y1="-180" x2="0" y2="-168"/><text font-family="JetBrains Mono" font-size="10" fill="#E6EAF0" transform="rotate(-10)" x="32" y="-160">10</text></g>
|
||||
<g transform="rotate(-20)"><line x1="0" y1="-180" x2="0" y2="-168"/></g>
|
||||
<g transform="rotate(20)"><line x1="0" y1="-180" x2="0" y2="-168"/></g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Horizon disc (clipped) -->
|
||||
<g clip-path="url(#horizonClip)">
|
||||
<g transform="rotate(-4 200 200)">
|
||||
<!-- Pitch shift -->
|
||||
<g transform="translate(0, 10)">
|
||||
<rect x="0" y="-200" width="400" height="400" fill="url(#skyGrad)"/>
|
||||
<rect x="0" y="200" width="400" height="400" fill="url(#seaGrad)"/>
|
||||
<line x1="0" y1="200" x2="400" y2="200" stroke="#00D9FF" stroke-width="2.5" opacity="0.9"/>
|
||||
<line x1="0" y1="200" x2="400" y2="200" stroke="#00D9FF" stroke-width="6" opacity="0.3"/>
|
||||
<!-- Pitch ladder -->
|
||||
<g font-family="JetBrains Mono" font-size="11" fill="#E6EAF0" font-weight="600">
|
||||
<line x1="150" y1="180" x2="250" y2="180" stroke="#E6EAF0" stroke-width="1.5"/>
|
||||
<text x="135" y="184" text-anchor="end">10</text>
|
||||
<text x="265" y="184">10</text>
|
||||
<line x1="170" y1="160" x2="230" y2="160" stroke="#E6EAF0" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="150" y1="140" x2="250" y2="140" stroke="#E6EAF0" stroke-width="1.5"/>
|
||||
<text x="135" y="144" text-anchor="end">20</text>
|
||||
<text x="265" y="144">20</text>
|
||||
<line x1="150" y1="220" x2="250" y2="220" stroke="#E6EAF0" stroke-width="1.5"/>
|
||||
<text x="135" y="224" text-anchor="end">10</text>
|
||||
<text x="265" y="224">10</text>
|
||||
<line x1="170" y1="240" x2="230" y2="240" stroke="#E6EAF0" stroke-width="1" opacity="0.7"/>
|
||||
<line x1="150" y1="260" x2="250" y2="260" stroke="#E6EAF0" stroke-width="1.5"/>
|
||||
<text x="135" y="264" text-anchor="end">20</text>
|
||||
<text x="265" y="264">20</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Outer ring overlay -->
|
||||
<circle cx="200" cy="200" r="170" fill="none" stroke="#1A2B42" stroke-width="2"/>
|
||||
|
||||
<!-- Aircraft / boat symbol (fixed) -->
|
||||
<g stroke="#00D9FF" stroke-width="3" fill="none" stroke-linecap="round">
|
||||
<line x1="130" y1="200" x2="170" y2="200"/>
|
||||
<line x1="230" y1="200" x2="270" y2="200"/>
|
||||
<line x1="170" y1="200" x2="170" y2="210"/>
|
||||
<line x1="230" y1="200" x2="230" y2="210"/>
|
||||
</g>
|
||||
<circle cx="200" cy="200" r="4" fill="#00D9FF"/>
|
||||
|
||||
<!-- Roll indicator triangle (current heading on outer ring) -->
|
||||
<g transform="translate(200,200) rotate(-4)">
|
||||
<path d="M 0 -185 L -6 -195 L 6 -195 Z" fill="#00D9FF" stroke="#04111F" stroke-width="1"/>
|
||||
</g>
|
||||
|
||||
<!-- Reference triangle (always at top, 0°) -->
|
||||
<path d="M 200 35 L 192 22 L 208 22 Z" fill="#FFB020" opacity="0.85"/>
|
||||
|
||||
<!-- Inner readout -->
|
||||
<g font-family="JetBrains Mono" fill="#E6EAF0">
|
||||
<text x="200" y="290" text-anchor="middle" font-size="13" font-weight="700" fill="#00D9FF">SAFE · L1</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="att-readout">
|
||||
<div class="att-box">
|
||||
<div class="lbl">Roll</div>
|
||||
<div class="val">-4.1°</div>
|
||||
<div class="sub">babor · estable</div>
|
||||
</div>
|
||||
<div class="att-box">
|
||||
<div class="lbl">Pitch</div>
|
||||
<div class="val">+1.8°</div>
|
||||
<div class="sub">popa abajo</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="levels">
|
||||
<div class="level l1 active">
|
||||
<div class="num">L1</div>
|
||||
<div class="name">Warning</div>
|
||||
<div class="thr">> 8° / 10 s</div>
|
||||
</div>
|
||||
<div class="level l2">
|
||||
<div class="num">L2</div>
|
||||
<div class="name">Auto-offer</div>
|
||||
<div class="thr">> 12° / 5 s</div>
|
||||
</div>
|
||||
<div class="level l3">
|
||||
<div class="num">L3</div>
|
||||
<div class="name">Forced reset</div>
|
||||
<div class="thr">> 18°</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== TRIM SLIDERS ===== -->
|
||||
<section class="trim-card">
|
||||
<div class="card-header">
|
||||
<h3>Control de trim</h3>
|
||||
<span class="meta">4 actuadores · authority MÁQUINAS</span>
|
||||
</div>
|
||||
|
||||
<div class="sliders">
|
||||
<div class="slider-col">
|
||||
<span class="name">ME_PORT</span>
|
||||
<div class="slider-track">
|
||||
<div class="slider-handle" style="top: 38%;">+12</div>
|
||||
</div>
|
||||
<span class="pct">+12%</span>
|
||||
<span class="range">−100 / +100</span>
|
||||
<div class="buttons">
|
||||
<button>−</button>
|
||||
<button>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slider-col">
|
||||
<span class="name">ME_STBD</span>
|
||||
<div class="slider-track">
|
||||
<div class="slider-handle" style="top: 42%;">+8</div>
|
||||
</div>
|
||||
<span class="pct">+8%</span>
|
||||
<span class="range">−100 / +100</span>
|
||||
<div class="buttons">
|
||||
<button>−</button>
|
||||
<button>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slider-col">
|
||||
<span class="name">TAB_PORT</span>
|
||||
<div class="slider-track">
|
||||
<div class="slider-handle" style="top: 65%;">−30</div>
|
||||
</div>
|
||||
<span class="pct">−30%</span>
|
||||
<span class="range">−100 / +100</span>
|
||||
<div class="buttons">
|
||||
<button>−</button>
|
||||
<button>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slider-col">
|
||||
<span class="name">TAB_STBD</span>
|
||||
<div class="slider-track">
|
||||
<div class="slider-handle" style="top: 50%;">0</div>
|
||||
</div>
|
||||
<span class="pct">0%</span>
|
||||
<span class="range">−100 / +100</span>
|
||||
<div class="buttons">
|
||||
<button>−</button>
|
||||
<button>+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="predict-card">
|
||||
<div class="ttl">⚲ Predicción safety envelope</div>
|
||||
<div class="body">
|
||||
Mover <strong>TAB_PORT a −60%</strong> llevaría a roll estimado
|
||||
<strong>−9.2°</strong>. Está dentro del envelope (±10°). Acción
|
||||
permitida.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== RESET + OWNER MANUAL ===== -->
|
||||
<section class="reset-card">
|
||||
<div class="card-header">
|
||||
<h3 style="color: var(--c-emergency);">Reset emergencia</h3>
|
||||
<span class="meta">cableado físico + virtual</span>
|
||||
</div>
|
||||
|
||||
<button class="reset-btn pulse-emergency">
|
||||
<svg class="ic-big" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<path d="M12 7v6M12 16h.01"/>
|
||||
</svg>
|
||||
Reset trims
|
||||
<span class="sub">Lleva todos los actuadores a posición neutra</span>
|
||||
</button>
|
||||
|
||||
<div class="owner-toggle">
|
||||
<div class="label">
|
||||
<div class="ttl">Modo manual del owner</div>
|
||||
<div class="desc">Desactiva L1/L2 · mantiene L3 + envelope ±10°</div>
|
||||
</div>
|
||||
<div class="toggle"></div>
|
||||
</div>
|
||||
|
||||
<div class="envelope-card">
|
||||
<div class="ttl">Safety envelope (predicción)</div>
|
||||
<div class="envelope-bar">
|
||||
<div class="envelope-pointer" style="left: 46%;"></div>
|
||||
</div>
|
||||
<div class="envelope-scale">
|
||||
<span>−18°</span>
|
||||
<span>−12°</span>
|
||||
<span>−8°</span>
|
||||
<span>0°</span>
|
||||
<span>+8°</span>
|
||||
<span>+12°</span>
|
||||
<span>+18°</span>
|
||||
</div>
|
||||
<div class="envelope-msg">✓ Roll actual dentro del rango seguro (verde)</div>
|
||||
</div>
|
||||
|
||||
<div class="envelope-card" style="margin-top: var(--s-3);">
|
||||
<div class="ttl">Eventos de seguridad recientes</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: var(--c-sand); font-family: var(--f-mono);">
|
||||
<div style="padding: 4px 0;">02:14 · L1 warning · roll 9.2° / 6 s · auto cleared</div>
|
||||
<div style="padding: 4px 0;">01:33 · L1 warning · roll 8.4° / 11 s · ACK Carlos</div>
|
||||
<div style="padding: 4px 0; color: var(--c-fog);">23:51 · L3 reset forzado · roll 19.1° · auto-reset · ✓</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="ticker">
|
||||
<span class="pulse"></span>
|
||||
<span>Roll <strong style="color:var(--c-sand)">-4.1°</strong></span>
|
||||
<span style="color: var(--c-iron)">|</span>
|
||||
<span>Pitch <strong style="color:var(--c-sand)">+1.8°</strong></span>
|
||||
<span style="color: var(--c-iron)">|</span>
|
||||
<span>Rate <strong style="color:var(--c-sand)">0.3 deg/s</strong></span>
|
||||
<span style="color: var(--c-iron)">|</span>
|
||||
<span>Predicción envelope <strong style="color:var(--c-ok)">SAFE</strong></span>
|
||||
<span class="tk-spacer"></span>
|
||||
<span>Calibración modelo trim: <strong style="color:var(--c-sand)">v1 (mar pruebas 2026-03)</strong></span>
|
||||
<span style="color: var(--c-iron)">|</span>
|
||||
<span>v0.1.0.dev0</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,187 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor · Splash</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body {
|
||||
background: var(--g-horizon);
|
||||
overflow: hidden;
|
||||
}
|
||||
.splash {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: var(--s-7);
|
||||
}
|
||||
.compass-bg {
|
||||
position: absolute;
|
||||
width: 720px;
|
||||
height: 720px;
|
||||
border-radius: 50%;
|
||||
background: var(--g-cyan-glow);
|
||||
opacity: 0.45;
|
||||
filter: blur(20px);
|
||||
animation: drift 8s ease-in-out infinite;
|
||||
}
|
||||
@keyframes drift {
|
||||
0%, 100% { transform: scale(1) rotate(0deg); }
|
||||
50% { transform: scale(1.1) rotate(180deg); }
|
||||
}
|
||||
.waves {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 220px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.wave {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
width: 200%;
|
||||
height: 220px;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.wave svg { width: 100%; height: 100%; }
|
||||
.wave-1 { animation: waveMove 18s linear infinite; }
|
||||
.wave-2 { animation: waveMove 26s linear infinite reverse; opacity: 0.35; }
|
||||
@keyframes waveMove {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
.logo-wrap {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--s-5);
|
||||
}
|
||||
.logo-wrap img { width: 220px; height: 220px; filter: drop-shadow(0 12px 40px rgba(0,217,255,0.4)); }
|
||||
.product-name {
|
||||
font-family: var(--f-display);
|
||||
font-size: 96px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -3px;
|
||||
line-height: 1;
|
||||
color: var(--c-foam);
|
||||
margin: 0;
|
||||
text-shadow: 0 4px 32px rgba(0,217,255,0.25);
|
||||
}
|
||||
.product-name .accent { color: var(--c-cyan); font-weight: 300; }
|
||||
.product-name .light { font-weight: 400; }
|
||||
.tagline {
|
||||
font-family: var(--f-ui);
|
||||
font-size: 18px;
|
||||
letter-spacing: 4px;
|
||||
text-transform: uppercase;
|
||||
color: var(--c-horizon);
|
||||
margin-top: -8px;
|
||||
margin-bottom: var(--s-6);
|
||||
}
|
||||
.build-bar {
|
||||
display: flex;
|
||||
gap: var(--s-5);
|
||||
align-items: center;
|
||||
padding: var(--s-3) var(--s-5);
|
||||
background: rgba(4,17,31,0.55);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: var(--r-pill);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
font-family: var(--f-mono);
|
||||
font-size: 12px;
|
||||
color: var(--c-sand);
|
||||
}
|
||||
.build-bar .sep { color: var(--c-iron); }
|
||||
.loading-track {
|
||||
width: 360px;
|
||||
height: 4px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
border-radius: var(--r-pill);
|
||||
margin-top: var(--s-6);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.loading-bar {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 40%;
|
||||
background: var(--g-cyan);
|
||||
border-radius: var(--r-pill);
|
||||
animation: scan 2.4s ease-in-out infinite;
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
@keyframes scan {
|
||||
0% { transform: translateX(-100%); }
|
||||
50% { transform: translateX(150%); }
|
||||
100% { transform: translateX(150%); }
|
||||
}
|
||||
.footer-marks {
|
||||
position: absolute;
|
||||
bottom: var(--s-5);
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--s-6);
|
||||
color: var(--c-fog);
|
||||
font-size: 11px;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
z-index: 2;
|
||||
}
|
||||
.footer-marks strong { color: var(--c-sand); font-weight: 600; letter-spacing: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="splash">
|
||||
<div class="compass-bg"></div>
|
||||
|
||||
<div class="logo-wrap">
|
||||
<img src="../brand/logo-mark.svg" alt="">
|
||||
<h1 class="product-name">VMS<span class="accent"> · </span><span class="light">Sailor</span></h1>
|
||||
<p class="tagline">Vessel Management System</p>
|
||||
|
||||
<div class="build-bar">
|
||||
<span>v0.1.0 sprint0</span>
|
||||
<span class="sep">|</span>
|
||||
<span>IEC 60092-504</span>
|
||||
<span class="sep">|</span>
|
||||
<span>IACS UR E22</span>
|
||||
<span class="sep">|</span>
|
||||
<span>NMEA 2000</span>
|
||||
</div>
|
||||
|
||||
<div class="loading-track"><div class="loading-bar"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="waves">
|
||||
<div class="wave wave-2">
|
||||
<svg viewBox="0 0 1440 220" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 120 C 240 80 480 160 720 120 C 960 80 1200 160 1440 120 L 1440 220 L 0 220 Z" fill="#1B3E6E"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="wave wave-1">
|
||||
<svg viewBox="0 0 1440 220" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 0 150 C 240 110 480 190 720 150 C 960 110 1200 190 1440 150 L 1440 220 L 0 220 Z" fill="#04111F"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-marks">
|
||||
<span><strong>Álvaro</strong> · Propietario IP</span>
|
||||
<span>HWID: <strong>04F3-2BA1-19EE</strong></span>
|
||||
<span>Licencia: <strong>Activa</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,442 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VMS-Sailor Studio · Mockup Sprint 1</title>
|
||||
<link rel="icon" type="image/svg+xml" href="../brand/favicon.svg">
|
||||
<link rel="stylesheet" href="_tokens.css">
|
||||
<style>
|
||||
body { overflow: hidden; }
|
||||
.studio {
|
||||
display: grid;
|
||||
grid-template-rows: 56px 1fr 32px;
|
||||
grid-template-columns: 280px 1fr 340px;
|
||||
grid-template-areas:
|
||||
"topbar topbar topbar"
|
||||
"sidebar canvas inspector"
|
||||
"statusbar statusbar statusbar";
|
||||
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);
|
||||
}
|
||||
.topbar img { height: 32px; }
|
||||
.breadcrumb {
|
||||
display: flex; align-items: center; gap: var(--s-3);
|
||||
font-size: 13px; color: var(--c-fog);
|
||||
}
|
||||
.breadcrumb .sep { opacity: 0.5; }
|
||||
.breadcrumb strong { color: var(--c-sand); font-weight: 600; }
|
||||
.breadcrumb .active { color: var(--c-cyan); font-weight: 600; }
|
||||
.topbar-spacer { flex: 1; }
|
||||
.topbar-actions { display: flex; gap: var(--s-2); align-items: center; }
|
||||
|
||||
/* 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-3) var(--s-4); }
|
||||
.sb-title {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-size: 11px; font-weight: 700; letter-spacing: 2px;
|
||||
color: var(--c-fog); text-transform: uppercase;
|
||||
margin-bottom: var(--s-2);
|
||||
}
|
||||
.sb-tree { list-style: none; padding: 0; margin: 0; }
|
||||
.sb-tree li {
|
||||
padding: 8px 12px; border-radius: var(--r-2);
|
||||
display: flex; align-items: center; gap: var(--s-2);
|
||||
color: var(--c-sand); font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 120ms var(--ease-standard);
|
||||
}
|
||||
.sb-tree li:hover { background: var(--c-steel); }
|
||||
.sb-tree li.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);
|
||||
}
|
||||
.sb-tree .count {
|
||||
margin-left: auto;
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
color: var(--c-fog);
|
||||
background: var(--c-steel);
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--r-pill);
|
||||
}
|
||||
|
||||
/* Canvas */
|
||||
.canvas {
|
||||
grid-area: canvas;
|
||||
position: relative;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgba(0,217,255,0.06) 0%, transparent 70%),
|
||||
var(--g-deep-sea);
|
||||
overflow: hidden;
|
||||
}
|
||||
.canvas-grid {
|
||||
position: absolute; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(91,192,235,0.06) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(91,192,235,0.06) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
}
|
||||
.canvas-toolbar {
|
||||
position: absolute;
|
||||
top: var(--s-4); left: var(--s-4);
|
||||
display: flex; gap: var(--s-2);
|
||||
padding: var(--s-2);
|
||||
background: var(--g-glass);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: var(--r-3);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
.vessel-stage {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.vessel-svg {
|
||||
width: min(90%, 900px);
|
||||
filter: drop-shadow(0 12px 32px rgba(0,217,255,0.18));
|
||||
}
|
||||
.coord-label {
|
||||
position: absolute;
|
||||
font-family: var(--f-mono);
|
||||
font-size: 10px;
|
||||
color: var(--c-fog);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.axis-x { bottom: var(--s-4); left: 50%; }
|
||||
.axis-z { left: var(--s-4); top: 50%; transform: rotate(-90deg); transform-origin: left center; }
|
||||
|
||||
/* Equipment dots on canvas */
|
||||
.eq-dot {
|
||||
position: absolute;
|
||||
width: 14px; height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--c-cyan);
|
||||
box-shadow: 0 0 0 4px rgba(0,217,255,0.18), var(--glow-cyan);
|
||||
cursor: pointer;
|
||||
}
|
||||
.eq-tooltip {
|
||||
position: absolute;
|
||||
background: rgba(4,17,31,0.92);
|
||||
border: 1px solid var(--c-iron);
|
||||
border-radius: var(--r-2);
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.eq-tooltip strong { color: var(--c-cyan); display: block; margin-bottom: 2px; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; }
|
||||
.eq-tooltip span { color: var(--c-sand); font-family: var(--f-mono); }
|
||||
|
||||
/* Inspector */
|
||||
.inspector {
|
||||
grid-area: inspector;
|
||||
background: var(--c-midnight);
|
||||
border-left: 1px solid var(--c-steel);
|
||||
overflow-y: auto;
|
||||
padding: var(--s-5);
|
||||
}
|
||||
.insp-header {
|
||||
display: flex; align-items: center; gap: var(--s-3);
|
||||
margin-bottom: var(--s-5);
|
||||
}
|
||||
.insp-header h2 {
|
||||
margin: 0;
|
||||
font-family: var(--f-display);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--c-foam);
|
||||
}
|
||||
.insp-row {
|
||||
display: grid;
|
||||
grid-template-columns: 110px 1fr;
|
||||
gap: var(--s-3);
|
||||
padding: var(--s-3) 0;
|
||||
border-bottom: 1px solid var(--c-steel);
|
||||
font-size: 13px;
|
||||
}
|
||||
.insp-row:last-child { border-bottom: none; }
|
||||
.insp-row .k { color: var(--c-fog); }
|
||||
.insp-row .v { color: var(--c-foam); font-family: var(--f-mono); }
|
||||
.insp-row .v.text { font-family: var(--f-ui); }
|
||||
|
||||
/* Statusbar */
|
||||
.statusbar {
|
||||
grid-area: statusbar;
|
||||
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);
|
||||
}
|
||||
.statusbar .sep { color: var(--c-iron); }
|
||||
.statusbar strong { color: var(--c-sand); }
|
||||
.sb-spacer { flex: 1; }
|
||||
|
||||
/* Icons */
|
||||
.ic { width: 16px; height: 16px; stroke-width: 2; stroke: currentColor; fill: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root studio">
|
||||
|
||||
<header class="topbar">
|
||||
<img src="../brand/logo.svg" alt="VMS-Sailor">
|
||||
<div class="breadcrumb">
|
||||
<span>Proyectos</span>
|
||||
<span class="sep">/</span>
|
||||
<strong>m_y_aurora_sunseeker_76</strong>
|
||||
<span class="sep">/</span>
|
||||
<span class="active">Topología</span>
|
||||
</div>
|
||||
<div class="topbar-spacer"></div>
|
||||
<div class="topbar-actions">
|
||||
<span class="chip"><span class="dot ok"></span> Sin cambios sin guardar</span>
|
||||
<button class="btn-icon" title="Simulador">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
</button>
|
||||
<button class="btn-icon" title="Validar">
|
||||
<svg class="ic" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="9"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary">Compilar .vmspack</button>
|
||||
<button class="btn btn-primary">Guardar</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="sb-section">
|
||||
<div class="sb-title">
|
||||
<span>Wizard</span>
|
||||
<span style="color: var(--c-ok); font-weight: 700;">8/8</span>
|
||||
</div>
|
||||
<ul class="sb-tree">
|
||||
<li><span class="dot ok"></span> 1. Tipo de buque</li>
|
||||
<li><span class="dot ok"></span> 2. Plantilla</li>
|
||||
<li><span class="dot ok"></span> 3. Dimensiones</li>
|
||||
<li><span class="dot ok"></span> 4. Sistemas</li>
|
||||
<li><span class="dot ok"></span> 5. Equipos sugeridos</li>
|
||||
<li><span class="dot ok"></span> 6. Refinamiento</li>
|
||||
<li class="active"><span class="dot cyan"></span> 7. Topología I/O</li>
|
||||
<li><span class="dot"></span> 8. Confirmación</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="sb-section">
|
||||
<div class="sb-title">
|
||||
<span>Sistemas habilitados</span>
|
||||
<span>14</span>
|
||||
</div>
|
||||
<ul class="sb-tree">
|
||||
<li>⚙ Máquina principal <span class="count">2</span></li>
|
||||
<li>⚡ Generadores <span class="count">1</span></li>
|
||||
<li>🛡 Trim & Maniobra <span class="count">4</span></li>
|
||||
<li>⛽ Combustible <span class="count">2</span></li>
|
||||
<li>💧 Refrigeración <span class="count">2</span></li>
|
||||
<li>🔥 Sentinas <span class="count">3</span></li>
|
||||
<li>🌬 HVAC <span class="count">1</span></li>
|
||||
<li>🔆 Iluminación <span class="count">5</span></li>
|
||||
<li><em style="color: var(--c-fog);">+ 6 más…</em></li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="canvas">
|
||||
<div class="canvas-grid"></div>
|
||||
|
||||
<div class="canvas-toolbar">
|
||||
<button class="btn-icon" title="Zoom in"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg></button>
|
||||
<button class="btn-icon" title="Zoom out"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="8" y1="11" x2="14" y2="11"/></svg></button>
|
||||
<button class="btn-icon" title="Centrar"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/></svg></button>
|
||||
<button class="btn-icon" title="Mostrar tarjetas"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="12" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="vessel-stage">
|
||||
<svg class="vessel-svg" viewBox="0 0 900 380" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="hull" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#E6EAF0"/>
|
||||
<stop offset="100%" stop-color="#5A6B7F"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="superstructure" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#F2F5F9"/>
|
||||
<stop offset="100%" stop-color="#94A3B8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="water" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="rgba(0,217,255,0.18)"/>
|
||||
<stop offset="100%" stop-color="rgba(0,217,255,0)"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Waterline reflection -->
|
||||
<rect x="0" y="260" width="900" height="120" fill="url(#water)"/>
|
||||
|
||||
<!-- Silhouette: Sunseeker 76 side view, simplified -->
|
||||
<!-- Hull -->
|
||||
<path d="M 80 240 Q 60 200 130 175 L 720 175 Q 800 175 820 215 L 815 255 Q 800 280 760 280 L 200 280 Q 110 280 80 240 Z"
|
||||
fill="url(#hull)" stroke="#04111F" stroke-width="1.5"/>
|
||||
<!-- Superstructure -->
|
||||
<path d="M 230 175 L 250 130 L 580 130 L 640 175 Z"
|
||||
fill="url(#superstructure)" stroke="#04111F" stroke-width="1.5"/>
|
||||
<!-- Flybridge -->
|
||||
<path d="M 360 130 L 380 100 L 520 100 L 540 130 Z"
|
||||
fill="url(#superstructure)" stroke="#04111F" stroke-width="1.2"/>
|
||||
<!-- Windows -->
|
||||
<rect x="280" y="142" width="280" height="20" rx="3" fill="#04111F" opacity="0.7"/>
|
||||
<!-- Mast -->
|
||||
<line x1="450" y1="100" x2="450" y2="60" stroke="#7C8B9F" stroke-width="2"/>
|
||||
<circle cx="450" cy="60" r="3" fill="#00D9FF"/>
|
||||
|
||||
<!-- Waterline -->
|
||||
<line x1="60" y1="260" x2="830" y2="260" stroke="#00D9FF" stroke-width="1" stroke-dasharray="6 6" opacity="0.6"/>
|
||||
|
||||
<!-- Bulkheads -->
|
||||
<line x1="320" y1="175" x2="320" y2="270" stroke="#00D9FF" stroke-width="1" stroke-dasharray="3 3" opacity="0.4"/>
|
||||
<line x1="540" y1="175" x2="540" y2="270" stroke="#00D9FF" stroke-width="1" stroke-dasharray="3 3" opacity="0.4"/>
|
||||
<line x1="690" y1="175" x2="690" y2="270" stroke="#00D9FF" stroke-width="1" stroke-dasharray="3 3" opacity="0.4"/>
|
||||
|
||||
<!-- Coord ticks -->
|
||||
<g font-family="JetBrains Mono, monospace" font-size="9" fill="#7C8B9F">
|
||||
<text x="780" y="298">x_pp 0</text>
|
||||
<text x="80" y="298">x_pp 23.5 m</text>
|
||||
</g>
|
||||
|
||||
<!-- Equipment dots overlaid via SVG -->
|
||||
<g>
|
||||
<circle cx="250" cy="240" r="7" fill="#00D9FF" stroke="#04111F" stroke-width="2"/>
|
||||
<circle cx="250" cy="240" r="13" fill="none" stroke="rgba(0,217,255,0.4)" stroke-width="2"/>
|
||||
<text x="248" y="312" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">ME_PORT</text>
|
||||
|
||||
<circle cx="320" cy="240" r="7" fill="#00D9FF" stroke="#04111F" stroke-width="2"/>
|
||||
<circle cx="320" cy="240" r="13" fill="none" stroke="rgba(0,217,255,0.4)" stroke-width="2"/>
|
||||
<text x="318" y="328" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">ME_STBD</text>
|
||||
|
||||
<circle cx="400" cy="240" r="7" fill="#FFB020" stroke="#04111F" stroke-width="2"/>
|
||||
<text x="400" y="312" font-family="Inter" font-size="10" fill="#FFB020" text-anchor="middle">GEN_1</text>
|
||||
|
||||
<circle cx="610" cy="225" r="6" fill="#5BC0EB" stroke="#04111F" stroke-width="2"/>
|
||||
<text x="610" y="312" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">TANK_FUEL_1</text>
|
||||
|
||||
<circle cx="660" cy="245" r="6" fill="#5BC0EB" stroke="#04111F" stroke-width="2"/>
|
||||
<text x="660" y="328" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">TANK_FUEL_2</text>
|
||||
|
||||
<circle cx="180" cy="245" r="5" fill="#00E08A" stroke="#04111F" stroke-width="2"/>
|
||||
<text x="180" y="312" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">BILGE_AFT</text>
|
||||
|
||||
<circle cx="500" cy="245" r="5" fill="#00E08A" stroke="#04111F" stroke-width="2"/>
|
||||
<text x="500" y="312" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">BILGE_MID</text>
|
||||
|
||||
<circle cx="730" cy="220" r="5" fill="#00E08A" stroke="#04111F" stroke-width="2"/>
|
||||
<text x="730" y="312" font-family="Inter" font-size="10" fill="#E6EAF0" text-anchor="middle">BILGE_FWD</text>
|
||||
</g>
|
||||
|
||||
<!-- Card highlight -->
|
||||
<g>
|
||||
<rect x="240" y="220" width="100" height="40" rx="6" fill="none" stroke="#00D9FF" stroke-width="2" stroke-dasharray="4 4" opacity="0.8"/>
|
||||
<text x="290" y="216" font-family="Inter" font-size="10" font-weight="700" fill="#00D9FF" text-anchor="middle">CARD_001 · slot 1 · addr 1</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="inspector">
|
||||
<div class="insp-header">
|
||||
<div style="width:36px; height:36px; border-radius: var(--r-2); background: var(--g-cyan); display:flex; align-items:center; justify-content:center;">
|
||||
<svg class="ic" viewBox="0 0 24 24" style="color:#04111F"><rect x="3" y="6" width="18" height="12" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2>CARD_001</h2>
|
||||
<span class="caption">AR-NMEA-IO-v1.0 · slot 1</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="insp-row"><span class="k">Serial</span><span class="v">ARC-2026-00153</span></div>
|
||||
<div class="insp-row"><span class="k">Bus</span><span class="v">bus_main</span></div>
|
||||
<div class="insp-row"><span class="k">Rol</span><span class="v">MODBUS_SLAVE</span></div>
|
||||
<div class="insp-row"><span class="k">Modbus addr</span><span class="v">1</span></div>
|
||||
<div class="insp-row"><span class="k">Ubicación</span><span class="v text">SM panel ME_PORT</span></div>
|
||||
<div class="insp-row"><span class="k">x_pp</span><span class="v">5.50 m</span></div>
|
||||
<div class="insp-row"><span class="k">y_cl</span><span class="v">-0.50 m</span></div>
|
||||
<div class="insp-row"><span class="k">z_bl</span><span class="v">+1.40 m</span></div>
|
||||
<div class="insp-row"><span class="k">Firmware</span><span class="v">1.4.2</span></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: var(--s-5);">
|
||||
<div class="sb-title" style="padding: 0; color: var(--c-fog);">Capacidad usada</div>
|
||||
<div style="display: grid; gap: var(--s-2); margin-top: var(--s-2); font-family: var(--f-mono); font-size: 12px;">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>DO</span><span><strong style="color:var(--c-foam)">2</strong>/10 <span style="color:var(--c-fog)">· 20%</span></span>
|
||||
</div>
|
||||
<div style="height: 6px; background: var(--c-steel); border-radius: var(--r-pill);">
|
||||
<div style="width: 20%; height: 100%; background: var(--g-cyan); border-radius: var(--r-pill);"></div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>DI</span><span><strong style="color:var(--c-foam)">2</strong>/5 <span style="color:var(--c-fog)">· 40%</span></span>
|
||||
</div>
|
||||
<div style="height: 6px; background: var(--c-steel); border-radius: var(--r-pill);">
|
||||
<div style="width: 40%; height: 100%; background: var(--g-cyan); border-radius: var(--r-pill);"></div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>AI</span><span><strong style="color:var(--c-foam)">4</strong>/4 <span style="color:var(--c-warn)">· 100%</span></span>
|
||||
</div>
|
||||
<div style="height: 6px; background: var(--c-steel); border-radius: var(--r-pill);">
|
||||
<div style="width: 100%; height: 100%; background: var(--g-warn); border-radius: var(--r-pill);"></div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span>RPM</span><span><strong style="color:var(--c-foam)">1</strong>/1 <span style="color:var(--c-warn)">· 100%</span></span>
|
||||
</div>
|
||||
<div style="height: 6px; background: var(--c-steel); border-radius: var(--r-pill);">
|
||||
<div style="width: 100%; height: 100%; background: var(--g-warn); border-radius: var(--r-pill);"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: var(--s-5);">
|
||||
<button class="btn btn-secondary" style="width: 100%; margin-bottom: var(--s-2);">Editar bindings</button>
|
||||
<button class="btn btn-ghost" style="width: 100%;">Ver puertos físicos →</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<footer class="statusbar">
|
||||
<span><strong>14</strong> sistemas</span>
|
||||
<span class="sep">|</span>
|
||||
<span><strong>3</strong> equipos</span>
|
||||
<span class="sep">|</span>
|
||||
<span><strong>11</strong> tags</span>
|
||||
<span class="sep">|</span>
|
||||
<span><strong>3</strong> tarjetas</span>
|
||||
<span class="sep">|</span>
|
||||
<span>tests <strong style="color:var(--c-ok)">99/99</strong></span>
|
||||
<span class="sep">|</span>
|
||||
<span>cov <strong style="color:var(--c-ok)">95.17%</strong></span>
|
||||
<span class="sb-spacer"></span>
|
||||
<span>VMS-Sailor Studio <strong>0.1.0</strong></span>
|
||||
<span class="sep">|</span>
|
||||
<span>Sprint <strong style="color:var(--c-cyan)">0</strong></span>
|
||||
<span class="sep">|</span>
|
||||
<span><span class="dot ok"></span> Activado</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user