+ ${alerts.map(a=>`
${a.level==='danger'?'🔴':'🟡'} ${a.title}: ${a.msg}
`).join('')}
+
`;
+}
+
+async function sendStormNotification(alerts){
+ const worst=alerts.find(a=>a.level==='danger')||alerts[0];
+ const title=worst.level==='danger'?'⚠ ALERTA CRÍTICO':'🟡 Aviso meteorológico';
+ const body=alerts.map(a=>`${a.title}: ${a.msg}`).join(' · ');
+ // Capacitor LocalNotifications
+ try{
+ const ln=window.Capacitor?.Plugins?.LocalNotifications;
+ if(ln){
+ await ln.schedule({notifications:[{
+ id:Math.floor(Math.random()*1e9),
+ title,body,smallIcon:'ic_stat_anchor',
+ }]});
+ return;
+ }
+ }catch(e){console.warn('storm notif',e.message)}
+ // Web Notifications API
+ if('Notification' in window){
+ if(Notification.permission==='granted'){
+ try{new Notification(title,{body,icon:'/icon.svg',tag:'storm-alert'})}catch{}
+ }else if(Notification.permission!=='denied'){
+ try{await Notification.requestPermission()}catch{}
+ }
+ }
+}
+
// Helpers de conversão de unidades
function uvToSpeedDir(u,v){
// u: leste-oeste (positivo = leste). v: norte-sul (positivo = norte).
@@ -5624,8 +5787,47 @@ function renderWeather(){
if(!el)return;
const d=weather.data;
if(!d){el.innerHTML='';return}
- if(d.provider==='windy')return renderWindyWeather(el,d);
- return renderOpenMeteoWeather(el,d);
+ if(d.provider==='windy')renderWindyWeather(el,d);
+ else renderOpenMeteoWeather(el,d);
+ // Anexa card de marés se tiver dados
+ appendTidesCard(el);
+}
+
+function appendTidesCard(weatherEl){
+ if(!weather.tides||!weather.tides.nextHigh)return;
+ const t=weather.tides;
+ const fmtTide=(p)=>{
+ if(!p)return '—';
+ const d=new Date(p.time);
+ const hh=d.getHours().toString().padStart(2,'0');
+ const mm=d.getMinutes().toString().padStart(2,'0');
+ const diffMin=Math.round((d.getTime()-Date.now())/60000);
+ const inH=diffMin>=60?`em ${Math.floor(diffMin/60)}h${(diffMin%60).toString().padStart(2,'0')}`:`em ${diffMin}min`;
+ return `${hh}:${mm} (${inH}) · ${p.height>=0?'+':''}${p.height.toFixed(2)}m`;
+ };
+ const tidesHtml=`
+