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

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

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

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

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

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

Decisiones autonomas documentadas en docs/decisions_sprint0.md.

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

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

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

764 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VMS-Sailor · 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">&gt; 8° / 10 s</div>
</div>
<div class="level l2">
<div class="num">L2</div>
<div class="name">Auto-offer</div>
<div class="thr">&gt; 12° / 5 s</div>
</div>
<div class="level l3">
<div class="num">L3</div>
<div class="name">Forced reset</div>
<div class="thr">&gt; 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></span>
<span></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>