deb04c9315
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>
764 lines
26 KiB
HTML
764 lines
26 KiB
HTML
<!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>
|