feat(ui): redesign Marine Pro Dark — bottom nav + dark navy + Inter v1.7.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run

Reskin completo baseado em pesquisa de Navionics/Windy/PredictWind/Garmin
ActiveCaptain. Mata o feel "magazine editorial vintage" e adota padrões
mobile-app modernos.

Mudanças visuais (CSS overlay v3 sem alterar HTML/JS de business):
- Paleta dark navy (#0d2538) + cyan accent (#06b6d4) + reservado red pra alarme
- Inter (sans-serif) substitui Fraunces (italic editorial)
- Tabular nums em todas as métricas (lat/lon/depth/speed)
- Cards modernos: border-radius 14px + shadows sutis + bg dark
- Header 50% mais compacto (sem compass mark, avatar maior + accent cyan)
- FAB reposicionado acima da bottom nav, gradient cyan
- Modais: bottom sheet no mobile com top corners rounded
- Form fields dark com focus glow cyan
- Buttons com border-radius modernos, primary = cyan filled

Novos componentes:
- Bottom navigation: 5 tabs com line icons (Início/Travessias/Pendências/
  Zonas/Mais), backdrop-filter blur, badge vermelho em pendências overdue
- Safety status bar (sticky abaixo do header): GPS dot + Anchor watch +
  Bateria. Pulsa amarelo se warn, vermelho se danger
- switchPanel() unifica top tabs (legacy) + bottom nav

Service worker bumped pra invalidar cache antigo automaticamente.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
PontualTech / Karlão 2026-04-28 09:00:29 -03:00
parent b57ba0da37
commit c7994167be
6 changed files with 945 additions and 9 deletions

View file

@ -15,7 +15,7 @@
<title>Diário de Bordo</title> <title>Diário de Bordo</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT@0,9..144,400..700,0..100;1,9..144,400..700,0..100&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg stroke='%230e2a3d' stroke-width='1' fill='none'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 2 L13.5 12 L12 22 L10.5 12 Z' fill='%230e2a3d'/%3E%3Cpath d='M2 12 L12 13.5 L22 12 L12 10.5 Z' fill='%23a07832'/%3E%3C/g%3E%3C/svg%3E"> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg stroke='%230e2a3d' stroke-width='1' fill='none'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 2 L13.5 12 L12 22 L10.5 12 Z' fill='%230e2a3d'/%3E%3Cpath d='M2 12 L12 13.5 L22 12 L12 10.5 Z' fill='%23a07832'/%3E%3C/g%3E%3C/svg%3E">
@ -1405,6 +1405,393 @@ header{
/* ── TOOLBAR refinada ── */ /* ── TOOLBAR refinada ── */
.toolbar{gap:10px; margin-bottom:18px} .toolbar{gap:10px; margin-bottom:18px}
/* ════════════════════════════════════════════════════════════════════
v3 — "MARINE PRO DARK" REDESIGN (override completo)
Bottom nav · Inter + Mono · Dark navy + cyan · Cards modernos
════════════════════════════════════════════════════════════════════ */
:root{
/* Tipografia — sem Fraunces editorial */
--f-display:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
--f-body:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
--f-mono:'JetBrains Mono','SF Mono','Consolas',monospace;
/* Paleta marine pro dark */
--m-bg:#0d2538; /* deep navy (canvas) */
--m-bg-2:#0f2a40; /* surface 1 */
--m-bg-3:#163a55; /* surface 2 (cards) */
--m-bg-4:#1d4a6b; /* surface elevated */
--m-border:rgba(255,255,255,.08);
--m-border-strong:rgba(255,255,255,.18);
--m-text:#e8f1f8; /* primary text */
--m-text-mid:#b3c5d6; /* secondary */
--m-text-soft:#7d97ad; /* tertiary */
--m-accent:#06b6d4; /* cyan accent */
--m-accent-2:#22d3ee; /* cyan brighter */
--m-accent-glow:rgba(6,182,212,.20);
--m-ok:#10b981; /* anchored / safe */
--m-warn:#f59e0b; /* drift / soon */
--m-danger:#ef4444; /* alarm */
--m-info:#3b82f6; /* info */
/* Spacing scale */
--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:24px;--sp-6:32px;--sp-7:48px;
/* Radius */
--m-r-sm:6px;--m-r-md:10px;--m-r-lg:14px;--m-r-xl:20px;--m-r-pill:9999px;
/* Shadows (subtle, marine) */
--m-sh-1:0 1px 2px rgba(0,0,0,.32);
--m-sh-2:0 2px 6px rgba(0,0,0,.28),0 1px 2px rgba(0,0,0,.24);
--m-sh-3:0 8px 20px rgba(0,0,0,.32),0 2px 6px rgba(0,0,0,.24);
}
html,body{
background:var(--m-bg);
color:var(--m-text);
font-family:var(--f-body);
font-feature-settings:'cv11','ss03';
font-size:15px;line-height:1.5;
}
body{
background-image:
radial-gradient(ellipse 800px 600px at top right,rgba(34,211,238,.04) 0%,transparent 70%),
radial-gradient(ellipse 700px 500px at bottom left,rgba(6,182,212,.03) 0%,transparent 70%),
linear-gradient(180deg,#0a1f30 0%,#0d2538 100%);
background-attachment:fixed;
padding-bottom:calc(76px + env(safe-area-inset-bottom));
}
/* Mata o italic editorial em TUDO */
.boat-name,.entry-title,.empty-title,.modal-head h3,
.entry-notes,.field textarea,.export-card-title,
.export-card-text,.field-hint,h1,h2,h3,h4,h5,
.gps-card,.anchor-card,.fleet-name,.welcome-tagline{
font-family:var(--f-body)!important;
font-style:normal!important;
font-variation-settings:normal!important;
}
/* ── HEADER COMPACTO ── */
header{
background:linear-gradient(180deg,var(--m-bg-2) 0%,var(--m-bg) 100%);
border-bottom:1px solid var(--m-border);
padding:max(10px,env(safe-area-inset-top)) 14px 10px;
box-shadow:var(--m-sh-1);
}
.header-row{gap:10px;max-width:760px}
.compass-mark{display:none} /* compact: avatar é suficiente */
.boat-tagline{
font-family:var(--f-mono);font-size:9px;color:var(--m-accent);
letter-spacing:.18em;opacity:.85;margin-bottom:1px;
}
.boat-name,.boat-selector{
font-family:var(--f-body)!important;font-style:normal!important;
font-size:17px!important;font-weight:600;letter-spacing:-.01em;
color:var(--m-text)!important;line-height:1.2;
}
.boat-meta{
font-family:var(--f-body);font-size:11px;color:var(--m-text-soft);
text-transform:none;letter-spacing:0;
}
.boat-edit-btn{display:none}
.boat-header-avatar{
width:38px;height:38px;border:2px solid var(--m-accent);
background:var(--m-bg-3);color:var(--m-accent);
box-shadow:0 0 0 3px var(--m-accent-glow);
}
/* ── SAFETY STATUS BAR (logo abaixo do header) ── */
.safety-bar{
display:flex;justify-content:space-between;align-items:center;
background:var(--m-bg-2);border-bottom:1px solid var(--m-border);
padding:6px 14px;font-family:var(--f-mono);font-size:10.5px;
color:var(--m-text-mid);letter-spacing:.04em;
position:sticky;top:0;z-index:39;
}
.safety-bar-item{display:inline-flex;align-items:center;gap:5px}
.safety-bar-dot{width:7px;height:7px;border-radius:50%;background:var(--m-text-soft)}
.safety-bar-dot.ok{background:var(--m-ok);box-shadow:0 0 6px var(--m-ok)}
.safety-bar-dot.warn{background:var(--m-warn);box-shadow:0 0 6px var(--m-warn);animation:pulseDot 1.5s infinite}
.safety-bar-dot.danger{background:var(--m-danger);box-shadow:0 0 8px var(--m-danger);animation:pulseDot .8s infinite}
@keyframes pulseDot{50%{opacity:.4}}
/* ── TOP TABS REMOVIDAS (substituídas pela bottom nav) ── */
.tabs{display:none!important}
/* ── CONTAINER ── */
.container{padding:14px 12px 20px;max-width:760px}
.panel{padding:0;background:transparent}
/* ── CARDS DARK MODERNOS ── */
.export-card,.gps-card,.anchor-card,.empty,.contact-card{
background:var(--m-bg-3);
border:1px solid var(--m-border);
border-radius:var(--m-r-lg);
box-shadow:var(--m-sh-2);
padding:16px;margin-bottom:12px;
color:var(--m-text);
}
.export-card-title{
color:var(--m-text)!important;font-weight:600;font-size:15px;
letter-spacing:-.005em;margin-bottom:6px;
}
.export-card-text{
color:var(--m-text-mid)!important;font-size:13px;line-height:1.5;
}
.gps-card,.anchor-card{
background:linear-gradient(165deg,var(--m-bg-3),var(--m-bg-2));
border:1px solid var(--m-border-strong);
}
.gps-card.idle{
background:var(--m-bg-3);
color:var(--m-text);
}
.gps-stats{gap:14px}
.gps-stat-value,.t-stat-value{
color:var(--m-accent)!important;
font-family:var(--f-mono);font-weight:700;
font-variant-numeric:tabular-nums;
}
.gps-stat-label,.t-stat-label{
color:var(--m-text-soft)!important;
font-size:9.5px;letter-spacing:.14em;
}
.entry{
background:var(--m-bg-3);
border:1px solid var(--m-border);
border-left:3px solid var(--m-accent);
border-radius:var(--m-r-md);
box-shadow:var(--m-sh-1);
padding:14px;margin-bottom:10px;
color:var(--m-text);
}
.entry-title{color:var(--m-text)!important;font-weight:600;font-size:15px}
.entry-meta{color:var(--m-text-mid)}
.entry.maint{border-left-color:var(--m-warn)}
.entry.pending{border-left-color:var(--m-warn)}
.entry.pending.overdue{border-left-color:var(--m-danger)}
.entry.pending.done{border-left-color:var(--m-ok);opacity:.7}
.entry-grid dt{color:var(--m-text-soft)}
.entry-grid dd{color:var(--m-text)}
.entry-notes{
background:var(--m-bg-2)!important;border-left-color:var(--m-accent)!important;
color:var(--m-text-mid)!important;border-radius:var(--m-r-sm);
font-style:normal!important;
}
/* ── STATS DASHBOARD ── */
.stats{gap:8px}
.stat{
background:var(--m-bg-3);
border:1px solid var(--m-border);
border-radius:var(--m-r-md);
padding:14px 12px;
position:relative;overflow:hidden;
}
.stat::before{
content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:linear-gradient(90deg,var(--m-accent),var(--m-accent-2));
opacity:.7;
}
.stat-value{
color:var(--m-accent)!important;
font-family:var(--f-mono);font-weight:700;
font-variant-numeric:tabular-nums;
font-size:24px;
}
.stat-label{
color:var(--m-text-soft)!important;
font-size:9.5px;letter-spacing:.14em;
}
.stat-sub{color:var(--m-text-mid)!important;font-style:normal!important;font-size:11px}
/* ── BUTTONS ── */
.btn{
background:var(--m-bg-3);border:1px solid var(--m-border-strong);
color:var(--m-text);font-family:var(--f-body);
font-size:14px;font-weight:500;
border-radius:var(--m-r-md);
min-height:44px;padding:11px 18px;
text-transform:none;letter-spacing:0;
transition:all .15s;
}
.btn:hover{background:var(--m-bg-4);border-color:var(--m-accent)}
.btn-primary,.btn-brass{
background:var(--m-accent);color:#001a25;border-color:var(--m-accent);
font-weight:600;
}
.btn-primary:hover,.btn-brass:hover{background:var(--m-accent-2);color:#001a25;border-color:var(--m-accent-2)}
.btn-danger{background:transparent;color:var(--m-danger);border-color:var(--m-danger)}
.btn-danger:hover{background:rgba(239,68,68,.1);color:var(--m-danger)}
.btn-block{width:100%}
.btn-sm{min-height:34px;padding:7px 14px;font-size:12.5px}
.btn-big{min-height:54px;padding:14px 22px;font-size:15px;font-weight:600}
/* ── FAB acima da bottom nav ── */
.fab{
bottom:calc(86px + env(safe-area-inset-bottom))!important;
right:18px!important;
background:linear-gradient(135deg,var(--m-accent),var(--m-accent-2))!important;
color:#001a25!important;
width:56px!important;height:56px!important;
box-shadow:0 6px 20px var(--m-accent-glow),0 2px 8px rgba(0,0,0,.5)!important;
}
.fab:hover{transform:scale(1.06) translateY(-2px)!important}
.fab svg{stroke:#001a25!important}
/* ── BOTTOM NAVIGATION ── */
.bottom-nav{
position:fixed;bottom:0;left:0;right:0;z-index:48;
background:var(--m-bg-2);
border-top:1px solid var(--m-border);
padding:6px 0 max(6px,env(safe-area-inset-bottom));
display:flex;justify-content:space-around;align-items:stretch;
backdrop-filter:blur(18px);
-webkit-backdrop-filter:blur(18px);
background:rgba(15,42,64,.92);
}
.bn-item{
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:3px;padding:6px 4px;
background:none;border:none;cursor:pointer;
color:var(--m-text-soft);
font-family:var(--f-body);font-size:10px;font-weight:500;
transition:color .15s;
min-height:54px;
}
.bn-item:hover{color:var(--m-text-mid)}
.bn-item.active{color:var(--m-accent)}
.bn-item svg{width:22px;height:22px;stroke-width:2;transition:stroke-width .15s}
.bn-item.active svg{stroke-width:2.4}
.bn-badge{
position:absolute;top:4px;right:50%;transform:translate(14px,0);
background:var(--m-danger);color:#fff;
font-family:var(--f-mono);font-size:9px;font-weight:700;
min-width:16px;height:16px;border-radius:8px;
display:flex;align-items:center;justify-content:center;padding:0 4px;
}
.bn-item{position:relative}
/* ── MODAIS dark ── */
.modal{
background:var(--m-bg-3);
color:var(--m-text);
border:1px solid var(--m-border-strong);
border-radius:var(--m-r-xl) var(--m-r-xl) 0 0;
border-top:1px solid var(--m-border-strong);
}
@media(min-width:600px){
.modal{border-radius:var(--m-r-xl);}
}
.modal-head{border-bottom:1px solid var(--m-border)}
.modal-head h3{color:var(--m-text);font-style:normal;font-weight:600;font-size:17px}
.modal-foot{border-top:1px solid var(--m-border);background:var(--m-bg-2)}
.modal-backdrop{background:rgba(0,0,0,.7);backdrop-filter:blur(6px)}
.icon-btn{color:var(--m-text-mid);background:transparent;border:none}
.icon-btn:hover{color:var(--m-text);background:var(--m-bg-2)}
/* ── FORM FIELDS dark ── */
.field-label{color:var(--m-text-soft);font-size:10px;letter-spacing:.12em;font-weight:600;text-transform:uppercase}
.field input,.field textarea,.field select{
background:var(--m-bg-2)!important;
border:1px solid var(--m-border-strong)!important;
color:var(--m-text)!important;
border-radius:var(--m-r-sm);
font-family:var(--f-body);font-style:normal!important;
font-size:15px;padding:11px 13px;
}
.field input:focus,.field textarea:focus,.field select:focus{
outline:none!important;
border-color:var(--m-accent)!important;
box-shadow:0 0 0 3px var(--m-accent-glow)!important;
background:var(--m-bg-3)!important;
}
.field-hint{color:var(--m-text-soft);font-style:normal!important}
/* ── SECTION HEADERS / TOOLBARS ── */
.section-header h2{color:var(--m-text-soft);text-transform:uppercase;letter-spacing:.12em;font-size:10.5px}
.toolbar{margin-bottom:14px}
/* ── EMPTY STATES ── */
.empty{text-align:center;padding:32px 20px;color:var(--m-text-soft)}
.empty-title{color:var(--m-text);font-weight:600;font-size:16px;font-style:normal!important}
.empty-text{color:var(--m-text-soft);font-size:13.5px;line-height:1.55}
/* ── SYNC INDICATOR ── */
.sync-indicator{font-size:10px;margin-left:5px}
/* ── WELCOME SCREEN dark ── */
.welcome-screen{background:linear-gradient(135deg,var(--m-bg) 0%,var(--m-bg-2) 50%,#06151f 100%)}
.welcome-card{
background:var(--m-bg-3);
border:1px solid var(--m-border-strong);
border-radius:var(--m-r-xl);
box-shadow:var(--m-sh-3);
color:var(--m-text);
}
.welcome-title{color:var(--m-text);font-style:normal;font-weight:700;font-size:24px}
.welcome-tagline{color:var(--m-text-mid);font-style:normal;font-size:14px}
.welcome-btn{
background:var(--m-bg-2);border:1px solid var(--m-border-strong);
color:var(--m-text);border-radius:var(--m-r-md);
font-family:var(--f-body);font-weight:500;
}
.welcome-btn:hover{background:var(--m-bg-4);border-color:var(--m-accent)}
.welcome-btn-google{background:#fff;color:#3c4043;border-color:#dadce0}
.welcome-btn-google:hover{background:#f1f3f4;color:#3c4043;border-color:#dadce0}
.welcome-btn-primary{background:var(--m-accent);color:#001a25;border-color:var(--m-accent)}
.welcome-btn-primary:hover{background:var(--m-accent-2);color:#001a25;border-color:var(--m-accent-2)}
.welcome-btn-text{color:var(--m-text-soft);background:transparent;border:none}
.welcome-tab{color:var(--m-text-soft);text-transform:uppercase;letter-spacing:.12em}
.welcome-tab.active{color:var(--m-accent);border-bottom-color:var(--m-accent)}
/* ── FLEET & ANCHOR CALC dark ── */
.fleet-item{background:var(--m-bg-2);border:1px solid var(--m-border)}
.fleet-item:hover{background:var(--m-bg-4);border-color:var(--m-accent)}
.fleet-item.active{border-left:3px solid var(--m-accent);background:var(--m-bg-4)}
.fleet-name{color:var(--m-text);font-style:normal;font-weight:600;font-size:15px}
.fleet-meta{color:var(--m-text-soft);text-transform:uppercase;letter-spacing:.06em}
.fleet-active-badge{background:var(--m-accent);color:#001a25;font-weight:700}
.fleet-edit-icon{background:transparent;border:1px solid var(--m-border-strong);color:var(--m-text-mid)}
.fleet-units-toggle{border:1px solid var(--m-border-strong);background:var(--m-bg-2)}
.fleet-units-toggle button{background:transparent;color:var(--m-text-mid)}
.fleet-units-toggle button.active{background:var(--m-accent);color:#001a25}
.anchor-calc{background:var(--m-bg-2);border:1px solid var(--m-border);border-left:3px solid var(--m-accent)}
.anchor-calc-head{color:var(--m-accent);font-weight:600}
.anchor-calc-stat{background:var(--m-bg-3);border:1px solid var(--m-border)}
.anchor-calc-stat-label{color:var(--m-text-soft)}
.anchor-calc-stat-value{color:var(--m-text);font-style:normal;font-weight:700;font-family:var(--f-mono);font-variant-numeric:tabular-nums}
.anchor-calc-stat-value.ok{color:var(--m-ok)}
.anchor-calc-stat-value.warn{color:var(--m-warn)}
.anchor-calc-stat-value.danger{color:var(--m-danger)}
.anchor-calc-advice{background:rgba(6,182,212,.08);border-left-color:var(--m-accent);color:var(--m-text-mid);font-style:normal}
/* ── BADGES & TAGS ── */
.badge{background:var(--m-danger);color:#fff;font-family:var(--f-mono);font-weight:700}
.pax-tag{background:var(--m-bg-3);border:1px solid var(--m-border);color:var(--m-text);border-radius:var(--m-r-pill)}
/* ── ALERTS ── */
.alert-overdue,.alert-danger{background:rgba(239,68,68,.08);border-left:3px solid var(--m-danger);color:var(--m-text)}
.alert-soon,.alert-warn{background:rgba(245,158,11,.08);border-left:3px solid var(--m-warn);color:var(--m-text)}
.alert-info{background:rgba(59,130,246,.08);border-left:3px solid var(--m-info);color:var(--m-text)}
/* ── TOAST ── */
.toast{
background:var(--m-bg-3);color:var(--m-text);
border:1px solid var(--m-border-strong);
border-radius:var(--m-r-md);
box-shadow:var(--m-sh-3);
font-family:var(--f-body);font-style:normal;font-weight:500;
}
/* ── pax/checklist/tags ajustes ── */
.checklist-card{background:var(--m-bg-3);border:1px solid var(--m-border);border-radius:var(--m-r-md);color:var(--m-text)}
.weather-widget{background:var(--m-bg-3)!important;border:1px solid var(--m-border)!important;color:var(--m-text)!important;border-radius:var(--m-r-md)}
/* ── SCROLLBAR thin estilo iOS ── */
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-thumb{background:var(--m-border-strong);border-radius:3px}
::-webkit-scrollbar-track{background:transparent}
</style> </style>
</head> </head>
<body> <body>
@ -1450,6 +1837,13 @@ header{
</div> </div>
</header> </header>
<!-- Safety status bar (sempre visível, marine pro) -->
<div class="safety-bar" id="safety-bar">
<span class="safety-bar-item"><span class="safety-bar-dot" id="sb-gps-dot"></span><span id="sb-gps">GPS aguardando</span></span>
<span class="safety-bar-item" id="sb-anchor-wrap" style="display:none"><span class="safety-bar-dot ok" id="sb-anchor-dot"></span><span id="sb-anchor">Fundeado</span></span>
<span class="safety-bar-item"><span id="sb-battery"></span></span>
</div>
<div class="container"> <div class="container">
<div class="tabs" role="tablist"> <div class="tabs" role="tablist">
@ -1951,6 +2345,31 @@ Hora: {HORA}</textarea>
</div> </div>
<button class="fab" id="fab" onclick="quickAdd()" title="Adicionar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button> <button class="fab" id="fab" onclick="quickAdd()" title="Adicionar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button>
<!-- Bottom Navigation (substitui top tabs) -->
<nav class="bottom-nav" id="bottom-nav">
<button class="bn-item active" data-panel="overview" onclick="switchPanel('overview')" aria-label="Início">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12l9-9 9 9"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/></svg>
<span>Início</span>
</button>
<button class="bn-item" data-panel="trips" onclick="switchPanel('trips')" aria-label="Travessias">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M2 17.5L12 22l10-4.5"/><path d="M2 12.5L12 17l10-4.5"/><path d="M12 2L2 7l10 5 10-5z"/></svg>
<span>Travessias</span>
</button>
<button class="bn-item" data-panel="pending" onclick="switchPanel('pending')" aria-label="Pendências">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
<span>Pendências</span>
<span class="bn-badge" id="bn-badge-pending" style="display:none">0</span>
</button>
<button class="bn-item" data-panel="zones" onclick="switchPanel('zones')" aria-label="Zonas">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<span>Zonas</span>
</button>
<button class="bn-item" data-panel="export" onclick="switchPanel('export')" aria-label="Mais">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
<span>Mais</span>
</button>
</nav>
<!-- Fleet Manager Modal --> <!-- Fleet Manager Modal -->
<div class="modal-backdrop" id="fleet-modal" onclick="if(event.target===this)closeModal('fleet-modal')"> <div class="modal-backdrop" id="fleet-modal" onclick="if(event.target===this)closeModal('fleet-modal')">
<div class="modal" style="max-width:520px"> <div class="modal" style="max-width:520px">
@ -2727,7 +3146,56 @@ function syncUnitsToggle(){
}); });
} }
document.querySelectorAll('.tab').forEach(t=>{t.addEventListener('click',()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));t.classList.add('active');document.getElementById('panel-'+t.dataset.panel).classList.add('active');document.getElementById('fab').style.display=['trips','maintenance','pending','zones'].includes(t.dataset.panel)?'flex':'none';if(t.dataset.panel==='export'){updateStorageInfo();bindCloudInputs();renderCloudStatus();renderShareList();bindWeatherInputs();renderAuthBox();refreshGoogleStatus()}if(t.dataset.panel==='pending'&&_gcalConnected&&Date.now()-_gcalLastPullAt>GCAL_PULL_INTERVAL_MS)googlePullNow();if(t.dataset.panel==='zones')renderZones();window.scrollTo(0,0)})}); function switchPanel(name){
document.querySelectorAll('.tab').forEach(x=>x.classList.toggle('active',x.dataset.panel===name));
document.querySelectorAll('.bn-item').forEach(x=>x.classList.toggle('active',x.dataset.panel===name));
document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));
const p=document.getElementById('panel-'+name);
if(p)p.classList.add('active');
// FAB visível em panels que têm "criar item"
document.getElementById('fab').style.display=['trips','maintenance','pending','zones','overview'].includes(name)?'flex':'none';
if(name==='export'){updateStorageInfo();bindCloudInputs();renderCloudStatus();renderShareList();bindWeatherInputs();renderAuthBox();refreshGoogleStatus()}
if(name==='pending'&&_gcalConnected&&Date.now()-_gcalLastPullAt>GCAL_PULL_INTERVAL_MS)googlePullNow();
if(name==='zones')renderZones();
window.scrollTo(0,0);
}
// Compat: top tabs (escondidos via CSS mas mantém handlers caso re-exibidos)
document.querySelectorAll('.tab').forEach(t=>{t.addEventListener('click',()=>switchPanel(t.dataset.panel))});
// Safety bar: atualiza bateria, GPS, anchor a cada 10s
function updateSafetyBar(){
const bat=document.getElementById('battery-indicator');
const sbBat=document.getElementById('sb-battery');
if(sbBat&&bat){sbBat.textContent=bat.textContent.trim()||'—'}
const sbGps=document.getElementById('sb-gps');
const sbGpsDot=document.getElementById('sb-gps-dot');
if(sbGps&&sbGpsDot){
if(tracking?.active||(typeof lastGpsPos!=='undefined'&&lastGpsPos)){
sbGps.textContent='GPS ativo';sbGpsDot.className='safety-bar-dot ok';
}else{
sbGps.textContent='GPS aguardando';sbGpsDot.className='safety-bar-dot';
}
}
const sbAnchorWrap=document.getElementById('sb-anchor-wrap');
const sbAnchorDot=document.getElementById('sb-anchor-dot');
const sbAnchor=document.getElementById('sb-anchor');
if(sbAnchorWrap&&anchorWatch){
if(anchorWatch.active){
sbAnchorWrap.style.display='inline-flex';
const breach=anchorWatch.currentDist>anchorWatch.radius;
sbAnchorDot.className='safety-bar-dot '+(breach?'danger':'ok');
sbAnchor.textContent=breach?'⚠ DERIVANDO!':`Fundeado · raio ${Math.round(anchorWatch.currentDist||0)}/${anchorWatch.radius}m`;
}else{
sbAnchorWrap.style.display='none';
}
}
// Bottom nav badge pendências
const overdue=(state.pending||[]).filter(p=>!p.done&&p.dueDate&&p.dueDate<new Date().toISOString().slice(0,10)).length;
const bnBadge=document.getElementById('bn-badge-pending');
if(bnBadge){bnBadge.style.display=overdue>0?'flex':'none';bnBadge.textContent=overdue}
}
setInterval(updateSafetyBar,5000);
setTimeout(updateSafetyBar,1500);
function quickAdd(){const a=document.querySelector('.tab.active').dataset.panel;if(a==='maintenance')openMaintModal();else if(a==='pending')openPendingModal();else if(a==='zones')openZoneEditor();else openTripModal()} function quickAdd(){const a=document.querySelector('.tab.active').dataset.panel;if(a==='maintenance')openMaintModal();else if(a==='pending')openPendingModal();else if(a==='zones')openZoneEditor();else openTripModal()}
function openModal(id){document.getElementById(id).classList.add('show')} function openModal(id){document.getElementById(id).classList.add('show')}
function closeModal(id){document.getElementById(id).classList.remove('show')} function closeModal(id){document.getElementById(id).classList.remove('show')}

View file

@ -7,8 +7,8 @@ android {
applicationId "br.com.pontualtech.shivao" applicationId "br.com.pontualtech.shivao"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 9 versionCode 10
versionName "1.6.2" versionName "1.7.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View file

@ -1,6 +1,6 @@
{ {
"name": "shivao-mobile", "name": "shivao-mobile",
"version": "1.6.2", "version": "1.7.0",
"description": "Shivao app nativo (Capacitor wrapper Android/iOS)", "description": "Shivao app nativo (Capacitor wrapper Android/iOS)",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View file

@ -15,7 +15,7 @@
<title>Diário de Bordo</title> <title>Diário de Bordo</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT@0,9..144,400..700,0..100;1,9..144,400..700,0..100&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg stroke='%230e2a3d' stroke-width='1' fill='none'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 2 L13.5 12 L12 22 L10.5 12 Z' fill='%230e2a3d'/%3E%3Cpath d='M2 12 L12 13.5 L22 12 L12 10.5 Z' fill='%23a07832'/%3E%3C/g%3E%3C/svg%3E"> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg stroke='%230e2a3d' stroke-width='1' fill='none'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 2 L13.5 12 L12 22 L10.5 12 Z' fill='%230e2a3d'/%3E%3Cpath d='M2 12 L12 13.5 L22 12 L12 10.5 Z' fill='%23a07832'/%3E%3C/g%3E%3C/svg%3E">
@ -1405,6 +1405,393 @@ header{
/* ── TOOLBAR refinada ── */ /* ── TOOLBAR refinada ── */
.toolbar{gap:10px; margin-bottom:18px} .toolbar{gap:10px; margin-bottom:18px}
/* ════════════════════════════════════════════════════════════════════
v3 — "MARINE PRO DARK" REDESIGN (override completo)
Bottom nav · Inter + Mono · Dark navy + cyan · Cards modernos
════════════════════════════════════════════════════════════════════ */
:root{
/* Tipografia — sem Fraunces editorial */
--f-display:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
--f-body:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
--f-mono:'JetBrains Mono','SF Mono','Consolas',monospace;
/* Paleta marine pro dark */
--m-bg:#0d2538; /* deep navy (canvas) */
--m-bg-2:#0f2a40; /* surface 1 */
--m-bg-3:#163a55; /* surface 2 (cards) */
--m-bg-4:#1d4a6b; /* surface elevated */
--m-border:rgba(255,255,255,.08);
--m-border-strong:rgba(255,255,255,.18);
--m-text:#e8f1f8; /* primary text */
--m-text-mid:#b3c5d6; /* secondary */
--m-text-soft:#7d97ad; /* tertiary */
--m-accent:#06b6d4; /* cyan accent */
--m-accent-2:#22d3ee; /* cyan brighter */
--m-accent-glow:rgba(6,182,212,.20);
--m-ok:#10b981; /* anchored / safe */
--m-warn:#f59e0b; /* drift / soon */
--m-danger:#ef4444; /* alarm */
--m-info:#3b82f6; /* info */
/* Spacing scale */
--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:24px;--sp-6:32px;--sp-7:48px;
/* Radius */
--m-r-sm:6px;--m-r-md:10px;--m-r-lg:14px;--m-r-xl:20px;--m-r-pill:9999px;
/* Shadows (subtle, marine) */
--m-sh-1:0 1px 2px rgba(0,0,0,.32);
--m-sh-2:0 2px 6px rgba(0,0,0,.28),0 1px 2px rgba(0,0,0,.24);
--m-sh-3:0 8px 20px rgba(0,0,0,.32),0 2px 6px rgba(0,0,0,.24);
}
html,body{
background:var(--m-bg);
color:var(--m-text);
font-family:var(--f-body);
font-feature-settings:'cv11','ss03';
font-size:15px;line-height:1.5;
}
body{
background-image:
radial-gradient(ellipse 800px 600px at top right,rgba(34,211,238,.04) 0%,transparent 70%),
radial-gradient(ellipse 700px 500px at bottom left,rgba(6,182,212,.03) 0%,transparent 70%),
linear-gradient(180deg,#0a1f30 0%,#0d2538 100%);
background-attachment:fixed;
padding-bottom:calc(76px + env(safe-area-inset-bottom));
}
/* Mata o italic editorial em TUDO */
.boat-name,.entry-title,.empty-title,.modal-head h3,
.entry-notes,.field textarea,.export-card-title,
.export-card-text,.field-hint,h1,h2,h3,h4,h5,
.gps-card,.anchor-card,.fleet-name,.welcome-tagline{
font-family:var(--f-body)!important;
font-style:normal!important;
font-variation-settings:normal!important;
}
/* ── HEADER COMPACTO ── */
header{
background:linear-gradient(180deg,var(--m-bg-2) 0%,var(--m-bg) 100%);
border-bottom:1px solid var(--m-border);
padding:max(10px,env(safe-area-inset-top)) 14px 10px;
box-shadow:var(--m-sh-1);
}
.header-row{gap:10px;max-width:760px}
.compass-mark{display:none} /* compact: avatar é suficiente */
.boat-tagline{
font-family:var(--f-mono);font-size:9px;color:var(--m-accent);
letter-spacing:.18em;opacity:.85;margin-bottom:1px;
}
.boat-name,.boat-selector{
font-family:var(--f-body)!important;font-style:normal!important;
font-size:17px!important;font-weight:600;letter-spacing:-.01em;
color:var(--m-text)!important;line-height:1.2;
}
.boat-meta{
font-family:var(--f-body);font-size:11px;color:var(--m-text-soft);
text-transform:none;letter-spacing:0;
}
.boat-edit-btn{display:none}
.boat-header-avatar{
width:38px;height:38px;border:2px solid var(--m-accent);
background:var(--m-bg-3);color:var(--m-accent);
box-shadow:0 0 0 3px var(--m-accent-glow);
}
/* ── SAFETY STATUS BAR (logo abaixo do header) ── */
.safety-bar{
display:flex;justify-content:space-between;align-items:center;
background:var(--m-bg-2);border-bottom:1px solid var(--m-border);
padding:6px 14px;font-family:var(--f-mono);font-size:10.5px;
color:var(--m-text-mid);letter-spacing:.04em;
position:sticky;top:0;z-index:39;
}
.safety-bar-item{display:inline-flex;align-items:center;gap:5px}
.safety-bar-dot{width:7px;height:7px;border-radius:50%;background:var(--m-text-soft)}
.safety-bar-dot.ok{background:var(--m-ok);box-shadow:0 0 6px var(--m-ok)}
.safety-bar-dot.warn{background:var(--m-warn);box-shadow:0 0 6px var(--m-warn);animation:pulseDot 1.5s infinite}
.safety-bar-dot.danger{background:var(--m-danger);box-shadow:0 0 8px var(--m-danger);animation:pulseDot .8s infinite}
@keyframes pulseDot{50%{opacity:.4}}
/* ── TOP TABS REMOVIDAS (substituídas pela bottom nav) ── */
.tabs{display:none!important}
/* ── CONTAINER ── */
.container{padding:14px 12px 20px;max-width:760px}
.panel{padding:0;background:transparent}
/* ── CARDS DARK MODERNOS ── */
.export-card,.gps-card,.anchor-card,.empty,.contact-card{
background:var(--m-bg-3);
border:1px solid var(--m-border);
border-radius:var(--m-r-lg);
box-shadow:var(--m-sh-2);
padding:16px;margin-bottom:12px;
color:var(--m-text);
}
.export-card-title{
color:var(--m-text)!important;font-weight:600;font-size:15px;
letter-spacing:-.005em;margin-bottom:6px;
}
.export-card-text{
color:var(--m-text-mid)!important;font-size:13px;line-height:1.5;
}
.gps-card,.anchor-card{
background:linear-gradient(165deg,var(--m-bg-3),var(--m-bg-2));
border:1px solid var(--m-border-strong);
}
.gps-card.idle{
background:var(--m-bg-3);
color:var(--m-text);
}
.gps-stats{gap:14px}
.gps-stat-value,.t-stat-value{
color:var(--m-accent)!important;
font-family:var(--f-mono);font-weight:700;
font-variant-numeric:tabular-nums;
}
.gps-stat-label,.t-stat-label{
color:var(--m-text-soft)!important;
font-size:9.5px;letter-spacing:.14em;
}
.entry{
background:var(--m-bg-3);
border:1px solid var(--m-border);
border-left:3px solid var(--m-accent);
border-radius:var(--m-r-md);
box-shadow:var(--m-sh-1);
padding:14px;margin-bottom:10px;
color:var(--m-text);
}
.entry-title{color:var(--m-text)!important;font-weight:600;font-size:15px}
.entry-meta{color:var(--m-text-mid)}
.entry.maint{border-left-color:var(--m-warn)}
.entry.pending{border-left-color:var(--m-warn)}
.entry.pending.overdue{border-left-color:var(--m-danger)}
.entry.pending.done{border-left-color:var(--m-ok);opacity:.7}
.entry-grid dt{color:var(--m-text-soft)}
.entry-grid dd{color:var(--m-text)}
.entry-notes{
background:var(--m-bg-2)!important;border-left-color:var(--m-accent)!important;
color:var(--m-text-mid)!important;border-radius:var(--m-r-sm);
font-style:normal!important;
}
/* ── STATS DASHBOARD ── */
.stats{gap:8px}
.stat{
background:var(--m-bg-3);
border:1px solid var(--m-border);
border-radius:var(--m-r-md);
padding:14px 12px;
position:relative;overflow:hidden;
}
.stat::before{
content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:linear-gradient(90deg,var(--m-accent),var(--m-accent-2));
opacity:.7;
}
.stat-value{
color:var(--m-accent)!important;
font-family:var(--f-mono);font-weight:700;
font-variant-numeric:tabular-nums;
font-size:24px;
}
.stat-label{
color:var(--m-text-soft)!important;
font-size:9.5px;letter-spacing:.14em;
}
.stat-sub{color:var(--m-text-mid)!important;font-style:normal!important;font-size:11px}
/* ── BUTTONS ── */
.btn{
background:var(--m-bg-3);border:1px solid var(--m-border-strong);
color:var(--m-text);font-family:var(--f-body);
font-size:14px;font-weight:500;
border-radius:var(--m-r-md);
min-height:44px;padding:11px 18px;
text-transform:none;letter-spacing:0;
transition:all .15s;
}
.btn:hover{background:var(--m-bg-4);border-color:var(--m-accent)}
.btn-primary,.btn-brass{
background:var(--m-accent);color:#001a25;border-color:var(--m-accent);
font-weight:600;
}
.btn-primary:hover,.btn-brass:hover{background:var(--m-accent-2);color:#001a25;border-color:var(--m-accent-2)}
.btn-danger{background:transparent;color:var(--m-danger);border-color:var(--m-danger)}
.btn-danger:hover{background:rgba(239,68,68,.1);color:var(--m-danger)}
.btn-block{width:100%}
.btn-sm{min-height:34px;padding:7px 14px;font-size:12.5px}
.btn-big{min-height:54px;padding:14px 22px;font-size:15px;font-weight:600}
/* ── FAB acima da bottom nav ── */
.fab{
bottom:calc(86px + env(safe-area-inset-bottom))!important;
right:18px!important;
background:linear-gradient(135deg,var(--m-accent),var(--m-accent-2))!important;
color:#001a25!important;
width:56px!important;height:56px!important;
box-shadow:0 6px 20px var(--m-accent-glow),0 2px 8px rgba(0,0,0,.5)!important;
}
.fab:hover{transform:scale(1.06) translateY(-2px)!important}
.fab svg{stroke:#001a25!important}
/* ── BOTTOM NAVIGATION ── */
.bottom-nav{
position:fixed;bottom:0;left:0;right:0;z-index:48;
background:var(--m-bg-2);
border-top:1px solid var(--m-border);
padding:6px 0 max(6px,env(safe-area-inset-bottom));
display:flex;justify-content:space-around;align-items:stretch;
backdrop-filter:blur(18px);
-webkit-backdrop-filter:blur(18px);
background:rgba(15,42,64,.92);
}
.bn-item{
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:3px;padding:6px 4px;
background:none;border:none;cursor:pointer;
color:var(--m-text-soft);
font-family:var(--f-body);font-size:10px;font-weight:500;
transition:color .15s;
min-height:54px;
}
.bn-item:hover{color:var(--m-text-mid)}
.bn-item.active{color:var(--m-accent)}
.bn-item svg{width:22px;height:22px;stroke-width:2;transition:stroke-width .15s}
.bn-item.active svg{stroke-width:2.4}
.bn-badge{
position:absolute;top:4px;right:50%;transform:translate(14px,0);
background:var(--m-danger);color:#fff;
font-family:var(--f-mono);font-size:9px;font-weight:700;
min-width:16px;height:16px;border-radius:8px;
display:flex;align-items:center;justify-content:center;padding:0 4px;
}
.bn-item{position:relative}
/* ── MODAIS dark ── */
.modal{
background:var(--m-bg-3);
color:var(--m-text);
border:1px solid var(--m-border-strong);
border-radius:var(--m-r-xl) var(--m-r-xl) 0 0;
border-top:1px solid var(--m-border-strong);
}
@media(min-width:600px){
.modal{border-radius:var(--m-r-xl);}
}
.modal-head{border-bottom:1px solid var(--m-border)}
.modal-head h3{color:var(--m-text);font-style:normal;font-weight:600;font-size:17px}
.modal-foot{border-top:1px solid var(--m-border);background:var(--m-bg-2)}
.modal-backdrop{background:rgba(0,0,0,.7);backdrop-filter:blur(6px)}
.icon-btn{color:var(--m-text-mid);background:transparent;border:none}
.icon-btn:hover{color:var(--m-text);background:var(--m-bg-2)}
/* ── FORM FIELDS dark ── */
.field-label{color:var(--m-text-soft);font-size:10px;letter-spacing:.12em;font-weight:600;text-transform:uppercase}
.field input,.field textarea,.field select{
background:var(--m-bg-2)!important;
border:1px solid var(--m-border-strong)!important;
color:var(--m-text)!important;
border-radius:var(--m-r-sm);
font-family:var(--f-body);font-style:normal!important;
font-size:15px;padding:11px 13px;
}
.field input:focus,.field textarea:focus,.field select:focus{
outline:none!important;
border-color:var(--m-accent)!important;
box-shadow:0 0 0 3px var(--m-accent-glow)!important;
background:var(--m-bg-3)!important;
}
.field-hint{color:var(--m-text-soft);font-style:normal!important}
/* ── SECTION HEADERS / TOOLBARS ── */
.section-header h2{color:var(--m-text-soft);text-transform:uppercase;letter-spacing:.12em;font-size:10.5px}
.toolbar{margin-bottom:14px}
/* ── EMPTY STATES ── */
.empty{text-align:center;padding:32px 20px;color:var(--m-text-soft)}
.empty-title{color:var(--m-text);font-weight:600;font-size:16px;font-style:normal!important}
.empty-text{color:var(--m-text-soft);font-size:13.5px;line-height:1.55}
/* ── SYNC INDICATOR ── */
.sync-indicator{font-size:10px;margin-left:5px}
/* ── WELCOME SCREEN dark ── */
.welcome-screen{background:linear-gradient(135deg,var(--m-bg) 0%,var(--m-bg-2) 50%,#06151f 100%)}
.welcome-card{
background:var(--m-bg-3);
border:1px solid var(--m-border-strong);
border-radius:var(--m-r-xl);
box-shadow:var(--m-sh-3);
color:var(--m-text);
}
.welcome-title{color:var(--m-text);font-style:normal;font-weight:700;font-size:24px}
.welcome-tagline{color:var(--m-text-mid);font-style:normal;font-size:14px}
.welcome-btn{
background:var(--m-bg-2);border:1px solid var(--m-border-strong);
color:var(--m-text);border-radius:var(--m-r-md);
font-family:var(--f-body);font-weight:500;
}
.welcome-btn:hover{background:var(--m-bg-4);border-color:var(--m-accent)}
.welcome-btn-google{background:#fff;color:#3c4043;border-color:#dadce0}
.welcome-btn-google:hover{background:#f1f3f4;color:#3c4043;border-color:#dadce0}
.welcome-btn-primary{background:var(--m-accent);color:#001a25;border-color:var(--m-accent)}
.welcome-btn-primary:hover{background:var(--m-accent-2);color:#001a25;border-color:var(--m-accent-2)}
.welcome-btn-text{color:var(--m-text-soft);background:transparent;border:none}
.welcome-tab{color:var(--m-text-soft);text-transform:uppercase;letter-spacing:.12em}
.welcome-tab.active{color:var(--m-accent);border-bottom-color:var(--m-accent)}
/* ── FLEET & ANCHOR CALC dark ── */
.fleet-item{background:var(--m-bg-2);border:1px solid var(--m-border)}
.fleet-item:hover{background:var(--m-bg-4);border-color:var(--m-accent)}
.fleet-item.active{border-left:3px solid var(--m-accent);background:var(--m-bg-4)}
.fleet-name{color:var(--m-text);font-style:normal;font-weight:600;font-size:15px}
.fleet-meta{color:var(--m-text-soft);text-transform:uppercase;letter-spacing:.06em}
.fleet-active-badge{background:var(--m-accent);color:#001a25;font-weight:700}
.fleet-edit-icon{background:transparent;border:1px solid var(--m-border-strong);color:var(--m-text-mid)}
.fleet-units-toggle{border:1px solid var(--m-border-strong);background:var(--m-bg-2)}
.fleet-units-toggle button{background:transparent;color:var(--m-text-mid)}
.fleet-units-toggle button.active{background:var(--m-accent);color:#001a25}
.anchor-calc{background:var(--m-bg-2);border:1px solid var(--m-border);border-left:3px solid var(--m-accent)}
.anchor-calc-head{color:var(--m-accent);font-weight:600}
.anchor-calc-stat{background:var(--m-bg-3);border:1px solid var(--m-border)}
.anchor-calc-stat-label{color:var(--m-text-soft)}
.anchor-calc-stat-value{color:var(--m-text);font-style:normal;font-weight:700;font-family:var(--f-mono);font-variant-numeric:tabular-nums}
.anchor-calc-stat-value.ok{color:var(--m-ok)}
.anchor-calc-stat-value.warn{color:var(--m-warn)}
.anchor-calc-stat-value.danger{color:var(--m-danger)}
.anchor-calc-advice{background:rgba(6,182,212,.08);border-left-color:var(--m-accent);color:var(--m-text-mid);font-style:normal}
/* ── BADGES & TAGS ── */
.badge{background:var(--m-danger);color:#fff;font-family:var(--f-mono);font-weight:700}
.pax-tag{background:var(--m-bg-3);border:1px solid var(--m-border);color:var(--m-text);border-radius:var(--m-r-pill)}
/* ── ALERTS ── */
.alert-overdue,.alert-danger{background:rgba(239,68,68,.08);border-left:3px solid var(--m-danger);color:var(--m-text)}
.alert-soon,.alert-warn{background:rgba(245,158,11,.08);border-left:3px solid var(--m-warn);color:var(--m-text)}
.alert-info{background:rgba(59,130,246,.08);border-left:3px solid var(--m-info);color:var(--m-text)}
/* ── TOAST ── */
.toast{
background:var(--m-bg-3);color:var(--m-text);
border:1px solid var(--m-border-strong);
border-radius:var(--m-r-md);
box-shadow:var(--m-sh-3);
font-family:var(--f-body);font-style:normal;font-weight:500;
}
/* ── pax/checklist/tags ajustes ── */
.checklist-card{background:var(--m-bg-3);border:1px solid var(--m-border);border-radius:var(--m-r-md);color:var(--m-text)}
.weather-widget{background:var(--m-bg-3)!important;border:1px solid var(--m-border)!important;color:var(--m-text)!important;border-radius:var(--m-r-md)}
/* ── SCROLLBAR thin estilo iOS ── */
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-thumb{background:var(--m-border-strong);border-radius:3px}
::-webkit-scrollbar-track{background:transparent}
</style> </style>
</head> </head>
<body> <body>
@ -1450,6 +1837,13 @@ header{
</div> </div>
</header> </header>
<!-- Safety status bar (sempre visível, marine pro) -->
<div class="safety-bar" id="safety-bar">
<span class="safety-bar-item"><span class="safety-bar-dot" id="sb-gps-dot"></span><span id="sb-gps">GPS aguardando</span></span>
<span class="safety-bar-item" id="sb-anchor-wrap" style="display:none"><span class="safety-bar-dot ok" id="sb-anchor-dot"></span><span id="sb-anchor">Fundeado</span></span>
<span class="safety-bar-item"><span id="sb-battery"></span></span>
</div>
<div class="container"> <div class="container">
<div class="tabs" role="tablist"> <div class="tabs" role="tablist">
@ -1951,6 +2345,31 @@ Hora: {HORA}</textarea>
</div> </div>
<button class="fab" id="fab" onclick="quickAdd()" title="Adicionar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button> <button class="fab" id="fab" onclick="quickAdd()" title="Adicionar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button>
<!-- Bottom Navigation (substitui top tabs) -->
<nav class="bottom-nav" id="bottom-nav">
<button class="bn-item active" data-panel="overview" onclick="switchPanel('overview')" aria-label="Início">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12l9-9 9 9"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/></svg>
<span>Início</span>
</button>
<button class="bn-item" data-panel="trips" onclick="switchPanel('trips')" aria-label="Travessias">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M2 17.5L12 22l10-4.5"/><path d="M2 12.5L12 17l10-4.5"/><path d="M12 2L2 7l10 5 10-5z"/></svg>
<span>Travessias</span>
</button>
<button class="bn-item" data-panel="pending" onclick="switchPanel('pending')" aria-label="Pendências">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
<span>Pendências</span>
<span class="bn-badge" id="bn-badge-pending" style="display:none">0</span>
</button>
<button class="bn-item" data-panel="zones" onclick="switchPanel('zones')" aria-label="Zonas">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<span>Zonas</span>
</button>
<button class="bn-item" data-panel="export" onclick="switchPanel('export')" aria-label="Mais">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
<span>Mais</span>
</button>
</nav>
<!-- Fleet Manager Modal --> <!-- Fleet Manager Modal -->
<div class="modal-backdrop" id="fleet-modal" onclick="if(event.target===this)closeModal('fleet-modal')"> <div class="modal-backdrop" id="fleet-modal" onclick="if(event.target===this)closeModal('fleet-modal')">
<div class="modal" style="max-width:520px"> <div class="modal" style="max-width:520px">
@ -2727,7 +3146,56 @@ function syncUnitsToggle(){
}); });
} }
document.querySelectorAll('.tab').forEach(t=>{t.addEventListener('click',()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));t.classList.add('active');document.getElementById('panel-'+t.dataset.panel).classList.add('active');document.getElementById('fab').style.display=['trips','maintenance','pending','zones'].includes(t.dataset.panel)?'flex':'none';if(t.dataset.panel==='export'){updateStorageInfo();bindCloudInputs();renderCloudStatus();renderShareList();bindWeatherInputs();renderAuthBox();refreshGoogleStatus()}if(t.dataset.panel==='pending'&&_gcalConnected&&Date.now()-_gcalLastPullAt>GCAL_PULL_INTERVAL_MS)googlePullNow();if(t.dataset.panel==='zones')renderZones();window.scrollTo(0,0)})}); function switchPanel(name){
document.querySelectorAll('.tab').forEach(x=>x.classList.toggle('active',x.dataset.panel===name));
document.querySelectorAll('.bn-item').forEach(x=>x.classList.toggle('active',x.dataset.panel===name));
document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));
const p=document.getElementById('panel-'+name);
if(p)p.classList.add('active');
// FAB visível em panels que têm "criar item"
document.getElementById('fab').style.display=['trips','maintenance','pending','zones','overview'].includes(name)?'flex':'none';
if(name==='export'){updateStorageInfo();bindCloudInputs();renderCloudStatus();renderShareList();bindWeatherInputs();renderAuthBox();refreshGoogleStatus()}
if(name==='pending'&&_gcalConnected&&Date.now()-_gcalLastPullAt>GCAL_PULL_INTERVAL_MS)googlePullNow();
if(name==='zones')renderZones();
window.scrollTo(0,0);
}
// Compat: top tabs (escondidos via CSS mas mantém handlers caso re-exibidos)
document.querySelectorAll('.tab').forEach(t=>{t.addEventListener('click',()=>switchPanel(t.dataset.panel))});
// Safety bar: atualiza bateria, GPS, anchor a cada 10s
function updateSafetyBar(){
const bat=document.getElementById('battery-indicator');
const sbBat=document.getElementById('sb-battery');
if(sbBat&&bat){sbBat.textContent=bat.textContent.trim()||'—'}
const sbGps=document.getElementById('sb-gps');
const sbGpsDot=document.getElementById('sb-gps-dot');
if(sbGps&&sbGpsDot){
if(tracking?.active||(typeof lastGpsPos!=='undefined'&&lastGpsPos)){
sbGps.textContent='GPS ativo';sbGpsDot.className='safety-bar-dot ok';
}else{
sbGps.textContent='GPS aguardando';sbGpsDot.className='safety-bar-dot';
}
}
const sbAnchorWrap=document.getElementById('sb-anchor-wrap');
const sbAnchorDot=document.getElementById('sb-anchor-dot');
const sbAnchor=document.getElementById('sb-anchor');
if(sbAnchorWrap&&anchorWatch){
if(anchorWatch.active){
sbAnchorWrap.style.display='inline-flex';
const breach=anchorWatch.currentDist>anchorWatch.radius;
sbAnchorDot.className='safety-bar-dot '+(breach?'danger':'ok');
sbAnchor.textContent=breach?'⚠ DERIVANDO!':`Fundeado · raio ${Math.round(anchorWatch.currentDist||0)}/${anchorWatch.radius}m`;
}else{
sbAnchorWrap.style.display='none';
}
}
// Bottom nav badge pendências
const overdue=(state.pending||[]).filter(p=>!p.done&&p.dueDate&&p.dueDate<new Date().toISOString().slice(0,10)).length;
const bnBadge=document.getElementById('bn-badge-pending');
if(bnBadge){bnBadge.style.display=overdue>0?'flex':'none';bnBadge.textContent=overdue}
}
setInterval(updateSafetyBar,5000);
setTimeout(updateSafetyBar,1500);
function quickAdd(){const a=document.querySelector('.tab.active').dataset.panel;if(a==='maintenance')openMaintModal();else if(a==='pending')openPendingModal();else if(a==='zones')openZoneEditor();else openTripModal()} function quickAdd(){const a=document.querySelector('.tab.active').dataset.panel;if(a==='maintenance')openMaintModal();else if(a==='pending')openPendingModal();else if(a==='zones')openZoneEditor();else openTripModal()}
function openModal(id){document.getElementById(id).classList.add('show')} function openModal(id){document.getElementById(id).classList.add('show')}
function closeModal(id){document.getElementById(id).classList.remove('show')} function closeModal(id){document.getElementById(id).classList.remove('show')}

View file

@ -1,7 +1,7 @@
// Shivao Service Worker — offline real // Shivao Service Worker — offline real
// Estratégia: shell precachado, tiles cache-first, windy network-first, /api passa direto. // Estratégia: shell precachado, tiles cache-first, windy network-first, /api passa direto.
// Versão usada nos cache names — bumpa essa string pra invalidar caches antigos em deploys. // Versão usada nos cache names — bumpa essa string pra invalidar caches antigos em deploys.
const VERSION = 'shivao-v1.6.0'; const VERSION = 'shivao-v1.7.0';
const SHELL_CACHE = `shivao-shell-${VERSION}`; const SHELL_CACHE = `shivao-shell-${VERSION}`;
const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell
const WINDY_CACHE = `shivao-windy-${VERSION}`; const WINDY_CACHE = `shivao-windy-${VERSION}`;

View file

@ -347,7 +347,7 @@ app.get('/.well-known/assetlinks.json', (req, res) => {
}); });
// Atalho: /apk redireciona pra última APK release no Forgejo // Atalho: /apk redireciona pra última APK release no Forgejo
const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.6.2/Shivao-v1.6.2.apk'; const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.7.0/Shivao-v1.7.0.apk';
app.get('/apk', (req, res) => res.redirect(302, LATEST_APK_URL)); app.get('/apk', (req, res) => res.redirect(302, LATEST_APK_URL));
// Página A4 imprimível com QR Code + instruções (cola no barco/marina) // Página A4 imprimível com QR Code + instruções (cola no barco/marina)