signalk-mareas-ihm 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "2.1.1",
3
- "timestamp": "20260603-1455",
2
+ "version": "2.1.3",
3
+ "timestamp": "20260609-0150",
4
4
  "gitHash": null,
5
5
  "gitDirty": true,
6
- "builtAt": "2026-06-03T12:55:45.424Z"
6
+ "builtAt": "2026-06-08T23:50:21.804Z"
7
7
  }
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ function isPositionValue(v) {
49
49
  // timestamp + git hash so we can verify exactly which build is running on the Pi
50
50
  // without ambiguity. ("¿Qué versión tengo deployada?" → /api/paths or landing.)
51
51
  const PLUGIN_VERSION = esmRequire("../package.json").version;
52
- const PLUGIN_REVISION = "Rev312";
52
+ const PLUGIN_REVISION = "Rev314";
53
53
  let _buildInfo = null;
54
54
  try {
55
55
  _buildInfo = esmRequire("./build-info.json");
package/dist/mobile.html CHANGED
@@ -1320,30 +1320,30 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1320
1320
  mismas funciones que el desktop -->
1321
1321
  <div id="m-sidebar">
1322
1322
  <!-- Rev187: orden Echar, Centrar, Info, AIS, Cartas, Abrigo, Curvas, Mareas, Alarmas, Fondeo -->
1323
- <button class="m-sb-btn" id="m-sb-anchor" onclick="m_onAnchorBtn()" title="Fondear/Levar"><span class="ico">⚓</span><span id="m-sb-anchor-lbl" data-i18n="sb_echar">Echar</span></button>
1324
- <button class="m-sb-btn" id="m-sb-center" onclick="centerOnAnchor&&centerOnAnchor()||m_centerOnBoat()" title="Centrar mapa">
1323
+ <button class="m-sb-btn" id="m-sb-anchor" onclick="m_onAnchorBtn()" data-i18n-title="sb_anchor_tip" title="Fondear/Levar"><span class="ico">⚓</span><span id="m-sb-anchor-lbl" data-i18n="sb_echar">Echar</span></button>
1324
+ <button class="m-sb-btn" id="m-sb-center" onclick="centerOnAnchor&&centerOnAnchor()||m_centerOnBoat()" data-i18n-title="sb_centrar_tip" title="Centrar mapa">
1325
1325
  <span class="ico" style="display:inline-flex;align-items:center;justify-content:center"><svg viewBox="0 0 44 44" width="34" height="34"><circle cx="22" cy="22" r="18" fill="none" stroke="#f44336" stroke-width="2.5" stroke-dasharray="4 3"/><line x1="22" y1="0" x2="22" y2="44" stroke="#f44336" stroke-width="1.5"/><line x1="0" y1="22" x2="44" y2="22" stroke="#f44336" stroke-width="1.5"/><circle cx="22" cy="22" r="3" fill="#f44336"/><text x="22" y="27" text-anchor="middle" font-size="18" fill="#fff" style="filter:drop-shadow(0 0 2px #000)">⚓</text></svg></span>
1326
1326
  <span data-i18n="sb_centrar">Centrar</span>
1327
1327
  </button>
1328
- <button class="m-sb-btn" id="m-sb-info" onclick="m_openInfoModal()" title="Datos del fondeo">
1328
+ <button class="m-sb-btn" id="m-sb-info" onclick="m_openInfoModal()" data-i18n-title="sb_info_tip" title="Datos del fondeo">
1329
1329
  <span class="ico" style="background:#fff;color:#000;width:32px;height:32px;border-radius:50%;font-family:Georgia,serif;font-style:italic;font-weight:900;font-size:22px;display:inline-flex;align-items:center;justify-content:center;line-height:1">i</span>
1330
1330
  <span data-i18n="sb_info">Info</span>
1331
1331
  </button>
1332
- <button class="m-sb-btn" id="m-sb-ais" onclick="m_toggleAisList()" title="AIS en zona"><span class="ico">🚢</span><span id="m-sb-ais-lbl" data-i18n="sb_ais">AIS</span></button>
1333
- <button class="m-sb-btn" id="m-sb-panel" onclick="m_togglePanel()" title="Cartas y Capas"><span class="ico">🗺️</span><span data-i18n="sb_cartas">Cartas</span></button>
1334
- <button class="m-sb-btn" id="m-sb-abrigo" onclick="openShelterConfig()" title="Previsión abrigo"><span class="ico">🏔️</span><span data-i18n="sb_abrigo">Abrigo</span></button>
1332
+ <button class="m-sb-btn" id="m-sb-ais" onclick="m_toggleAisList()" data-i18n-title="ais_en_zona" title="AIS en zona"><span class="ico">🚢</span><span id="m-sb-ais-lbl" data-i18n="sb_ais">AIS</span></button>
1333
+ <button class="m-sb-btn" id="m-sb-panel" onclick="m_togglePanel()" data-i18n-title="panel_cartas_capas" title="Cartas y Capas"><span class="ico">🗺️</span><span data-i18n="sb_cartas">Cartas</span></button>
1334
+ <button class="m-sb-btn" id="m-sb-abrigo" onclick="openShelterConfig()" data-i18n-title="sb_abrigo_tip" title="Previsión abrigo"><span class="ico">🏔️</span><span data-i18n="sb_abrigo">Abrigo</span></button>
1335
1335
  <!-- Rev297: Curvas y Mareas se han movido al left bar (entre Favorito y KIP).
1336
1336
  Se quitan de aquí para no duplicar. -->
1337
- <button class="m-sb-btn" id="m-sb-alarm" onclick="toggleAlarmPanel()" title="Panel de alarmas"><span class="ico">🔔</span><span data-i18n="sb_alarmas">Alarmas</span></button>
1338
- <button class="m-sb-btn" id="m-sb-fondeo" onclick="openPopup('fondeo-pop');fetchFondeoData()" title="Cálculo fondeo"><span class="ico">⛓️</span><span data-i18n="sb_fondeo">Fondeo</span></button>
1337
+ <button class="m-sb-btn" id="m-sb-alarm" onclick="toggleAlarmPanel()" data-i18n-title="sb_alarmas_tip" title="Panel de alarmas"><span class="ico">🔔</span><span data-i18n="sb_alarmas">Alarmas</span></button>
1338
+ <button class="m-sb-btn" id="m-sb-fondeo" onclick="openPopup('fondeo-pop');fetchFondeoData()" data-i18n-title="sb_fondeo_tip" title="Cálculo fondeo"><span class="ico">⛓️</span><span data-i18n="sb_fondeo">Fondeo</span></button>
1339
1339
  </div>
1340
1340
 
1341
1341
  <!-- Bottom info bar (icon-led + mini sparkline presión + grade abrigo) -->
1342
1342
  <!-- Rev285: viento con flecha grande+kt dentro, junto a SOG.
1343
1343
  Donut de abrigo con letra dentro. SOG sin click. -->
1344
1344
  <div id="m-bottom-bar">
1345
- <div class="it" title="Velocidad sobre fondo"><span class="lb" data-i18n="bb_velocidad">Velocidad</span><span class="vl" id="m-bb-sog">—</span></div>
1346
- <div class="it m-bb-wind" title="Viento real (veleta) · flecha apunta hacia DONDE VA (TO) · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1345
+ <div class="it" data-i18n-title="bb_velocidad_tip" title="Velocidad sobre fondo"><span class="lb" data-i18n="bb_velocidad">Velocidad</span><span class="vl" id="m-bb-sog">—</span></div>
1346
+ <div class="it m-bb-wind" data-i18n-title="bb_viento_tip" title="Viento real (veleta) · flecha apunta hacia DONDE VA (TO) · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1347
1347
  <span class="lb" data-i18n="bb_viento">Viento</span>
1348
1348
  <!-- Rev295: alineación correcta con el resto de .vl. El SVG con margin
1349
1349
  negativo compensa el padding interno del viewBox 28 con flecha hasta
@@ -1357,10 +1357,10 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1357
1357
  <span class="vl" id="m-bb-wind-kt-txt">—<span style="font-size:11px;font-weight:700;opacity:.75;margin-left:2px">kts</span></span>
1358
1358
  </div>
1359
1359
  </div>
1360
- <div class="it" title="Profundidad bajo quilla · Sonda" onclick="openPopup&&openPopup('sonda-pop')"><span class="lb" data-i18n="bb_sonda">Sonda</span><span class="vl" id="m-bb-sonda">—</span><span id="m-bb-sonda-status" style="display:none;font-size:9px;font-weight:800;margin-top:2px;text-align:center;line-height:1.1"></span></div>
1361
- <div class="it" title="Cadena recomendada" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_cadena_rec">Cadena rec.</span><span class="vl" id="m-bb-cad">—</span></div>
1362
- <div class="it" title="Distancia al ancla" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_dist_ancla">Dist. ancla</span><span class="vl" id="m-bb-dist">—</span></div>
1363
- <div class="it m-bb-graph" title="Presión barométrica · línea naranja = AHORA · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1360
+ <div class="it" data-i18n-title="bb_sonda_tip" title="Profundidad bajo quilla · Sonda" onclick="openPopup&&openPopup('sonda-pop')"><span class="lb" data-i18n="bb_sonda">Sonda</span><span class="vl" id="m-bb-sonda">—</span><span id="m-bb-sonda-status" style="display:none;font-size:9px;font-weight:800;margin-top:2px;text-align:center;line-height:1.1"></span></div>
1361
+ <div class="it" data-i18n-title="bb_cadena_tip" title="Cadena recomendada" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_cadena_rec">Cadena rec.</span><span class="vl" id="m-bb-cad">—</span></div>
1362
+ <div class="it" data-i18n-title="bb_dist_tip" title="Distancia al ancla" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_dist_ancla">Dist. ancla</span><span class="vl" id="m-bb-dist">—</span></div>
1363
+ <div class="it m-bb-graph" data-i18n-title="bb_presion_tip" title="Presión barométrica · línea naranja = AHORA · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1364
1364
  <span class="lb" data-i18n="bb_presion">Presión</span>
1365
1365
  <!-- Rev288: viewBox extendido 0-80 para reservar espacio a la previsión
1366
1366
  (a la derecha del AHORA). El histórico ocupa 0-60 (línea AHORA en
@@ -1373,11 +1373,11 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1373
1373
  </svg>
1374
1374
  <span class="vl" id="m-bb-pres-val" style="font-size:13px!important">—</span>
1375
1375
  </div>
1376
- <div class="it m-bb-grade" title="Grado de abrigo · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1376
+ <div class="it m-bb-grade" data-i18n-title="bb_abrigo_tip" title="Grado de abrigo · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1377
1377
  <span class="lb" data-i18n="bb_abrigo">Abrigo</span>
1378
1378
  <span id="m-bb-grade" style="display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:rgba(120,120,120,.35);color:#fff;font-weight:900;font-size:18px;line-height:1">—</span>
1379
1379
  </div>
1380
- <div class="it m-bb-donut" title="Calidad de abrigo (gauge) · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1380
+ <div class="it m-bb-donut" data-i18n-title="bb_calidad_tip" title="Calidad de abrigo (gauge) · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1381
1381
  <span class="lb" data-i18n="bb_calidad">Calidad</span>
1382
1382
  <svg viewBox="0 0 36 36" width="38" height="38" style="display:block">
1383
1383
  <circle cx="18" cy="18" r="15.5" fill="none" stroke="rgba(255,255,255,.15)" stroke-width="3.5"/>
@@ -1777,14 +1777,14 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1777
1777
  <!-- Rev286: modal propio para guardar fondeo favorito (en vez del prompt() nativo cutre). -->
1778
1778
  <div class="popup-overlay" id="m-fav-save-pop" onclick="if(event.target.id==='m-fav-save-pop')closePopup('m-fav-save-pop')">
1779
1779
  <div class="popup-box" onclick="event.stopPropagation()">
1780
- <h4>❤ Guardar fondeo favorito</h4>
1781
- <p style="opacity:.85;margin:8px 0 14px">Nombre del favorito (vacío = nombre automático por geolocalización):</p>
1782
- <input type="text" id="m-fav-save-name" placeholder="p.ej. Cangas - Massó"
1780
+ <h4 data-i18n="fav_guardar_title">❤ Guardar fondeo favorito</h4>
1781
+ <p style="opacity:.85;margin:8px 0 14px" data-i18n="fav_guardar_hint">Nombre del favorito (vacío = nombre automático por geolocalización):</p>
1782
+ <input type="text" id="m-fav-save-name" data-i18n-placeholder="fav_placeholder" placeholder="p.ej. Cangas - Massó"
1783
1783
  style="width:100%;padding:14px 16px;font-size:20px;border-radius:10px;border:2px solid rgba(255,255,255,.25);background:rgba(0,0,0,.35);color:#fff;box-sizing:border-box"
1784
1784
  onkeydown="if(event.key==='Enter')m_favSaveConfirm();">
1785
1785
  <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:18px">
1786
- <button onclick="closePopup('m-fav-save-pop')" style="padding:12px 22px;background:rgba(255,255,255,.15);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:700">Cancelar</button>
1787
- <button onclick="m_favSaveConfirm()" style="padding:12px 22px;background:var(--org);color:#0a1929;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:900">Guardar</button>
1786
+ <button onclick="closePopup('m-fav-save-pop')" style="padding:12px 22px;background:rgba(255,255,255,.15);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:700" data-i18n="m_cancelar">Cancelar</button>
1787
+ <button onclick="m_favSaveConfirm()" style="padding:12px 22px;background:var(--org);color:#0a1929;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:900" data-i18n="m_guardar">Guardar</button>
1788
1788
  </div>
1789
1789
  </div>
1790
1790
  </div>
@@ -1797,7 +1797,7 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1797
1797
  <div style="margin:4px 0;font-size:21px;display:flex;gap:10px;align-items:center;flex-wrap:wrap">
1798
1798
  <span style="opacity:.6;flex-shrink:0"><span data-i18n="meteo_modelo">Modelo:</span></span>
1799
1799
  <select id="meteo-model" style="background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.3);color:#fff;border-radius:4px;padding:4px 8px;font-size:21px;flex-shrink:0" onchange="fetchMeteo()">
1800
- <option value="" style="background:#1a2a3a;color:#fff">Mejor (automático)</option><option value="&models=icon_seamless" style="background:#1a2a3a;color:#fff">ICON (DWD)</option><option value="&models=gfs_seamless" style="background:#1a2a3a;color:#fff">GFS (NOAA)</option><option value="&models=ecmwf_ifs025" style="background:#1a2a3a;color:#fff">ECMWF IFS</option><option value="&models=meteofrance_seamless" style="background:#1a2a3a;color:#fff">Météo-France</option><option value="&models=gem_seamless" style="background:#1a2a3a;color:#fff">GEM (Canada)</option><option value="&models=jma_seamless" style="background:#1a2a3a;color:#fff">JMA (Japan)</option>
1800
+ <option value="" style="background:#1a2a3a;color:#fff" data-i18n="meteo_modelo_auto">Mejor (automático)</option><option value="&models=icon_seamless" style="background:#1a2a3a;color:#fff">ICON (DWD)</option><option value="&models=gfs_seamless" style="background:#1a2a3a;color:#fff">GFS (NOAA)</option><option value="&models=ecmwf_ifs025" style="background:#1a2a3a;color:#fff">ECMWF IFS</option><option value="&models=meteofrance_seamless" style="background:#1a2a3a;color:#fff">Météo-France</option><option value="&models=gem_seamless" style="background:#1a2a3a;color:#fff">GEM (Canada)</option><option value="&models=jma_seamless" style="background:#1a2a3a;color:#fff">JMA (Japan)</option>
1801
1801
  </select>
1802
1802
  <span id="meteo-model-desc" style="font-size:20px;color:#cde;font-weight:600;flex:1;min-width:200px;line-height:1.25;white-space:normal"></span>
1803
1803
  </div>
@@ -1806,7 +1806,7 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1806
1806
  como TEXTO HTML — se renderizaba tal cual hasta que fetchMeteo()
1807
1807
  reemplazaba el innerHTML. Ahora simplemente "Cargando…" en plano; JS
1808
1808
  lo sobreescribe en cuanto llega el primer fetch. -->
1809
- <div id="meteo-panel" style="overflow-y:auto;flex:1;padding:0"><div style="text-align:center;padding:20px;opacity:.5;font-size:15px">Cargando…</div></div>
1809
+ <div id="meteo-panel" style="overflow-y:auto;flex:1;padding:0"><div style="text-align:center;padding:20px;opacity:.5;font-size:15px" data-i18n="m_cargando">Cargando…</div></div>
1810
1810
  </div>
1811
1811
  </div>
1812
1812
  <div class="popup-overlay" id="ais-pop" onclick="closePopup('ais-pop')" style="background:transparent;pointer-events:none"><div class="popup-box" onclick="event.stopPropagation()" style="position:fixed;bottom:80px;left:10px;pointer-events:auto;width:321px;max-height:445px;overflow-y:auto;z-index:2001;padding:10px 12px;box-sizing:border-box"><button onclick="closePopup('ais-pop')" style="position:absolute;top:6px;right:8px;background:none;border:none;color:#aaa;font-size:20px;cursor:pointer;padding:2px 6px;line-height:1">✕</button><h4 id="ais-pop-title" style="margin-right:28px">🚢 Target AIS</h4><div id="ais-pop-actions"></div><div id="ais-pop-body"></div></div></div>
@@ -1814,7 +1814,7 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1814
1814
  <div class="popup-box" onclick="event.stopPropagation()" style="width:520px;max-width:94vw;max-height:85vh;overflow-y:auto">
1815
1815
  <button onclick="closePopup('sonda-pop')" style="position:absolute;top:6px;right:10px;background:none;border:none;color:#aaa;font-size:22px;cursor:pointer;line-height:1">✕</button>
1816
1816
  <h4 style="font-size:18px" data-i18n="calc_sonda_title">📐 Cálculo de Sonda</h4>
1817
- <div id="sonda-content" style="margin-top:10px"><div style="text-align:center;padding:20px;opacity:.5">Cargando...</div></div>
1817
+ <div id="sonda-content" style="margin-top:10px"><div style="text-align:center;padding:20px;opacity:.5" data-i18n="m_cargando">Cargando…</div></div>
1818
1818
  </div>
1819
1819
  </div>
1820
1820
  <div class="popup-overlay" id="fondeo-pop" onclick="closePopup('fondeo-pop')">
@@ -2007,6 +2007,29 @@ var _i18n={
2007
2007
  m_calma:{es:'Calma',en:'Calm'},
2008
2008
  sh_detected:{es:'Detectado',en:'Detected'},
2009
2009
  sh_sectores:{es:'sectores abrigados',en:'sheltered sectors'},
2010
+ /* Rev313: tooltips bottom bar */
2011
+ bb_velocidad_tip:{es:'Velocidad sobre fondo',en:'Speed over ground'},
2012
+ bb_viento_tip:{es:'Viento real (veleta) · flecha apunta hacia DONDE VA (TO) · click → Meteo',en:'True wind (vane) · arrow points TO where it goes · click → Weather'},
2013
+ bb_sonda_tip:{es:'Profundidad bajo quilla · Sonda',en:'Depth under keel · Sounder'},
2014
+ bb_cadena_tip:{es:'Cadena recomendada',en:'Recommended chain'},
2015
+ bb_dist_tip:{es:'Distancia al ancla',en:'Distance to anchor'},
2016
+ bb_presion_tip:{es:'Presión barométrica · línea naranja = AHORA · click → Meteo',en:'Barometric pressure · orange line = NOW · click → Weather'},
2017
+ bb_abrigo_tip:{es:'Grado de abrigo · click → Abrigo',en:'Shelter grade · click → Shelter'},
2018
+ bb_calidad_tip:{es:'Calidad de abrigo (gauge) · click → Abrigo',en:'Shelter quality (gauge) · click → Shelter'},
2019
+ /* Rev313: tooltips sidebar derecha */
2020
+ sb_anchor_tip:{es:'Fondear / Levar',en:'Drop / Lift anchor'},
2021
+ sb_centrar_tip:{es:'Centrar mapa',en:'Center map'},
2022
+ sb_info_tip:{es:'Datos del fondeo',en:'Anchorage data'},
2023
+ sb_abrigo_tip:{es:'Previsión abrigo',en:'Shelter forecast'},
2024
+ sb_alarmas_tip:{es:'Panel de alarmas',en:'Alarms panel'},
2025
+ sb_fondeo_tip:{es:'Cálculo fondeo',en:'Anchorage calculator'},
2026
+ /* Rev313: modal guardar favorito */
2027
+ fav_guardar_title:{es:'❤ Guardar fondeo favorito',en:'❤ Save favourite anchorage'},
2028
+ fav_guardar_hint:{es:'Nombre del favorito (vacío = nombre automático por geolocalización):',en:'Favourite name (empty = auto name by geolocation):'},
2029
+ fav_placeholder:{es:'p.ej. Cangas - Massó',en:'e.g. Cangas - Massó'},
2030
+ m_cancelar:{es:'Cancelar',en:'Cancel'},
2031
+ m_guardar:{es:'Guardar',en:'Save'},
2032
+ meteo_modelo_auto:{es:'Mejor (automático)',en:'Best (automatic)'},
2010
2033
  otras_capas:{es:'Otras capas base',en:'Other base layers'},
2011
2034
  cartas_mbt:{es:'Cartas MBTiles',en:'MBTiles Charts'},
2012
2035
  cartas_sk:{es:'Cartas registradas (Signal K)',en:'Registered charts (Signal K)'},
@@ -2226,6 +2249,15 @@ function applyLang(){
2226
2249
  var k=el.getAttribute('data-i18n-html');
2227
2250
  var t=T(k);if(t)el.innerHTML=t;
2228
2251
  });
2252
+ /* Rev313: traducir atributos title (tooltips on hover) y placeholders. */
2253
+ document.querySelectorAll('[data-i18n-title]').forEach(function(el){
2254
+ var k=el.getAttribute('data-i18n-title');
2255
+ var t=T(k);if(t)el.setAttribute('title',t);
2256
+ });
2257
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){
2258
+ var k=el.getAttribute('data-i18n-placeholder');
2259
+ var t=T(k);if(t)el.setAttribute('placeholder',t);
2260
+ });
2229
2261
  /* Special elements */
2230
2262
  var bl=document.getElementById('ais-borneo-label');if(bl)bl.textContent=T('colision');
2231
2263
  /* Flag active state */
@@ -5092,7 +5124,11 @@ function _shelterRenderPopup(){
5092
5124
  if(typeof _boatWave.heightSigM==='number'&&_boatWave.heightSigM>=0.05){
5093
5125
  waveLblTxt=_boatWave.heightSigM.toFixed(1).replace('.',',')+' m'+(_wavePer?' '+_wavePer:'');
5094
5126
  }else{
5095
- var _waveIntShort={calma:'Cal',rizada:'Riz',moderada:'Mod',agitada:'Agi',fuerte:'Fue'}[_boatWave.intensity||'']||(_boatWave.intensity||'');
5127
+ /* Rev313: i18n abreviaturas intensidad ola. */
5128
+ var _waveIntMap=(_lang==='en')
5129
+ ? {calma:'Calm',rizada:'Rip',moderada:'Mod',agitada:'Rou',fuerte:'Str'}
5130
+ : {calma:'Cal',rizada:'Riz',moderada:'Mod',agitada:'Agi',fuerte:'Fue'};
5131
+ var _waveIntShort=_waveIntMap[_boatWave.intensity||'']||(_boatWave.intensity||'');
5096
5132
  waveLblTxt=_waveIntShort+(_wavePer?' '+_wavePer:'');
5097
5133
  }
5098
5134
  waveLblHalfW=Math.max(30, waveLblTxt.length*4+8);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "signalk-mareas-ihm",
3
- "version": "2.1.1",
4
- "description": "Official tide predictions from the Spanish Hydrographic Institute (IHM).",
3
+ "version": "2.1.3",
4
+ "description": "Complete SignalK anchor watch & marine safety webapp. Voice alarms for anchor drag, AIS collision risk and grounding. Shelter / anchorage forecast (wind exposure + wave history). Weather forecast (Open-Meteo). Depth & scope calculator. Multi-source nautical charts (Esri Satellite, IHM ENC S-52, bathymetry, MBTiles offline, OpenStreetMap, OpenSeaMap). Favourite anchorages. Multi-device real-time sync (SSE). Includes official IHM Spain tide predictions. Mobile-first UI in ES/EN.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "exports": "./dist/index.js",
@@ -19,15 +19,72 @@
19
19
  "signalk-node-server-plugin",
20
20
  "signalk-category-weather",
21
21
  "signalk-category-utility",
22
+ "signalk-category-instruments",
23
+ "signalk-plugin",
24
+ "anchor",
25
+ "anchor-watch",
26
+ "anchor-alarm",
27
+ "drag-alarm",
28
+ "anchor-drag",
29
+ "dragging",
30
+ "ais",
31
+ "ais-alarm",
32
+ "ais-collision",
33
+ "collision-avoidance",
34
+ "shelter",
35
+ "shelter-forecast",
36
+ "anchorage",
37
+ "anchorage-forecast",
38
+ "exposure",
39
+ "swing-radius",
40
+ "scope",
41
+ "chain",
42
+ "snubber",
43
+ "rode",
44
+ "depth",
45
+ "depth-calculator",
46
+ "sounder",
47
+ "echo-sounder",
48
+ "bathymetry",
49
+ "grounding-alarm",
22
50
  "weather",
23
- "utility",
51
+ "weather-forecast",
52
+ "marine-weather",
53
+ "open-meteo",
54
+ "tide",
24
55
  "tides",
25
- "mareas",
26
- "ihm",
27
- "spain",
56
+ "tide-prediction",
57
+ "tide-predictions",
58
+ "wms",
59
+ "enc",
60
+ "s-52",
61
+ "mbtiles",
62
+ "nautical-charts",
63
+ "marine-charts",
64
+ "chartplotter",
65
+ "chart-plotter",
66
+ "leaflet",
67
+ "openseamap",
68
+ "esri",
69
+ "marine",
28
70
  "nautical",
29
71
  "navigation",
30
- "marine"
72
+ "boat",
73
+ "yacht",
74
+ "sailing",
75
+ "cruising",
76
+ "liveaboard",
77
+ "safety",
78
+ "marine-safety",
79
+ "alarm",
80
+ "voice-alarm",
81
+ "tts",
82
+ "favourite-anchorages",
83
+ "multi-device",
84
+ "sse",
85
+ "real-time",
86
+ "ihm-spain",
87
+ "spain-tides"
31
88
  ],
32
89
  "author": {
33
90
  "name": "Aitonos",
@@ -1320,30 +1320,30 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1320
1320
  mismas funciones que el desktop -->
1321
1321
  <div id="m-sidebar">
1322
1322
  <!-- Rev187: orden Echar, Centrar, Info, AIS, Cartas, Abrigo, Curvas, Mareas, Alarmas, Fondeo -->
1323
- <button class="m-sb-btn" id="m-sb-anchor" onclick="m_onAnchorBtn()" title="Fondear/Levar"><span class="ico">⚓</span><span id="m-sb-anchor-lbl" data-i18n="sb_echar">Echar</span></button>
1324
- <button class="m-sb-btn" id="m-sb-center" onclick="centerOnAnchor&&centerOnAnchor()||m_centerOnBoat()" title="Centrar mapa">
1323
+ <button class="m-sb-btn" id="m-sb-anchor" onclick="m_onAnchorBtn()" data-i18n-title="sb_anchor_tip" title="Fondear/Levar"><span class="ico">⚓</span><span id="m-sb-anchor-lbl" data-i18n="sb_echar">Echar</span></button>
1324
+ <button class="m-sb-btn" id="m-sb-center" onclick="centerOnAnchor&&centerOnAnchor()||m_centerOnBoat()" data-i18n-title="sb_centrar_tip" title="Centrar mapa">
1325
1325
  <span class="ico" style="display:inline-flex;align-items:center;justify-content:center"><svg viewBox="0 0 44 44" width="34" height="34"><circle cx="22" cy="22" r="18" fill="none" stroke="#f44336" stroke-width="2.5" stroke-dasharray="4 3"/><line x1="22" y1="0" x2="22" y2="44" stroke="#f44336" stroke-width="1.5"/><line x1="0" y1="22" x2="44" y2="22" stroke="#f44336" stroke-width="1.5"/><circle cx="22" cy="22" r="3" fill="#f44336"/><text x="22" y="27" text-anchor="middle" font-size="18" fill="#fff" style="filter:drop-shadow(0 0 2px #000)">⚓</text></svg></span>
1326
1326
  <span data-i18n="sb_centrar">Centrar</span>
1327
1327
  </button>
1328
- <button class="m-sb-btn" id="m-sb-info" onclick="m_openInfoModal()" title="Datos del fondeo">
1328
+ <button class="m-sb-btn" id="m-sb-info" onclick="m_openInfoModal()" data-i18n-title="sb_info_tip" title="Datos del fondeo">
1329
1329
  <span class="ico" style="background:#fff;color:#000;width:32px;height:32px;border-radius:50%;font-family:Georgia,serif;font-style:italic;font-weight:900;font-size:22px;display:inline-flex;align-items:center;justify-content:center;line-height:1">i</span>
1330
1330
  <span data-i18n="sb_info">Info</span>
1331
1331
  </button>
1332
- <button class="m-sb-btn" id="m-sb-ais" onclick="m_toggleAisList()" title="AIS en zona"><span class="ico">🚢</span><span id="m-sb-ais-lbl" data-i18n="sb_ais">AIS</span></button>
1333
- <button class="m-sb-btn" id="m-sb-panel" onclick="m_togglePanel()" title="Cartas y Capas"><span class="ico">🗺️</span><span data-i18n="sb_cartas">Cartas</span></button>
1334
- <button class="m-sb-btn" id="m-sb-abrigo" onclick="openShelterConfig()" title="Previsión abrigo"><span class="ico">🏔️</span><span data-i18n="sb_abrigo">Abrigo</span></button>
1332
+ <button class="m-sb-btn" id="m-sb-ais" onclick="m_toggleAisList()" data-i18n-title="ais_en_zona" title="AIS en zona"><span class="ico">🚢</span><span id="m-sb-ais-lbl" data-i18n="sb_ais">AIS</span></button>
1333
+ <button class="m-sb-btn" id="m-sb-panel" onclick="m_togglePanel()" data-i18n-title="panel_cartas_capas" title="Cartas y Capas"><span class="ico">🗺️</span><span data-i18n="sb_cartas">Cartas</span></button>
1334
+ <button class="m-sb-btn" id="m-sb-abrigo" onclick="openShelterConfig()" data-i18n-title="sb_abrigo_tip" title="Previsión abrigo"><span class="ico">🏔️</span><span data-i18n="sb_abrigo">Abrigo</span></button>
1335
1335
  <!-- Rev297: Curvas y Mareas se han movido al left bar (entre Favorito y KIP).
1336
1336
  Se quitan de aquí para no duplicar. -->
1337
- <button class="m-sb-btn" id="m-sb-alarm" onclick="toggleAlarmPanel()" title="Panel de alarmas"><span class="ico">🔔</span><span data-i18n="sb_alarmas">Alarmas</span></button>
1338
- <button class="m-sb-btn" id="m-sb-fondeo" onclick="openPopup('fondeo-pop');fetchFondeoData()" title="Cálculo fondeo"><span class="ico">⛓️</span><span data-i18n="sb_fondeo">Fondeo</span></button>
1337
+ <button class="m-sb-btn" id="m-sb-alarm" onclick="toggleAlarmPanel()" data-i18n-title="sb_alarmas_tip" title="Panel de alarmas"><span class="ico">🔔</span><span data-i18n="sb_alarmas">Alarmas</span></button>
1338
+ <button class="m-sb-btn" id="m-sb-fondeo" onclick="openPopup('fondeo-pop');fetchFondeoData()" data-i18n-title="sb_fondeo_tip" title="Cálculo fondeo"><span class="ico">⛓️</span><span data-i18n="sb_fondeo">Fondeo</span></button>
1339
1339
  </div>
1340
1340
 
1341
1341
  <!-- Bottom info bar (icon-led + mini sparkline presión + grade abrigo) -->
1342
1342
  <!-- Rev285: viento con flecha grande+kt dentro, junto a SOG.
1343
1343
  Donut de abrigo con letra dentro. SOG sin click. -->
1344
1344
  <div id="m-bottom-bar">
1345
- <div class="it" title="Velocidad sobre fondo"><span class="lb" data-i18n="bb_velocidad">Velocidad</span><span class="vl" id="m-bb-sog">—</span></div>
1346
- <div class="it m-bb-wind" title="Viento real (veleta) · flecha apunta hacia DONDE VA (TO) · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1345
+ <div class="it" data-i18n-title="bb_velocidad_tip" title="Velocidad sobre fondo"><span class="lb" data-i18n="bb_velocidad">Velocidad</span><span class="vl" id="m-bb-sog">—</span></div>
1346
+ <div class="it m-bb-wind" data-i18n-title="bb_viento_tip" title="Viento real (veleta) · flecha apunta hacia DONDE VA (TO) · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1347
1347
  <span class="lb" data-i18n="bb_viento">Viento</span>
1348
1348
  <!-- Rev295: alineación correcta con el resto de .vl. El SVG con margin
1349
1349
  negativo compensa el padding interno del viewBox 28 con flecha hasta
@@ -1357,10 +1357,10 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1357
1357
  <span class="vl" id="m-bb-wind-kt-txt">—<span style="font-size:11px;font-weight:700;opacity:.75;margin-left:2px">kts</span></span>
1358
1358
  </div>
1359
1359
  </div>
1360
- <div class="it" title="Profundidad bajo quilla · Sonda" onclick="openPopup&&openPopup('sonda-pop')"><span class="lb" data-i18n="bb_sonda">Sonda</span><span class="vl" id="m-bb-sonda">—</span><span id="m-bb-sonda-status" style="display:none;font-size:9px;font-weight:800;margin-top:2px;text-align:center;line-height:1.1"></span></div>
1361
- <div class="it" title="Cadena recomendada" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_cadena_rec">Cadena rec.</span><span class="vl" id="m-bb-cad">—</span></div>
1362
- <div class="it" title="Distancia al ancla" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_dist_ancla">Dist. ancla</span><span class="vl" id="m-bb-dist">—</span></div>
1363
- <div class="it m-bb-graph" title="Presión barométrica · línea naranja = AHORA · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1360
+ <div class="it" data-i18n-title="bb_sonda_tip" title="Profundidad bajo quilla · Sonda" onclick="openPopup&&openPopup('sonda-pop')"><span class="lb" data-i18n="bb_sonda">Sonda</span><span class="vl" id="m-bb-sonda">—</span><span id="m-bb-sonda-status" style="display:none;font-size:9px;font-weight:800;margin-top:2px;text-align:center;line-height:1.1"></span></div>
1361
+ <div class="it" data-i18n-title="bb_cadena_tip" title="Cadena recomendada" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_cadena_rec">Cadena rec.</span><span class="vl" id="m-bb-cad">—</span></div>
1362
+ <div class="it" data-i18n-title="bb_dist_tip" title="Distancia al ancla" onclick="m_openInfoModal&&m_openInfoModal()"><span class="lb" data-i18n="bb_dist_ancla">Dist. ancla</span><span class="vl" id="m-bb-dist">—</span></div>
1363
+ <div class="it m-bb-graph" data-i18n-title="bb_presion_tip" title="Presión barométrica · línea naranja = AHORA · click → Meteo" onclick="toggleMeteo&&toggleMeteo()">
1364
1364
  <span class="lb" data-i18n="bb_presion">Presión</span>
1365
1365
  <!-- Rev288: viewBox extendido 0-80 para reservar espacio a la previsión
1366
1366
  (a la derecha del AHORA). El histórico ocupa 0-60 (línea AHORA en
@@ -1373,11 +1373,11 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1373
1373
  </svg>
1374
1374
  <span class="vl" id="m-bb-pres-val" style="font-size:13px!important">—</span>
1375
1375
  </div>
1376
- <div class="it m-bb-grade" title="Grado de abrigo · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1376
+ <div class="it m-bb-grade" data-i18n-title="bb_abrigo_tip" title="Grado de abrigo · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1377
1377
  <span class="lb" data-i18n="bb_abrigo">Abrigo</span>
1378
1378
  <span id="m-bb-grade" style="display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:rgba(120,120,120,.35);color:#fff;font-weight:900;font-size:18px;line-height:1">—</span>
1379
1379
  </div>
1380
- <div class="it m-bb-donut" title="Calidad de abrigo (gauge) · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1380
+ <div class="it m-bb-donut" data-i18n-title="bb_calidad_tip" title="Calidad de abrigo (gauge) · click → Abrigo" onclick="openShelterConfig&&openShelterConfig()">
1381
1381
  <span class="lb" data-i18n="bb_calidad">Calidad</span>
1382
1382
  <svg viewBox="0 0 36 36" width="38" height="38" style="display:block">
1383
1383
  <circle cx="18" cy="18" r="15.5" fill="none" stroke="rgba(255,255,255,.15)" stroke-width="3.5"/>
@@ -1777,14 +1777,14 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1777
1777
  <!-- Rev286: modal propio para guardar fondeo favorito (en vez del prompt() nativo cutre). -->
1778
1778
  <div class="popup-overlay" id="m-fav-save-pop" onclick="if(event.target.id==='m-fav-save-pop')closePopup('m-fav-save-pop')">
1779
1779
  <div class="popup-box" onclick="event.stopPropagation()">
1780
- <h4>❤ Guardar fondeo favorito</h4>
1781
- <p style="opacity:.85;margin:8px 0 14px">Nombre del favorito (vacío = nombre automático por geolocalización):</p>
1782
- <input type="text" id="m-fav-save-name" placeholder="p.ej. Cangas - Massó"
1780
+ <h4 data-i18n="fav_guardar_title">❤ Guardar fondeo favorito</h4>
1781
+ <p style="opacity:.85;margin:8px 0 14px" data-i18n="fav_guardar_hint">Nombre del favorito (vacío = nombre automático por geolocalización):</p>
1782
+ <input type="text" id="m-fav-save-name" data-i18n-placeholder="fav_placeholder" placeholder="p.ej. Cangas - Massó"
1783
1783
  style="width:100%;padding:14px 16px;font-size:20px;border-radius:10px;border:2px solid rgba(255,255,255,.25);background:rgba(0,0,0,.35);color:#fff;box-sizing:border-box"
1784
1784
  onkeydown="if(event.key==='Enter')m_favSaveConfirm();">
1785
1785
  <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:18px">
1786
- <button onclick="closePopup('m-fav-save-pop')" style="padding:12px 22px;background:rgba(255,255,255,.15);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:700">Cancelar</button>
1787
- <button onclick="m_favSaveConfirm()" style="padding:12px 22px;background:var(--org);color:#0a1929;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:900">Guardar</button>
1786
+ <button onclick="closePopup('m-fav-save-pop')" style="padding:12px 22px;background:rgba(255,255,255,.15);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:700" data-i18n="m_cancelar">Cancelar</button>
1787
+ <button onclick="m_favSaveConfirm()" style="padding:12px 22px;background:var(--org);color:#0a1929;border:none;border-radius:10px;cursor:pointer;font-size:17px;font-weight:900" data-i18n="m_guardar">Guardar</button>
1788
1788
  </div>
1789
1789
  </div>
1790
1790
  </div>
@@ -1797,7 +1797,7 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1797
1797
  <div style="margin:4px 0;font-size:21px;display:flex;gap:10px;align-items:center;flex-wrap:wrap">
1798
1798
  <span style="opacity:.6;flex-shrink:0"><span data-i18n="meteo_modelo">Modelo:</span></span>
1799
1799
  <select id="meteo-model" style="background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.3);color:#fff;border-radius:4px;padding:4px 8px;font-size:21px;flex-shrink:0" onchange="fetchMeteo()">
1800
- <option value="" style="background:#1a2a3a;color:#fff">Mejor (automático)</option><option value="&models=icon_seamless" style="background:#1a2a3a;color:#fff">ICON (DWD)</option><option value="&models=gfs_seamless" style="background:#1a2a3a;color:#fff">GFS (NOAA)</option><option value="&models=ecmwf_ifs025" style="background:#1a2a3a;color:#fff">ECMWF IFS</option><option value="&models=meteofrance_seamless" style="background:#1a2a3a;color:#fff">Météo-France</option><option value="&models=gem_seamless" style="background:#1a2a3a;color:#fff">GEM (Canada)</option><option value="&models=jma_seamless" style="background:#1a2a3a;color:#fff">JMA (Japan)</option>
1800
+ <option value="" style="background:#1a2a3a;color:#fff" data-i18n="meteo_modelo_auto">Mejor (automático)</option><option value="&models=icon_seamless" style="background:#1a2a3a;color:#fff">ICON (DWD)</option><option value="&models=gfs_seamless" style="background:#1a2a3a;color:#fff">GFS (NOAA)</option><option value="&models=ecmwf_ifs025" style="background:#1a2a3a;color:#fff">ECMWF IFS</option><option value="&models=meteofrance_seamless" style="background:#1a2a3a;color:#fff">Météo-France</option><option value="&models=gem_seamless" style="background:#1a2a3a;color:#fff">GEM (Canada)</option><option value="&models=jma_seamless" style="background:#1a2a3a;color:#fff">JMA (Japan)</option>
1801
1801
  </select>
1802
1802
  <span id="meteo-model-desc" style="font-size:20px;color:#cde;font-weight:600;flex:1;min-width:200px;line-height:1.25;white-space:normal"></span>
1803
1803
  </div>
@@ -1806,7 +1806,7 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1806
1806
  como TEXTO HTML — se renderizaba tal cual hasta que fetchMeteo()
1807
1807
  reemplazaba el innerHTML. Ahora simplemente "Cargando…" en plano; JS
1808
1808
  lo sobreescribe en cuanto llega el primer fetch. -->
1809
- <div id="meteo-panel" style="overflow-y:auto;flex:1;padding:0"><div style="text-align:center;padding:20px;opacity:.5;font-size:15px">Cargando…</div></div>
1809
+ <div id="meteo-panel" style="overflow-y:auto;flex:1;padding:0"><div style="text-align:center;padding:20px;opacity:.5;font-size:15px" data-i18n="m_cargando">Cargando…</div></div>
1810
1810
  </div>
1811
1811
  </div>
1812
1812
  <div class="popup-overlay" id="ais-pop" onclick="closePopup('ais-pop')" style="background:transparent;pointer-events:none"><div class="popup-box" onclick="event.stopPropagation()" style="position:fixed;bottom:80px;left:10px;pointer-events:auto;width:321px;max-height:445px;overflow-y:auto;z-index:2001;padding:10px 12px;box-sizing:border-box"><button onclick="closePopup('ais-pop')" style="position:absolute;top:6px;right:8px;background:none;border:none;color:#aaa;font-size:20px;cursor:pointer;padding:2px 6px;line-height:1">✕</button><h4 id="ais-pop-title" style="margin-right:28px">🚢 Target AIS</h4><div id="ais-pop-actions"></div><div id="ais-pop-body"></div></div></div>
@@ -1814,7 +1814,7 @@ body.mobile-ui #shelter-hours .sh-hr{flex:0 0 60px!important}
1814
1814
  <div class="popup-box" onclick="event.stopPropagation()" style="width:520px;max-width:94vw;max-height:85vh;overflow-y:auto">
1815
1815
  <button onclick="closePopup('sonda-pop')" style="position:absolute;top:6px;right:10px;background:none;border:none;color:#aaa;font-size:22px;cursor:pointer;line-height:1">✕</button>
1816
1816
  <h4 style="font-size:18px" data-i18n="calc_sonda_title">📐 Cálculo de Sonda</h4>
1817
- <div id="sonda-content" style="margin-top:10px"><div style="text-align:center;padding:20px;opacity:.5">Cargando...</div></div>
1817
+ <div id="sonda-content" style="margin-top:10px"><div style="text-align:center;padding:20px;opacity:.5" data-i18n="m_cargando">Cargando…</div></div>
1818
1818
  </div>
1819
1819
  </div>
1820
1820
  <div class="popup-overlay" id="fondeo-pop" onclick="closePopup('fondeo-pop')">
@@ -2007,6 +2007,29 @@ var _i18n={
2007
2007
  m_calma:{es:'Calma',en:'Calm'},
2008
2008
  sh_detected:{es:'Detectado',en:'Detected'},
2009
2009
  sh_sectores:{es:'sectores abrigados',en:'sheltered sectors'},
2010
+ /* Rev313: tooltips bottom bar */
2011
+ bb_velocidad_tip:{es:'Velocidad sobre fondo',en:'Speed over ground'},
2012
+ bb_viento_tip:{es:'Viento real (veleta) · flecha apunta hacia DONDE VA (TO) · click → Meteo',en:'True wind (vane) · arrow points TO where it goes · click → Weather'},
2013
+ bb_sonda_tip:{es:'Profundidad bajo quilla · Sonda',en:'Depth under keel · Sounder'},
2014
+ bb_cadena_tip:{es:'Cadena recomendada',en:'Recommended chain'},
2015
+ bb_dist_tip:{es:'Distancia al ancla',en:'Distance to anchor'},
2016
+ bb_presion_tip:{es:'Presión barométrica · línea naranja = AHORA · click → Meteo',en:'Barometric pressure · orange line = NOW · click → Weather'},
2017
+ bb_abrigo_tip:{es:'Grado de abrigo · click → Abrigo',en:'Shelter grade · click → Shelter'},
2018
+ bb_calidad_tip:{es:'Calidad de abrigo (gauge) · click → Abrigo',en:'Shelter quality (gauge) · click → Shelter'},
2019
+ /* Rev313: tooltips sidebar derecha */
2020
+ sb_anchor_tip:{es:'Fondear / Levar',en:'Drop / Lift anchor'},
2021
+ sb_centrar_tip:{es:'Centrar mapa',en:'Center map'},
2022
+ sb_info_tip:{es:'Datos del fondeo',en:'Anchorage data'},
2023
+ sb_abrigo_tip:{es:'Previsión abrigo',en:'Shelter forecast'},
2024
+ sb_alarmas_tip:{es:'Panel de alarmas',en:'Alarms panel'},
2025
+ sb_fondeo_tip:{es:'Cálculo fondeo',en:'Anchorage calculator'},
2026
+ /* Rev313: modal guardar favorito */
2027
+ fav_guardar_title:{es:'❤ Guardar fondeo favorito',en:'❤ Save favourite anchorage'},
2028
+ fav_guardar_hint:{es:'Nombre del favorito (vacío = nombre automático por geolocalización):',en:'Favourite name (empty = auto name by geolocation):'},
2029
+ fav_placeholder:{es:'p.ej. Cangas - Massó',en:'e.g. Cangas - Massó'},
2030
+ m_cancelar:{es:'Cancelar',en:'Cancel'},
2031
+ m_guardar:{es:'Guardar',en:'Save'},
2032
+ meteo_modelo_auto:{es:'Mejor (automático)',en:'Best (automatic)'},
2010
2033
  otras_capas:{es:'Otras capas base',en:'Other base layers'},
2011
2034
  cartas_mbt:{es:'Cartas MBTiles',en:'MBTiles Charts'},
2012
2035
  cartas_sk:{es:'Cartas registradas (Signal K)',en:'Registered charts (Signal K)'},
@@ -2226,6 +2249,15 @@ function applyLang(){
2226
2249
  var k=el.getAttribute('data-i18n-html');
2227
2250
  var t=T(k);if(t)el.innerHTML=t;
2228
2251
  });
2252
+ /* Rev313: traducir atributos title (tooltips on hover) y placeholders. */
2253
+ document.querySelectorAll('[data-i18n-title]').forEach(function(el){
2254
+ var k=el.getAttribute('data-i18n-title');
2255
+ var t=T(k);if(t)el.setAttribute('title',t);
2256
+ });
2257
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){
2258
+ var k=el.getAttribute('data-i18n-placeholder');
2259
+ var t=T(k);if(t)el.setAttribute('placeholder',t);
2260
+ });
2229
2261
  /* Special elements */
2230
2262
  var bl=document.getElementById('ais-borneo-label');if(bl)bl.textContent=T('colision');
2231
2263
  /* Flag active state */
@@ -5092,7 +5124,11 @@ function _shelterRenderPopup(){
5092
5124
  if(typeof _boatWave.heightSigM==='number'&&_boatWave.heightSigM>=0.05){
5093
5125
  waveLblTxt=_boatWave.heightSigM.toFixed(1).replace('.',',')+' m'+(_wavePer?' '+_wavePer:'');
5094
5126
  }else{
5095
- var _waveIntShort={calma:'Cal',rizada:'Riz',moderada:'Mod',agitada:'Agi',fuerte:'Fue'}[_boatWave.intensity||'']||(_boatWave.intensity||'');
5127
+ /* Rev313: i18n abreviaturas intensidad ola. */
5128
+ var _waveIntMap=(_lang==='en')
5129
+ ? {calma:'Calm',rizada:'Rip',moderada:'Mod',agitada:'Rou',fuerte:'Str'}
5130
+ : {calma:'Cal',rizada:'Riz',moderada:'Mod',agitada:'Agi',fuerte:'Fue'};
5131
+ var _waveIntShort=_waveIntMap[_boatWave.intensity||'']||(_boatWave.intensity||'');
5096
5132
  waveLblTxt=_waveIntShort+(_wavePer?' '+_wavePer:'');
5097
5133
  }
5098
5134
  waveLblHalfW=Math.max(30, waveLblTxt.length*4+8);