Files
AR-VMS-Seaman/docs/mockups/mobile_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

571 lines
20 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 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></span>
<span></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>