signalk-siparu 0.2.0 → 0.2.2
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.
- package/README.md +7 -0
- package/package.json +1 -1
- package/plugin/dist/config-routes.js +3 -2
- package/plugin/dist/origin-guard.d.ts +5 -0
- package/plugin/dist/origin-guard.js +34 -0
- package/plugin/dist/pairing.js +17 -14
- package/plugin/dist/voyage-routes.js +3 -2
- package/public/assets/{Logbook-DFHM-jqx.js → Logbook-DKQgrjzR.js} +1 -1
- package/public/assets/{Map-8D_Vswr5.js → Map-DeGKZhDf.js} +1 -1
- package/public/assets/{Voyage-CKrgqKFd.js → Voyage-csfbROs0.js} +1 -1
- package/public/assets/index-Bws3NPSA.css +1 -0
- package/public/assets/{index-DAYjCs-4.js → index-DFYgRoHp.js} +3 -3
- package/public/assets/{style-BEJHY9Dg.js → style-DhMV3_OD.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/index-vrv9X4MC.css +0 -1
package/README.md
CHANGED
|
@@ -111,6 +111,13 @@ commit, names each write route, and an eighth one fails the build.
|
|
|
111
111
|
| `POST /voyages/:id/merge-previous` | Join this passage to the one before it, when one trip was recorded as two |
|
|
112
112
|
| `POST /voyages/:id/undo-merge` | Separate a passage back into what it was made of |
|
|
113
113
|
|
|
114
|
+
Those seven, and `GET /pair/status` with them, answer only requests that came from the
|
|
115
|
+
boat's own screen. A browser marks every request with its origin, and one from a page on
|
|
116
|
+
another site gets 403: on a server with security off, which is the default, an advert
|
|
117
|
+
frame in any tab on the boat's network could otherwise unpair her or lift the pairing
|
|
118
|
+
code off the helm. A caller with no browser headers, curl or a script on her own network,
|
|
119
|
+
is not affected. Neither is the reading surface above.
|
|
120
|
+
|
|
114
121
|
The two voyage edits are reachable from the boat's own network only. What the
|
|
115
122
|
shore can ask her is a closed set of five questions, all of them reads
|
|
116
123
|
(`history`, `snapshots`, `voyages`, `track`, `phases`); nothing on that socket
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-siparu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Kept aboard, proven ashore. An impartial, timestamped record of every voyage: position, wind, depth and logbook, written on the boat and readable from anywhere. A read-only Signal K plugin with a built-in dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"signalk-node-server-plugin",
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.allowConfigure = allowConfigure;
|
|
4
4
|
exports.registerConfigRoutes = registerConfigRoutes;
|
|
5
5
|
const pairing_1 = require("./pairing");
|
|
6
|
+
const origin_guard_1 = require("./origin-guard");
|
|
6
7
|
function allowConfigure(app, req) {
|
|
7
8
|
try {
|
|
8
9
|
const ss = app.securityStrategy;
|
|
@@ -43,7 +44,7 @@ function registerConfigRoutes(router, deps) {
|
|
|
43
44
|
router.get('/config/fuel-paths', (_req, res) => {
|
|
44
45
|
res.json(fuelPathsView());
|
|
45
46
|
});
|
|
46
|
-
router.post('/config/fuel-paths', (req, res) => {
|
|
47
|
+
router.post('/config/fuel-paths', (0, origin_guard_1.sameOrigin)((req, res) => {
|
|
47
48
|
if (!(0, pairing_1.securityOff)(app, req) && !allowConfigure(app, req)) {
|
|
48
49
|
res.status(403).json({ error: 'admin_required' });
|
|
49
50
|
return;
|
|
@@ -66,5 +67,5 @@ function registerConfigRoutes(router, deps) {
|
|
|
66
67
|
if (!res.headersSent)
|
|
67
68
|
res.status(500).json({ error: 'internal' });
|
|
68
69
|
});
|
|
69
|
-
});
|
|
70
|
+
}));
|
|
70
71
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.crossSite = crossSite;
|
|
4
|
+
exports.sameOrigin = sameOrigin;
|
|
5
|
+
function crossSite(headers) {
|
|
6
|
+
const site = header(headers, 'sec-fetch-site');
|
|
7
|
+
if (site) {
|
|
8
|
+
return site !== 'same-origin' && site !== 'none';
|
|
9
|
+
}
|
|
10
|
+
const origin = header(headers, 'origin');
|
|
11
|
+
if (!origin)
|
|
12
|
+
return false;
|
|
13
|
+
let named;
|
|
14
|
+
try {
|
|
15
|
+
named = new URL(origin).host;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return named !== header(headers, 'host');
|
|
21
|
+
}
|
|
22
|
+
function sameOrigin(handler) {
|
|
23
|
+
return (req, res) => {
|
|
24
|
+
if (crossSite(req.headers)) {
|
|
25
|
+
res.status(403).json({ error: 'cross_site' });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
handler(req, res);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function header(headers, name) {
|
|
32
|
+
const value = headers?.[name];
|
|
33
|
+
return typeof value === 'string' ? value.toLowerCase() : undefined;
|
|
34
|
+
}
|
package/plugin/dist/pairing.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.maskEmail = maskEmail;
|
|
|
6
6
|
exports.registerPairRoutes = registerPairRoutes;
|
|
7
7
|
exports.retryPendingUnlinks = retryPendingUnlinks;
|
|
8
8
|
exports.__resetPairingState = __resetPairingState;
|
|
9
|
+
const origin_guard_1 = require("./origin-guard");
|
|
9
10
|
function securityOff(app, req) {
|
|
10
11
|
try {
|
|
11
12
|
const ss = app.securityStrategy;
|
|
@@ -68,6 +69,7 @@ async function relay(url, path, body, boatToken) {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
const NOT_YOUR_BOAT = 'That account does not own this boat. She is still linked as before.';
|
|
72
|
+
const BOAT_LIMIT = 'That account already holds as many boats as its plan allows. Make room for her there, then pair again.';
|
|
71
73
|
function maskEmail(email) {
|
|
72
74
|
if (!email)
|
|
73
75
|
return null;
|
|
@@ -85,7 +87,7 @@ function registerPairRoutes(router, deps) {
|
|
|
85
87
|
pairedAt: remote.pairedAt,
|
|
86
88
|
uplink: uplinkStatus() ?? undefined
|
|
87
89
|
});
|
|
88
|
-
router.get('/pair/status', (req, res) => {
|
|
90
|
+
router.get('/pair/status', (0, origin_guard_1.sameOrigin)((req, res) => {
|
|
89
91
|
const json = (screen) => {
|
|
90
92
|
const status = { ...screen };
|
|
91
93
|
if (securityOff(app, req))
|
|
@@ -139,8 +141,8 @@ function registerPairRoutes(router, deps) {
|
|
|
139
141
|
app.debug(`pair status: ${String(e)}`);
|
|
140
142
|
}
|
|
141
143
|
})();
|
|
142
|
-
});
|
|
143
|
-
router.post('/pair/start', (_req, res) => {
|
|
144
|
+
}));
|
|
145
|
+
router.post('/pair/start', (0, origin_guard_1.sameOrigin)((_req, res) => {
|
|
144
146
|
void (async () => {
|
|
145
147
|
lastError = null;
|
|
146
148
|
try {
|
|
@@ -163,8 +165,8 @@ function registerPairRoutes(router, deps) {
|
|
|
163
165
|
res.status(502).json({ state: 'error', message: lastError });
|
|
164
166
|
}
|
|
165
167
|
})();
|
|
166
|
-
});
|
|
167
|
-
router.post('/pair/approve', (_req, res) => {
|
|
168
|
+
}));
|
|
169
|
+
router.post('/pair/approve', (0, origin_guard_1.sameOrigin)((_req, res) => {
|
|
168
170
|
void (async () => {
|
|
169
171
|
if (!deviceCode) {
|
|
170
172
|
res.status(409).json({ error: 'no_pairing_in_progress' });
|
|
@@ -194,17 +196,18 @@ function registerPairRoutes(router, deps) {
|
|
|
194
196
|
}
|
|
195
197
|
catch (e) {
|
|
196
198
|
app.error(`pair approve failed: ${String(e)}`);
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
+
const answered = e instanceof RelayRefused && (e.code === 'not_your_boat' || e.code === 'boat_limit');
|
|
200
|
+
if (answered)
|
|
199
201
|
deviceCode = userCode = expiresAt = null;
|
|
200
|
-
|
|
202
|
+
const message = e instanceof RelayRefused && e.code === 'boat_limit' ? BOAT_LIMIT : NOT_YOUR_BOAT;
|
|
203
|
+
res.status(answered ? 409 : 502).json({
|
|
201
204
|
state: 'error',
|
|
202
|
-
message:
|
|
205
|
+
message: answered ? message : 'Could not finish pairing. Try again.'
|
|
203
206
|
});
|
|
204
207
|
}
|
|
205
208
|
})();
|
|
206
|
-
});
|
|
207
|
-
router.post('/pair/deny', (_req, res) => {
|
|
209
|
+
}));
|
|
210
|
+
router.post('/pair/deny', (0, origin_guard_1.sameOrigin)((_req, res) => {
|
|
208
211
|
void (async () => {
|
|
209
212
|
if (deviceCode) {
|
|
210
213
|
await relay(relayUrl, '/pair/deny', { device_code: deviceCode }).catch(() => { });
|
|
@@ -213,8 +216,8 @@ function registerPairRoutes(router, deps) {
|
|
|
213
216
|
const remote = getRemote();
|
|
214
217
|
res.json(remote ? paired(remote) : { state: 'idle' });
|
|
215
218
|
})();
|
|
216
|
-
});
|
|
217
|
-
router.post('/pair/reset', (_req, res) => {
|
|
219
|
+
}));
|
|
220
|
+
router.post('/pair/reset', (0, origin_guard_1.sameOrigin)((_req, res) => {
|
|
218
221
|
void (async () => {
|
|
219
222
|
const remote = getRemote();
|
|
220
223
|
if (remote?.boatToken) {
|
|
@@ -240,7 +243,7 @@ function registerPairRoutes(router, deps) {
|
|
|
240
243
|
if (!res.headersSent)
|
|
241
244
|
res.status(500).json({ state: 'error', message: 'Could not turn off remote viewing.' });
|
|
242
245
|
});
|
|
243
|
-
});
|
|
246
|
+
}));
|
|
244
247
|
}
|
|
245
248
|
async function retryPendingUnlinks(relayUrl, getPending, clear, log) {
|
|
246
249
|
for (const pending of getPending()) {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerVoyageEditRoutes = registerVoyageEditRoutes;
|
|
4
4
|
const pairing_1 = require("./pairing");
|
|
5
5
|
const config_routes_1 = require("./config-routes");
|
|
6
|
+
const origin_guard_1 = require("./origin-guard");
|
|
6
7
|
const STATUS = {
|
|
7
8
|
not_found: 404,
|
|
8
9
|
no_previous: 409,
|
|
@@ -40,6 +41,6 @@ function registerVoyageEditRoutes(router, deps) {
|
|
|
40
41
|
});
|
|
41
42
|
};
|
|
42
43
|
};
|
|
43
|
-
router.post('/voyages/:id/merge-previous', edit('merge-previous', mergeWithPrevious));
|
|
44
|
-
router.post('/voyages/:id/undo-merge', edit('undo-merge', undoMerge));
|
|
44
|
+
router.post('/voyages/:id/merge-previous', (0, origin_guard_1.sameOrigin)(edit('merge-previous', mergeWithPrevious)));
|
|
45
|
+
router.post('/voyages/:id/undo-merge', (0, origin_guard_1.sameOrigin)(edit('undo-merge', undoMerge)));
|
|
45
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{c as t,g as n,h as r,i,l as a,n as o,o as s,p as c,s as l,t as u,u as d,v as f}from"./index-
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{c as t,g as n,h as r,i,l as a,n as o,o as s,p as c,s as l,t as u,u as d,v as f}from"./index-DFYgRoHp.js";import{t as p}from"./visibleInterval-CFxPzgzx.js";var m=e(f(),1),h={"1m":1,"1h":60,"6h":360,"1d":1440},g={"1m":60,"1h":48,"6h":40,"1d":30},_=15e3;function v(){let[e,t]=(0,m.useState)(`1h`),[n,i]=(0,m.useState)(0),[a,o]=(0,m.useState)(!1),[s,c]=(0,m.useState)([]),[l,u]=(0,m.useState)(null),[d,f]=(0,m.useState)(!1),v=h[e],y=g[e]+n*g[e],b=(0,m.useCallback)(e=>{t(e),i(0),c([]),o(!1)},[]),x=(0,m.useCallback)(async()=>{f(!0),u(null);try{let e=y+1,t=await r.logbook.snapshots({bucket:v,limit:e,order:`desc`});o(t.length>y),c(t.slice(0,y))}catch(e){u(e.message)}finally{f(!1)}},[v,y]);return(0,m.useEffect)(()=>{x();let e=p(x,_);return()=>e()},[x]),{granularity:e,changeGran:b,snaps:s,err:l,busy:d,hasMore:a,loadMore:(0,m.useCallback)(()=>i(e=>e+1),[])}}function y(){let[e,t]=(0,m.useState)(o()),[n,i]=(0,m.useState)([]),[a,s]=(0,m.useState)(null),[l,d]=(0,m.useState)(!1),f=(0,m.useRef)(!0),h=c(6e4),g=o(new Date(h)),v=(0,m.useMemo)(()=>u(e),[e]),y=v+24*36e5,b=e===g;(0,m.useEffect)(()=>{f.current&&e!==g&&t(g)},[g,e]);let x=(0,m.useCallback)(e=>{f.current=e===o(),t(e)},[]),S=(0,m.useCallback)(async()=>{d(!0),s(null);try{let e=await r.logbook.snapshots({from:v,to:y,limit:5e3,order:`desc`,bucket:60});i(e)}catch(e){s(e.message)}finally{d(!1)}},[v,y]);return(0,m.useEffect)(()=>{if(S(),!b)return;let e=p(S,_);return()=>e()},[S,b]),{dateStr:e,setDateStr:x,dayStart:v,isToday:b,snaps:n,err:a,busy:l,prevDay:(0,m.useCallback)(()=>x(o(new Date(v-864e5))),[v,x]),nextDay:(0,m.useCallback)(()=>x(o(new Date(v+864e5))),[v,x]),goToday:(0,m.useCallback)(()=>x(o()),[x])}}var b=n(),x=[`1m`,`1h`,`6h`,`1d`],S={"1m":`Last hour`,"1h":`Last 2 days`,"6h":`Last 10 days`,"1d":`Last month`};function C(){let[e,t]=(0,m.useState)(`live`),[n,r]=(0,m.useState)(()=>localStorage.getItem(`lb:windUnit`)||`kn`),i={mode:e,setMode:t,windUnit:n,toggleWind:()=>r(e=>{let t=e===`kn`?`bft`:`kn`;return localStorage.setItem(`lb:windUnit`,t),t})};return(0,b.jsx)(`div`,{className:`lb`,children:e===`live`?(0,b.jsx)(E,{...i}):(0,b.jsx)(D,{...i})})}function w({mode:e,setMode:t}){return(0,b.jsxs)(`div`,{className:`seg`,children:[(0,b.jsx)(`button`,{className:e===`live`?`on`:``,onClick:()=>t(`live`),children:`Live`}),(0,b.jsx)(`button`,{className:e===`day`?`on`:``,onClick:()=>t(`day`),children:`Day`})]})}function T({windUnit:e,toggleWind:t}){return(0,b.jsxs)(`div`,{className:`lb-cols`,children:[(0,b.jsx)(`span`,{children:`UTC`}),(0,b.jsx)(`span`,{children:`SOG`}),(0,b.jsx)(`span`,{children:`HDG`}),(0,b.jsx)(`span`,{className:`tap`,onClick:t,title:`Tap: knots ⇄ Beaufort`,children:e===`kn`?`TWS`:`BFT`}),(0,b.jsx)(`span`,{children:`BARO`}),(0,b.jsx)(`span`,{children:`DEP`})]})}function E({mode:e,setMode:t,windUnit:n,toggleWind:r}){let{granularity:i,changeGran:a,snaps:o,err:s,busy:c,hasMore:l,loadMore:u}=v();return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`lb-ctrl`,children:[(0,b.jsx)(w,{mode:e,setMode:t}),(0,b.jsx)(`div`,{className:`seg`,children:x.map(e=>(0,b.jsx)(`button`,{className:i===e?`on`:``,onClick:()=>a(e),children:e},e))}),(0,b.jsx)(`span`,{className:`lb-count`,children:o.length})]}),(0,b.jsx)(T,{windUnit:n,toggleWind:r}),(0,b.jsxs)(`div`,{className:`lb-day`,children:[(0,b.jsx)(`span`,{children:S[i]}),(0,b.jsx)(`b`,{children:o.length})]}),s&&(0,b.jsx)(`div`,{className:`lb-err`,children:s}),(0,b.jsx)(O,{snaps:o,windUnit:n,footer:l?(0,b.jsx)(`button`,{className:`lb-more`,onClick:u,disabled:c,children:c?`Loading…`:`Load ${g[i]} more`}):null})]})}function D({mode:e,setMode:t,windUnit:n,toggleWind:r}){let{dateStr:i,setDateStr:a,isToday:s,snaps:c,err:l,busy:u,prevDay:d,nextDay:f,goToday:p}=y(),m=s?`Today · ${new Date(i).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,timeZone:`UTC`})}`:new Date(i).toLocaleDateString(`en-GB`,{weekday:`short`,day:`2-digit`,month:`short`,timeZone:`UTC`}).replace(/,/g,``);return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`lb-ctrl`,children:[(0,b.jsx)(w,{mode:e,setMode:t}),(0,b.jsxs)(`div`,{className:`lb-date`,children:[(0,b.jsx)(`button`,{onClick:d,"aria-label":`Previous day`,children:`‹`}),(0,b.jsx)(`input`,{type:`date`,className:`dt`,value:i,max:o(),onChange:e=>a(e.target.value),style:{border:`1.5px solid var(--rule)`,background:`var(--cell)`,color:`var(--text)`,fontFamily:`var(--sp-font)`,fontSize:12,padding:`5px 7px`}}),(0,b.jsx)(`button`,{onClick:f,disabled:s,"aria-label":`Next day`,children:`›`}),(0,b.jsx)(`button`,{onClick:p,disabled:s,children:`Now`})]})]}),(0,b.jsx)(T,{windUnit:n,toggleWind:r}),(0,b.jsxs)(`div`,{className:`lb-day`,children:[(0,b.jsx)(`span`,{children:m}),(0,b.jsx)(`b`,{children:c.length})]}),l&&(0,b.jsx)(`div`,{className:`lb-err`,children:l}),!u&&c.length===0?(0,b.jsxs)(`div`,{className:`sp-empty`,children:[(0,b.jsx)(`div`,{className:`em-t`,children:`No snapshots`}),(0,b.jsx)(`div`,{className:`em-s`,children:`No telemetry was logged for this day.`})]}):(0,b.jsx)(O,{snaps:c,windUnit:n,footer:null})]})}function O({snaps:e,footer:t,windUnit:n}){return(0,b.jsxs)(`div`,{className:`lb-rows`,children:[e.map(e=>(0,b.jsx)(k,{s:e,windUnit:n},e.ts)),t]})}function k({s:e,windUnit:n}){let r=new Date(e.ts),o=e=>String(e).padStart(2,`0`),c=d(e.sog),u=a(e.heading_true??e.heading_mag),f=l(e.wind_speed_true),p=f===null?`·`:String(n===`kn`?Math.round(f):s(f)??`·`),m=t(e.air_pressure_pa);return(0,b.jsxs)(`div`,{className:`lb-row`,children:[(0,b.jsxs)(`span`,{className:`tm`,children:[o(r.getUTCHours()),`:`,o(r.getUTCMinutes())]}),(0,b.jsx)(`span`,{className:`v`,children:i(c,1)}),(0,b.jsx)(`span`,{className:`v`,children:u===null?`·`:Math.round(u)+`°`}),(0,b.jsx)(`span`,{className:`v`,children:p}),(0,b.jsx)(`span`,{className:`v dim`,children:m===null?`·`:Math.round(m)}),(0,b.jsx)(`span`,{className:`v`,children:e.depth===null?`·`:e.depth.toFixed(1)})]})}function A(){return(0,b.jsx)(C,{})}export{A as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{a as t,d as n,g as r,h as i,i as a,l as o,r as s,u as c,v as l}from"./index-
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{a as t,d as n,g as r,h as i,i as a,l as o,r as s,u as c,v as l}from"./index-DFYgRoHp.js";import{t as u}from"./visibleInterval-CFxPzgzx.js";import{a as d,i as f,n as p,o as m,r as h,s as g,t as _}from"./style-DhMV3_OD.js";var v=e(l(),1),y={fill:`#ffb938`,stroke:`#b38326`,glow:`rgba(255,185,56,0.65)`,dot:`#050706`};function b(e,t=y){let n=t.fill,r=t.stroke;return e===null||Number.isNaN(e)?`
|
|
2
2
|
<div style="
|
|
3
3
|
width: 14px; height: 14px;
|
|
4
4
|
background: ${n};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,f as n,g as r,h as i,i as a,m as o,r as s,v as c}from"./index-
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,f as n,g as r,h as i,i as a,m as o,r as s,v as c}from"./index-DFYgRoHp.js";import{a as l,i as u,n as d,o as f,s as p,t as m}from"./style-DhMV3_OD.js";var h=e(c(),1),g=[{mode:`total_l`,label:`Litres`},{mode:`total_usgal`,label:`US gallons`},{mode:`total_impgal`,label:`Imp gallons`},{mode:`per_nm`,label:`Litres / nm`},{mode:`nm_per_l`,label:`nm / L`},{mode:`per_hour`,label:`Litres / hour`}],_=.26417205,v=.21996923,y=1e-6;function b(e,t,n){return`${e.toFixed(n)} ${t}`}function x(e,t,n,r){if(e===null||!Number.isFinite(e))return null;switch(r){case`total_l`:return b(e,`L`,+(e<100));case`total_usgal`:return b(e*_,`US gal`,1);case`total_impgal`:return b(e*v,`Imp gal`,1);case`per_nm`:return t>y?b(e/t,`L/nm`,2):null;case`nm_per_l`:return e>y?b(t/e,`nm/L`,2):null;case`per_hour`:return n>y?b(e/n,`L/h`,1):null}}function S(e){if(e===null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function C(e){if(!(e>0))return``;let t=Math.round(e*60),n=Math.floor(t/60),r=t%60;return n>0?`${n}h ${r}m`:`${r}m`}var w=e=>e===null?null:new Date(e).toISOString(),T=[`id`,`start_utc`,`end_utc`,`duration`,`from`,`to`,`distance_nm`,`hours_underway`,`avg_sog_kn`,`max_sog_kn`,`fuel_used_l`,`start_lat`,`start_lon`,`end_lat`,`end_lon`];function E(e){let t=[...e].sort((e,t)=>e.start_ts-t.start_ts),n=[T.join(`,`)];for(let e of t)n.push([e.id,w(e.start_ts),w(e.end_ts),C(e.hours_underway),e.start_port,e.end_port,e.distance_nm,e.hours_underway,e.avg_sog_kn,e.max_sog_kn,e.fuel_used_l,e.start_lat,e.start_lon,e.end_lat,e.end_lon].map(S).join(`,`));return n.join(`\r
|
|
2
2
|
`)+`\r
|
|
3
3
|
`}function D(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}function O(e){let t=new Date(e.start_ts).toISOString().slice(0,10);return!e.start_port&&!e.end_port?`Voyage ${e.id} ${t}`:`${t} ${e.start_port??`?`} to ${e.end_port??`?`}`}function k(e,t){let n=D(O(e)),r=t.map(e=>` <trkpt lat="${e.lat}" lon="${e.lon}"><time>${new Date(e.ts).toISOString()}</time></trkpt>`).join(`
|
|
4
4
|
`);return[`<?xml version="1.0" encoding="UTF-8"?>`,`<gpx version="1.1" creator="Siparu" xmlns="http://www.topografix.com/GPX/1/1">`,` <metadata>`,` <name>${n}</name>`,` <time>${new Date(e.start_ts).toISOString()}</time>`,` </metadata>`,` <trk>`,` <name>${n}</name>`,` <trkseg>`,r,` </trkseg>`,` </trk>`,`</gpx>`,``].join(`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:300;src:url(./archivo-latin-300-normal-AMs-pvbP.woff2)format("woff2"),url(./archivo-latin-300-normal-BRswihEa.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:300;src:url(./archivo-latin-ext-300-normal-0WwdIqab.woff2)format("woff2"),url(./archivo-latin-ext-300-normal-D3llZCfd.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:400;src:url(./archivo-latin-400-normal-C81ewxNO.woff2)format("woff2"),url(./archivo-latin-400-normal-Bl602Mgc.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:400;src:url(./archivo-latin-ext-400-normal-B5tPZzqW.woff2)format("woff2"),url(./archivo-latin-ext-400-normal-DrmqtLj6.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:500;src:url(./archivo-latin-500-normal-CGugC8dq.woff2)format("woff2"),url(./archivo-latin-500-normal-pkzA7jw9.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:500;src:url(./archivo-latin-ext-500-normal-RFhg4CpD.woff2)format("woff2"),url(./archivo-latin-ext-500-normal-D7mXw3Kz.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:600;src:url(./archivo-latin-600-normal-3BBy0ZsW.woff2)format("woff2"),url(./archivo-latin-600-normal-DwYieO8P.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:600;src:url(./archivo-latin-ext-600-normal-Dm61iqcg.woff2)format("woff2"),url(./archivo-latin-ext-600-normal-D7uErJuz.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:700;src:url(./archivo-latin-700-normal-D9cHMWGj.woff2)format("woff2"),url(./archivo-latin-700-normal-lT5TFe7H.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:700;src:url(./archivo-latin-ext-700-normal-DD_nkw6b.woff2)format("woff2"),url(./archivo-latin-ext-700-normal-CgyfnKjw.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:800;src:url(./archivo-latin-800-normal-cB6v3kRN.woff2)format("woff2"),url(./archivo-latin-800-normal-DZa_k145.woff)format("woff")}@font-face{font-family:Archivo;font-style:normal;font-display:swap;font-weight:800;src:url(./archivo-latin-ext-800-normal-BC6piDwP.woff2)format("woff2"),url(./archivo-latin-ext-800-normal-B9NTbsU1.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:300;src:url(./jetbrains-mono-latin-300-normal-DuMDZskh.woff2)format("woff2"),url(./jetbrains-mono-latin-300-normal-BYcAiAh2.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./jetbrains-mono-latin-400-normal-V6pRDFza.woff2)format("woff2"),url(./jetbrains-mono-latin-400-normal-6-qcROiO.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./jetbrains-mono-latin-500-normal-BWZEU5yA.woff2)format("woff2"),url(./jetbrains-mono-latin-500-normal-CJOVTJB7.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:600;src:url(./jetbrains-mono-latin-600-normal-C8RAYTDA.woff2)format("woff2"),url(./jetbrains-mono-latin-600-normal-BfsvjouI.woff)format("woff")}:root{--sp-font:"Archivo", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;--sp-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--cell:#1a1d21;--ink-band:#101214;--text:#e8eaed;--muted:#8b9095;--band-muted:#969ba1;--chrome-text:#f2f3f5;--accent:#e5484d;--accent-block:#7d2427;--on-block:#ffecec;--accent-soft:#f2a6a9;--on-accent:#140606;--rule:#ffffff29;--hair:#ffffff16;--alarm:#ff9f43;--warn:#d6a23c;--nav-port-bg:#7d2427;--nav-port-fg:#ffd7d9;--nav-stbd-bg:#1c5e2e;--nav-stbd-fg:#cdf3d6;--nav-center-bg:#6e5214;--nav-center-fg:#f6e6bd;--spark-gust:#e5484d;--spark-baro:#c7ced2;--map-sea:#101418;--map-grid:#ffffff0f;--map-land:#1e2226;--map-ring:#ffffff24;--map-ais:#9aa1a6;--map-ais-hi:#e5484d;--map-vessel:#e5484d;--map-land-text:#ffffff57;--ctrl-icon-filter:invert(.85)}:root[data-theme=day]{--cell:#fff;--ink-band:#e9e7e3;--text:#17191c;--muted:#6b7075;--band-muted:#5f6569;--chrome-text:#17191c;--accent:#c92a30;--accent-block:#b3272c;--on-block:#fff;--accent-soft:#a33236;--on-accent:#fff;--rule:#0003;--hair:#00000017;--alarm:#c2410c;--warn:#a16207;--nav-port-bg:#b3272c;--nav-port-fg:#fff;--nav-stbd-bg:#1e7a38;--nav-stbd-fg:#fff;--nav-center-bg:#8a6410;--nav-center-fg:#fff;--spark-gust:#c92a30;--spark-baro:#5a6167;--map-sea:#dfe6ea;--map-grid:#0000000d;--map-land:#cfd6da;--map-ring:#0000001f;--map-ais:#6a7176;--map-ais-hi:#c92a30;--map-vessel:#c92a30;--map-land-text:#00000059;--ctrl-icon-filter:none}html,body,#root{height:100%}html,body{overscroll-behavior:none;background:var(--ink-band);overflow:hidden}body{background:var(--ink-band);color:var(--text);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;margin:0}.swiss,.swiss *{box-sizing:border-box}.swiss{font-family:var(--sp-font);-webkit-tap-highlight-color:transparent}.sp-screen{background:var(--ink-band);color:var(--text);flex-direction:column;transition:background .25s,transform .3s;display:flex;position:fixed;inset:0;overflow:hidden}.sp-screen.sp-leaving{transform:translate(100%)}.swiss [hidden]{display:none!important}.sp-n,.sp-num{font-variant-numeric:tabular-nums}.t-section{color:var(--accent)}.sp-glyph{flex:none;width:auto;height:.62em;display:block}.sp-lockup{align-items:center;gap:9px;min-width:0;display:inline-flex}.swiss .head{background:var(--ink-band);color:var(--chrome-text);padding:calc(6px + env(safe-area-inset-top)) 16px 13px;flex:none;justify-content:space-between;align-items:center;gap:12px;display:flex}.swiss .head .mkwrap{align-items:center;min-width:0;display:flex}.swiss .head .sp-lockup{font-size:19px}.swiss .head .mk{font-family:var(--sp-font);letter-spacing:-.02em;text-transform:uppercase;color:var(--chrome-text);white-space:nowrap;font-weight:800}.swiss .head .mk .lt{opacity:.55;font-weight:300}.swiss .head .mkflag{flex:none;align-items:center;margin-left:9px;display:inline-flex}.swiss .head .mkflag svg{border-radius:3px;display:block}.swiss .head .back{border:1px solid var(--rule);width:30px;height:30px;color:var(--chrome-text);cursor:pointer;background:0 0;flex:none;justify-content:center;align-items:center;margin-right:10px;display:inline-flex}.swiss .head .back svg{width:15px;height:15px}.swiss .head .ctx{font-family:var(--sp-font);letter-spacing:.16em;text-transform:uppercase;color:var(--accent);border:1px solid var(--accent);flex:none;margin-left:11px;padding:4px 7px;font-size:9px;font-weight:700}.swiss .head .r{flex:none;align-items:center;gap:11px;display:flex}.swiss .head .live{font-family:var(--sp-font);letter-spacing:.14em;color:var(--accent-soft);align-items:center;gap:6px;font-size:10px;font-weight:700;display:inline-flex}.swiss .head .live .dot{background:var(--accent);border-radius:50%;width:6px;height:6px}.swiss .head .live.is-stale{color:var(--band-muted)}.swiss .head .live.is-stale .dot{background:var(--band-muted);animation:none}.swiss .head .live .dot{animation:2s ease-in-out infinite spPulse}.swiss .head .clk{font-family:var(--sp-mono);color:var(--chrome-text);opacity:.82;font-variant-numeric:tabular-nums;font-size:13px;font-weight:500}.swiss .head .umenu-btn{border:1px solid var(--rule);width:30px;height:30px;color:var(--chrome-text);font-family:var(--sp-font);cursor:pointer;transition:transform .12s var(--ease-snap);background:0 0;flex:none;place-items:center;font-size:12px;font-weight:700;display:grid}.swiss .head .umenu-btn:active{transform:scale(.94)}@keyframes spPulse{0%,to{opacity:1}50%{opacity:.35}}.swiss .tabbar{background:var(--ink-band);flex:none;display:flex}.swiss .tab{color:#79817f;cursor:pointer;background:0 0;border:none;flex-direction:column;flex:1;align-items:center;gap:6px;padding:11px 0 12px;text-decoration:none;transition:color .15s;display:flex;position:relative}.swiss .tab+.tab{border-left:1px solid #ffffff17}.swiss .tab svg{width:21px;height:21px}.swiss .tab .tl{font-family:var(--sp-font);letter-spacing:.13em;text-transform:uppercase;font-size:9px;font-weight:700}.swiss .tab.on{color:var(--chrome-text)}.swiss .tab .tind{background:var(--accent);height:3px;position:absolute;top:0;left:-.5px;right:-.5px}@media (width>=1000px){.swiss.sp-wide{flex-direction:row}.swiss.sp-wide>main{min-width:0}}.swiss .sp-rail{background:var(--ink-band);width:216px;color:var(--chrome-text);border-right:1px solid var(--rule);padding:calc(16px + env(safe-area-inset-top)) 0 16px;flex-direction:column;flex:none;display:flex}.swiss .sp-rail .sp-brand{padding:0 18px 22px;font-size:19px}.swiss .sp-rail .mk{font-family:var(--sp-font);letter-spacing:-.02em;text-transform:uppercase;color:var(--chrome-text);white-space:nowrap;font-weight:800;text-decoration:none}.swiss .sp-rail .rnav-list{flex-direction:column;display:flex}.swiss .sp-rail .rnav{color:#79817f;font-family:var(--sp-font);letter-spacing:.13em;text-transform:uppercase;align-items:center;gap:13px;padding:13px 18px;font-size:11px;font-weight:700;text-decoration:none;transition:color .15s;display:flex;position:relative}.swiss .sp-rail .rnav svg{flex:none;width:20px;height:20px}.swiss .sp-rail .rnav:hover,.swiss .sp-rail .rnav.on{color:var(--chrome-text)}.swiss .sp-rail .rnav.on:before{content:"";background:var(--accent);width:3px;position:absolute;top:0;bottom:0;left:0}.swiss .sp-rail .rfoot{flex-direction:column;align-items:flex-start;gap:13px;margin-top:auto;padding:16px 18px 0;display:flex}.swiss .sp-rail .live{font-family:var(--sp-font);letter-spacing:.14em;color:var(--accent-soft);align-items:center;gap:6px;font-size:10px;font-weight:700;display:inline-flex}.swiss .sp-rail .live .dot{background:var(--accent);border-radius:50%;width:6px;height:6px;animation:2s ease-in-out infinite spPulse}.swiss .sp-rail .live.is-stale{color:var(--band-muted)}.swiss .sp-rail .live.is-stale .dot{background:var(--band-muted);animation:none}.swiss .sp-rail .clk{font-family:var(--sp-mono);color:var(--chrome-text);opacity:.82;font-variant-numeric:tabular-nums;font-size:13px;font-weight:500}.swiss .sp-rail .umenu-btn{border:1px solid var(--rule);width:30px;height:30px;color:var(--chrome-text);cursor:pointer;transition:transform .12s var(--ease-snap);background:0 0;place-items:center;display:grid}.swiss .sp-rail .umenu-btn:active{transform:scale(.94)}.swiss .sp-dash{flex-direction:column;flex:1;gap:2px;min-height:0;display:flex}.swiss .sp-board{background:var(--ink-band);overflow:hidden auto}.swiss .sp-systems{flex-wrap:wrap;align-items:flex-start;gap:2px;display:flex}.swiss .sp-systems .sp-sec{flex:0 auto}.swiss .sp-systems .sp-sec-tanks{flex:320px;min-width:300px}.swiss .sp-systems .sp-sec-trip{flex:0 340px;min-width:300px}.swiss .sp-sec-trip .grid .matrix>.c{flex:calc(50% - 2px)}.swiss .sp-board .sm-matrix{grid-template-columns:minmax(94px, auto) repeat(var(--sm-cols,1), minmax(90px, max-content));width:fit-content}.swiss .sp-sec{flex-direction:column;flex:none;gap:2px;display:flex}.swiss .sp-sec-h{font-family:var(--sp-font);letter-spacing:.17em;text-transform:uppercase;color:var(--muted);background:var(--ink-band);margin:0;padding:11px 14px 5px;font-size:10px;font-weight:700}.swiss .sp-board .grid,.swiss .sp-board .grid .band,.swiss .sp-board .grid .matrix,.swiss .sp-board .sy-wrap{flex:none}.swiss .sp-board .c{min-height:94px}.swiss .sp-board .c-sog .n{font-size:40px}.swiss .sp-board .grid .matrix .n{font-size:34px}.swiss .sp-board .c-state .s{font-size:22px}.swiss .sp-board .sy-n{font-size:34px}.swiss .sp-board .c-pos .coords{white-space:nowrap;font-size:14px}.swiss .sp-pane{flex-direction:column;flex:1 1 0;min-width:0;min-height:0;display:flex;container:pane/inline-size}.swiss .grid{background:var(--ink-band);flex-direction:column;flex:1;gap:2px;min-height:0;display:flex}.swiss .grid .band{flex-wrap:wrap;flex:1 1 0;align-content:stretch;gap:2px;min-height:0;display:flex}.swiss .grid .matrix{flex-wrap:wrap;flex:2 1 0;align-content:stretch;gap:2px;min-height:0;display:flex}.swiss .grid>.band:only-child,.swiss .grid>.matrix:only-child,.swiss .grid>.c-quiet:only-child{flex:auto}.swiss .c-quiet{justify-content:center;gap:10px}.swiss .c-quiet .meta{font-family:var(--sp-mono);color:var(--muted);max-width:42ch;font-size:11px;line-height:1.6}.swiss .grid .band>.c-sog{flex:2 min(200px,100%)}.swiss .grid .band>.c-state{flex:min(100px,100%)}.swiss .grid .band>.c-pos{flex:2 min(200px,100%)}.swiss .grid .matrix>.c{flex:min(158px,100%);min-width:0}@container pane (width>=600px){.swiss .grid .matrix>.c{flex-basis:min(210px,100%)}}.swiss .c{background:var(--cell);flex-direction:column;min-width:0;padding:13px 14px;transition:background .25s;display:flex;position:relative;overflow:hidden}.swiss .c .t{font-family:var(--sp-font);letter-spacing:.13em;text-transform:uppercase;color:var(--text);font-size:9.5px;font-weight:700}.swiss .c .t .sub{color:var(--muted)}.swiss .n{font-family:var(--sp-font);letter-spacing:-.04em;color:var(--text);font-variant-numeric:tabular-nums;font-weight:300;line-height:.82}.swiss .n .u{font-family:var(--sp-font);letter-spacing:.04em;color:var(--muted);margin-left:.3em;font-size:.26em;font-weight:600}.swiss .c-sog{justify-content:flex-start}.swiss .c-sog .n{margin-block:auto;font-size:104px}.swiss .c-state{background:var(--cell);color:var(--text);border-left:2px solid var(--accent);justify-content:flex-start}.swiss .c-state .t{color:var(--accent)}.swiss .c-state .s{font-family:var(--sp-font);letter-spacing:-.01em;overflow-wrap:break-word;-webkit-hyphens:manual;hyphens:manual;margin-top:auto;font-size:30px;font-weight:300;line-height:.92}.swiss .c-state .meta{font-family:var(--sp-mono);color:var(--accent-soft);letter-spacing:.02em;margin-top:8px;margin-bottom:auto;font-size:10px}.swiss .grid .matrix>.c{justify-content:flex-start}.swiss .grid .matrix .n{font-size:46px}.swiss .grid .matrix>.c>.n{margin-block:auto}.swiss .c-depth .meta{font-family:var(--sp-mono);color:var(--muted);letter-spacing:.02em;white-space:pre-line;font-size:10px}.swiss .c-gust{flex:2 min(316px,100%)}.swiss .c-gust .lab{font-family:var(--sp-mono);color:var(--accent-soft);letter-spacing:.03em;font-size:10px}.swiss .c-gust .lab b{color:var(--text)}.swiss .spark{width:100%;max-width:220px;height:42px;display:block}@media (width<=410px){.swiss .c-state .s{font-size:27px}.swiss .grid .c-pos .coords{white-space:nowrap;font-size:13.5px}.swiss .spark{max-width:150px}.swiss .gustseg button{padding:4px 7px;font-size:9px}}.swiss .gustseg{border:1px solid #fff3;display:inline-flex}.swiss .gustseg button{font-family:var(--sp-font);letter-spacing:.08em;text-transform:uppercase;color:var(--band-muted);cursor:pointer;background:0 0;border:none;padding:4px 7px;font-size:9px;font-weight:700}.swiss .gustseg button+button{border-left:1px solid #ffffff29}.swiss .gustseg button.on{background:var(--accent);color:var(--on-accent)}.swiss .c-baro{flex-direction:row;align-items:center;gap:16px}.swiss .c-baro .left{flex-direction:column;flex:none;justify-content:center;gap:6px;display:flex}.swiss .c-baro .trend{font-family:var(--sp-mono);color:var(--muted);font-size:11px;font-weight:500}.swiss .c-baro .trend.ok{color:var(--accent)}.swiss .c-baro .trend.warn{color:var(--warn)}.swiss .c-baro .trend.alarm{color:var(--alarm)}.swiss .c-baro .sparkwrap{flex-direction:column;flex:1;justify-content:center;align-items:flex-end;gap:5px;min-width:0;display:flex}.swiss .c-baro .sparkwrap .lab{font-family:var(--sp-mono);color:var(--muted);letter-spacing:.03em;font-size:10px}.swiss .spark-b{width:100%;max-width:220px;height:38px;display:block}.swiss .c-baro.tap{cursor:pointer}.swiss .baro-pop{flex-direction:column;gap:14px;display:flex}.swiss .bp-head{flex-wrap:wrap;align-items:baseline;gap:12px;display:flex}.swiss .bp-now{font-family:var(--sp-font);color:var(--accent);font-variant-numeric:tabular-nums;letter-spacing:-.02em;font-size:34px;font-weight:600;line-height:1}.swiss .bp-now .bp-u{color:var(--muted);margin-left:4px;font-size:13px;font-weight:500}.swiss .bp-delta{font-family:var(--sp-mono);color:var(--muted);font-size:12px}.swiss .bp-seg{margin-left:auto}.swiss .bp-chart{touch-action:none;-webkit-user-select:none;user-select:none;width:100%;height:auto;display:block;overflow:hidden}.swiss .bp-grid{stroke:var(--hair);stroke-width:1px}.swiss .bp-ylab,.swiss .bp-xlab{fill:var(--muted);font-family:var(--sp-mono);font-size:9px}.swiss .bp-ylab{text-anchor:end}.swiss .bp-line{fill:none;stroke:var(--accent);stroke-width:1.8px;stroke-linejoin:round;stroke-linecap:round}.swiss .bp-dot{fill:var(--accent)}.swiss .bp-msg{text-align:center;color:var(--muted);padding:40px 0;font-size:13px}.swiss .sheet:has(.baro-pop){border-color:var(--accent)}.swiss .sheet:has(.baro-pop) .sheet-head{border-bottom-color:var(--accent)}.swiss .c-pos{flex-direction:row;align-items:center;gap:16px}.swiss .c-pos .t{flex:none}.swiss .c-pos .coords{text-align:right;font-family:var(--sp-font);letter-spacing:-.01em;color:var(--text);font-variant-numeric:tabular-nums;margin-left:auto;font-size:18px;font-weight:400;line-height:1.32}.swiss .skel{background:linear-gradient(90deg, color-mix(in oklab, var(--muted) 18%, transparent) 25%, color-mix(in oklab, var(--muted) 32%, transparent) 37%, color-mix(in oklab, var(--muted) 18%, transparent) 63%);background-size:400% 100%;border-radius:0;animation:1.4s infinite spShimmer;display:inline-block;color:#0000!important}@keyframes spShimmer{0%{background-position:100% 0}to{background-position:-100% 0}}.swiss .sp-empty{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:40px 24px;display:flex}.swiss .sp-empty .em-t{font-family:var(--sp-font);color:var(--text);font-size:13px;font-weight:600}.swiss .sp-empty .em-s{font-family:var(--sp-font);color:var(--muted);max-width:22ch;font-size:11px}.swiss .lb{background:var(--cell);color:var(--text);flex-direction:column;flex:1;min-height:0;transition:background .25s;display:flex;overflow:hidden}.swiss .lb-ctrl{border-bottom:2px solid var(--rule);flex-wrap:wrap;align-items:center;gap:9px;padding:11px 16px;display:flex}.swiss .seg{border:1.5px solid var(--rule);display:flex}.swiss .seg button{font-family:var(--sp-font);letter-spacing:.06em;text-transform:uppercase;background:var(--cell);color:var(--text);border:none;border-right:1.5px solid var(--rule);cursor:pointer;padding:6px 9px;font-size:9.5px;font-weight:700}.swiss .seg button:last-child{border-right:none}.swiss .seg button.on{background:var(--text);color:var(--cell)}.swiss .seg button:disabled{opacity:.4;cursor:default}.swiss .lb-count{font-family:var(--sp-font);letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-left:auto;font-size:10px;font-weight:700}.swiss .lb-date{align-items:center;gap:6px;margin-left:auto;display:flex}.swiss .lb-date .dt{font-family:var(--sp-font);color:var(--text);font-variant-numeric:tabular-nums;font-size:12px;font-weight:600}.swiss .lb-date button{border:1.5px solid var(--rule);background:var(--cell);width:28px;height:28px;color:var(--text);cursor:pointer;place-items:center;font-size:13px;display:grid}.swiss .lb-date button:disabled{opacity:.4;cursor:default}.swiss .lb-cols{border-bottom:1px solid var(--rule);font-family:var(--sp-font);letter-spacing:.09em;text-transform:uppercase;color:var(--muted);grid-template-columns:58px repeat(5,1fr);gap:4px;padding:9px 16px;font-size:8.5px;font-weight:700;display:grid}.swiss .lb-cols span:not(:first-child){text-align:right}.swiss .lb-cols span.tap{cursor:pointer;color:var(--accent);-webkit-user-select:none;user-select:none;text-underline-offset:3px;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swiss .lb-day{background:var(--ink-band);color:var(--chrome-text);font-family:var(--sp-font);letter-spacing:.13em;text-transform:uppercase;justify-content:space-between;padding:9px 16px;font-size:9.5px;font-weight:700;display:flex}.swiss .lb-day b{color:var(--accent);font-weight:700}.swiss .lb-rows{overscroll-behavior:contain;flex:1;min-height:0;overflow:hidden auto}.swiss .lb-rows::-webkit-scrollbar{width:0}.swiss .lb-row{border-bottom:1px solid var(--hair);grid-template-columns:58px repeat(5,1fr);align-items:baseline;gap:4px;padding:11px 16px;display:grid}.swiss .lb-row .tm{font-family:var(--sp-font);font-variant-numeric:tabular-nums;letter-spacing:-.02em;font-size:15px;font-weight:500}.swiss .lb-row .v{font-family:var(--sp-font);text-align:right;font-variant-numeric:tabular-nums;letter-spacing:-.01em;color:var(--text);font-size:14px;font-weight:400}.swiss .lb-row .v.dim{color:var(--muted);font-size:13px}.swiss .lb-more{background:var(--cell);border:none;border-top:1px solid var(--hair);width:100%;color:var(--accent);cursor:pointer;font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;padding:13px;font-size:10px;font-weight:700;display:block}.swiss .lb-err{border:1.5px solid var(--alarm);font-family:var(--sp-font);color:var(--alarm);margin:12px 16px;padding:10px 12px;font-size:12px}.swiss .mp{background:var(--map-sea);flex:1;min-height:0;transition:background .25s;position:relative;overflow:hidden}.swiss .mp-canvas{position:absolute;inset:0}.swiss .mp-strip{z-index:600;background:var(--ink-band);color:var(--chrome-text);justify-content:space-between;align-items:center;gap:10px;padding:10px 14px;display:flex;position:absolute;top:0;left:0;right:0}.swiss .mp-strip .co{font-family:var(--sp-font);font-variant-numeric:tabular-nums;letter-spacing:-.01em;font-size:12px;font-weight:500}.swiss .mp-strip .vec{font-family:var(--sp-font);color:var(--band-muted);font-variant-numeric:tabular-nums;font-size:10px}.swiss .mp-strip .vec b{color:var(--chrome-text);font-weight:600}.swiss .mp-strip .navp{font-family:var(--sp-font);letter-spacing:.13em;text-transform:uppercase;color:var(--accent);border:1px solid var(--accent);white-space:nowrap;padding:5px 8px;font-size:9px;font-weight:700}.swiss .mp-ctrl{z-index:600;flex-direction:column;align-items:flex-end;gap:8px;display:flex;position:absolute;bottom:34px;right:12px}.swiss .mp-btn{background:var(--cell);border:1.5px solid var(--rule);font-family:var(--sp-font);letter-spacing:.07em;text-transform:uppercase;color:var(--text);cursor:pointer;align-items:center;gap:8px;padding:8px 11px;font-size:10px;font-weight:700;display:flex}.swiss .mp-btn .n{font-variant-numeric:tabular-nums}.swiss .mp-btn.on{background:var(--text);color:var(--cell)}.swiss .mp-chartnote{z-index:600;background:var(--cell);border:1.5px solid var(--warn);color:var(--warn);font-family:var(--sp-font);letter-spacing:.04em;max-width:260px;padding:6px 9px;font-size:10px;font-weight:600;position:absolute;bottom:44px;left:12px}.sp-pop .nm{color:var(--text);justify-content:space-between;align-items:center;gap:10px;font-size:13px;font-weight:600;display:flex}.sp-pop .tag{letter-spacing:.1em;text-transform:uppercase;color:#fff;background:var(--map-ais-hi);padding:3px 5px;font-size:8px;font-weight:700}.sp-pop .rows{color:var(--muted);font-variant-numeric:tabular-nums;grid-template-columns:auto auto;gap:1px 14px;margin-top:7px;font-size:11px;line-height:1.7;display:grid}.sp-pop .rows b{color:var(--text);font-weight:600}.swiss .scrim{z-index:30;background:#00000080;justify-content:center;align-items:center;padding:18px;display:flex;position:absolute;inset:0}.swiss .sheet{z-index:31;background:var(--cell);border:2px solid var(--rule);flex-direction:column;width:100%;max-width:380px;max-height:82vh;display:flex;position:relative}@keyframes spFade{0%{opacity:0}to{opacity:1}}.swiss .sheet-head{border-bottom:2px solid var(--rule);justify-content:space-between;align-items:center;padding:15px 16px;display:flex}.swiss .sheet-title{font-family:var(--sp-font);color:var(--text);letter-spacing:-.01em;font-size:16px;font-weight:700}.swiss .sheet-eyebrow{font-family:var(--sp-font);letter-spacing:.16em;text-transform:uppercase;color:var(--muted);margin-bottom:3px;font-size:8.5px;font-weight:700}.swiss .sheet-x{border:1.5px solid var(--rule);width:30px;height:30px;color:var(--text);cursor:pointer;background:0 0;flex:none;place-items:center;font-size:16px;display:grid}.swiss .tabs2{border-bottom:2px solid var(--rule);display:flex}.swiss .tabs2 button{font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;color:var(--muted);cursor:pointer;background:0 0;border:none;border-bottom:3px solid #0000;flex:1;margin-bottom:-2px;padding:13px;font-size:11px;font-weight:700}.swiss .tabs2 button.on{color:var(--text);border-bottom-color:var(--accent)}.swiss .sheet-body{flex-direction:column;gap:17px;min-height:0;padding:18px 16px;display:flex;overflow-y:auto}.swiss .sheet-foot{border-top:1px solid var(--hair);gap:10px;padding:14px 16px 16px;display:flex}.swiss .field{flex-direction:column;gap:7px;display:flex}.swiss .field>label{font-family:var(--sp-font);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-size:9px;font-weight:700}.swiss .field input,.swiss .field textarea{font-family:var(--sp-font);color:var(--text);background:var(--cell);border:1.5px solid var(--rule);border-radius:0;outline:none;width:100%;padding:12px 13px;font-size:17px;font-weight:400}.swiss .field textarea{resize:vertical;min-height:64px;font-size:16px}.swiss .field input::placeholder,.swiss .field textarea::placeholder{color:var(--muted)}.swiss .field input:focus,.swiss .field textarea:focus{border-color:var(--accent)}.swiss .field input.err{border-color:var(--alarm)}.swiss .row2{grid-template-columns:1fr 1fr;gap:12px;display:grid}.swiss .hint{font-family:var(--sp-font);color:var(--muted);font-size:10.5px;line-height:1.6}.swiss .err{font-family:var(--sp-font);color:var(--alarm);font-size:11px;font-weight:600}.swiss .ok{font-family:var(--sp-font);color:var(--accent);font-size:12px;font-weight:600}.swiss .info-banner{font-family:var(--sp-font);color:var(--text);border:1.5px solid var(--accent);padding:9px 11px;font-size:11px;line-height:1.5}.swiss .chips{flex-wrap:wrap;gap:8px;display:flex}.swiss .chip{border:1.5px solid var(--rule);font-family:var(--sp-font);cursor:pointer;color:var(--text);background:var(--cell);align-items:center;gap:7px;padding:8px 11px;font-size:11px;font-weight:600;display:inline-flex}.swiss .chip .sw{flex:none;width:11px;height:11px}.swiss .chip.on{border-color:var(--text);box-shadow:inset 0 0 0 1px var(--text)}.swiss .stepper{border:1.5px solid var(--rule);width:max-content;display:flex}.swiss .stepper button{background:var(--cell);width:46px;height:46px;color:var(--text);cursor:pointer;font-size:22px;font-family:var(--sp-font);border:none}.swiss .stepper button:disabled{opacity:.4;cursor:default}.swiss .stepper button:first-child{border-right:1.5px solid var(--rule)}.swiss .stepper button:last-child{border-left:1.5px solid var(--rule)}.swiss .stepper .val{width:64px;font-family:var(--sp-font);font-variant-numeric:tabular-nums;color:var(--text);place-items:center;font-size:20px;font-weight:500;display:grid}.swiss .btn{font-family:var(--sp-font);letter-spacing:.09em;text-transform:uppercase;cursor:pointer;text-align:center;transition:transform .12s var(--ease-snap);border:none;border-radius:0;flex:1;padding:15px;font-size:12.5px;font-weight:700}.swiss .btn:active{transform:scale(.98)}.swiss .btn:disabled{opacity:.55;cursor:default}.swiss .btn.primary{background:var(--accent-block);color:var(--on-block)}.swiss .btn.ghost{background:var(--cell);color:var(--text);border:1.5px solid var(--rule)}.swiss .btn.ghost.danger{color:var(--alarm)}.swiss .search{border:1.5px solid var(--rule);align-items:center;gap:10px;margin:16px;padding:11px 13px;display:flex}.swiss .search input{font-family:var(--sp-font);color:var(--text);background:0 0;border:none;outline:none;flex:1;font-size:16px}.swiss .search input::placeholder{color:var(--muted)}.swiss .search svg{width:16px;height:16px;color:var(--muted);flex:none}.swiss .picklist{flex:1;min-height:0;overflow-y:auto}.swiss .pick-row{border-bottom:1px solid var(--hair);cursor:pointer;text-align:left;background:0 0;border-top:none;border-left:none;border-right:none;align-items:center;gap:12px;width:100%;padding:13px 16px;display:flex}.swiss .pick-row .sw{flex:none;width:12px;height:12px}.swiss .pick-row .nm{font-family:var(--sp-font);color:var(--text);flex:1;font-size:14px;font-weight:600}.swiss .pick-row .nm small{color:var(--muted);margin-top:2px;font-size:10px;font-weight:500;display:block}.swiss .pick-row .stock{font-family:var(--sp-font);letter-spacing:.06em;text-transform:uppercase;color:var(--text);border:1px solid var(--rule);flex:none;padding:4px 8px;font-size:11px;font-weight:700}.swiss .pick-row.dis{opacity:.4;cursor:default}.swiss .pick-row.dis .stock{color:var(--muted)}.swiss .filterbar{border-bottom:1px solid var(--hair);flex-wrap:wrap;gap:8px;padding:14px 16px;display:flex}.swiss .fchip{font-family:var(--sp-font);letter-spacing:.07em;text-transform:uppercase;border:1.5px solid var(--rule);background:var(--cell);color:var(--muted);cursor:pointer;padding:7px 11px;font-size:10px;font-weight:700}.swiss .fchip.on{background:var(--text);color:var(--cell);border-color:var(--text)}@media (prefers-reduced-motion:reduce){.swiss *,.swiss :before,.swiss :after{transition:none!important;animation:none!important}::view-transition-group(*){animation:none!important}::view-transition-image-pair(*){animation:none!important}::view-transition-old(*){animation:none!important}::view-transition-new(*){animation:none!important}}.sp-bload{background:var(--ink-band,#08090a);flex:1;justify-content:center;align-items:center;min-height:0;display:flex;overflow:hidden}.sp-bload.full{z-index:50;position:fixed;inset:0}.sp-bload .sp-lockup{font-size:21px}.sp-bload .sp-glyph{animation:1.8s ease-in-out infinite spBreathe}.sp-bload .sp-bload-mk{font-family:var(--sp-font);letter-spacing:.04em;color:var(--text);font-weight:700;animation:1.8s ease-in-out infinite spBreathe}.sp-bload .sp-bload-mk .lt{color:var(--accent);opacity:.9;font-weight:300}@keyframes spBreathe{0%,to{opacity:.35}50%{opacity:1}}@media (prefers-reduced-motion:reduce){.sp-bload .sp-bload-mk,.sp-bload .sp-glyph{opacity:.85;animation:none}}.swiss .vy{overscroll-behavior:contain;background:var(--ink-band);flex-direction:column;flex:1;gap:2px;min-height:0;display:flex;overflow:hidden auto}.swiss .vy::-webkit-scrollbar{width:0}.swiss .vy-active{background:var(--cell);color:var(--text);border-left:2px solid var(--accent);padding:14px 16px 16px}.swiss .vy-active-hd{font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;align-items:center;gap:8px;font-size:10px;font-weight:700;display:flex}.swiss .vy-pulse{background:var(--accent);border-radius:50%;flex:none;width:7px;height:7px;animation:1.4s ease-in-out infinite spPulse}.swiss .vy-active-grid{grid-template-columns:1.4fr 1fr 1fr;align-items:end;gap:12px;margin-top:12px;display:grid}.swiss .vy-a-hero .t,.swiss .vy-a-cell .t{font-family:var(--sp-font);letter-spacing:.08em;text-transform:uppercase;opacity:.8;margin-bottom:3px;font-size:8.5px;font-weight:700}.swiss .vy-a-hero .n{font-family:var(--sp-mono);letter-spacing:-.02em;font-size:36px;font-weight:700;line-height:.92}.swiss .vy-a-cell .v{font-family:var(--sp-mono);font-size:16px;font-weight:600}.swiss .vy-active .sub{opacity:.7}.swiss .vy-active .u{opacity:.72;margin-left:1px;font-size:.6em}.swiss .vy-seg{border-top:none;border-left:none;border-right:none}.swiss .vy-seg button{flex:1;padding:8px 5px}.swiss .vy-cards{background:var(--ink-band);grid-template-columns:1fr 1fr 1fr;gap:2px;display:grid}.swiss .vy-hero{background:var(--cell);border-left:2px solid var(--accent);grid-column:1/-1}.swiss .vy-hero .t{color:var(--accent)}.swiss .vy-hero .sub{color:var(--muted)}.swiss .vy-hero .n{font-family:var(--sp-mono);letter-spacing:-.02em;color:var(--text);font-size:40px;font-weight:700;line-height:1}.swiss .vy-cards .c .v{font-family:var(--sp-mono);color:var(--text);letter-spacing:-.01em;font-size:21px;font-weight:600}.swiss .vy-cards .c .u{color:var(--muted);margin-left:2px;font-size:.55em}.swiss .vy-hd{background:var(--ink-band);font-family:var(--sp-font);letter-spacing:.12em;text-transform:uppercase;color:var(--band-muted);justify-content:space-between;align-items:center;padding:13px 16px 9px;font-size:10px;font-weight:700;display:flex}.swiss .vy-hd b{color:var(--accent);font-family:var(--sp-mono);font-size:12px}.swiss .vy-hd-l{align-items:baseline;gap:6px;display:inline-flex}.swiss .vy-hd-acts{align-items:center;gap:6px;display:inline-flex}.swiss .vy-print-hd{display:none}.swiss .vy-fuelsrc{font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;color:var(--accent);border:1px solid var(--rule);cursor:pointer;background:0 0;padding:4px 9px;font-size:10px;font-weight:700}.swiss .fs-note{font-family:var(--sp-font);color:var(--muted);margin:0;font-size:12px;line-height:1.5}.swiss .fs-list{flex-direction:column;gap:8px;margin-top:12px;display:flex}.swiss .fs-row{text-align:left;background:var(--cell);border:1.5px solid var(--rule);cursor:pointer;flex-direction:column;align-items:flex-start;gap:3px;padding:11px 34px 11px 13px;display:flex;position:relative}.swiss .fs-row.on{border-color:var(--accent)}.swiss .fs-row.on:after{content:"";background:var(--accent);width:9px;height:9px;position:absolute;top:15px;right:13px}.swiss .fs-name{font-family:var(--sp-font);color:var(--text);font-size:15px;font-weight:600}.swiss .fs-row.on .fs-name{color:var(--accent-soft)}.swiss .fs-path{font-family:var(--sp-mono);color:var(--muted);font-size:11px}.swiss .fs-row.off-air{border-style:dashed}.swiss .fs-tag{font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;color:var(--accent-soft);font-size:9px;font-weight:700}.swiss .fs-quiet{font-family:var(--sp-font);color:var(--accent-soft);border-left:2px solid var(--accent);margin-top:10px;padding-left:9px;font-size:12px;line-height:1.5}.swiss .fs-apply{font-family:var(--sp-font);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-band);background:var(--accent);cursor:pointer;border:none;flex:1;padding:13px;font-size:13px;font-weight:700}.swiss .fs-apply:disabled{opacity:.55;cursor:default}.swiss .fs-err{font-family:var(--sp-font);color:var(--accent-soft);margin-top:10px;font-size:12px}.swiss .vy-list{background:var(--cell);flex-direction:column;display:flex}.swiss .vy-rowwrap{border-bottom:1px solid var(--hair)}.swiss .vy-rowwrap.open{background:#ffffff05}.swiss .vy-row{text-align:left;cursor:pointer;width:100%;color:var(--text);background:0 0;border:none;flex-direction:column;gap:4px;padding:13px 16px;display:flex}.swiss .vy-row-top{align-items:baseline;gap:10px;display:flex}.swiss .vy-date{font-family:var(--sp-font);letter-spacing:.02em;font-size:13px;font-weight:700}.swiss .vy-badge{font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;color:var(--accent);border:1px solid var(--accent);padding:2px 5px;font-size:7.5px;font-weight:700}.swiss .vy-dist{font-family:var(--sp-mono);margin-left:auto;font-size:16px;font-weight:600}.swiss .vy-unit{color:var(--muted);margin-left:2px;font-size:.6em}.swiss .vy-route{font-family:var(--sp-font);color:var(--accent);letter-spacing:.01em;font-size:11.5px;font-weight:600}.swiss .vy-row-sub{font-family:var(--sp-mono);color:var(--muted);font-size:11px}.swiss .vy-detail{flex-direction:column;gap:12px;padding:2px 16px 16px;display:flex}.swiss .vy-map{border:1px solid var(--hair);background:var(--map-sea);width:100%;height:180px}.swiss .vy-map .maplibregl-ctrl-attrib{background:color-mix(in srgb, var(--cell) 82%, transparent);font-family:var(--sp-font);color:var(--muted);border-radius:0;padding:1px 5px;font-size:8px}.swiss .vy-map .maplibregl-ctrl-attrib a{color:var(--muted);text-decoration:none}.swiss .tab,.swiss .seg button{transition:transform .13s;position:relative}.swiss .tab:active,.swiss .seg button:active{transform:scale(.95)}.swiss .tab:after,.swiss .seg button:after{content:"";pointer-events:none;z-index:1;opacity:0;background:radial-gradient(circle,#ffffff47,#fff0 62%);transition:opacity .5s;position:absolute;inset:0}.swiss .tab:active:after,.swiss .seg button:active:after{opacity:1;transition:opacity}.swiss .vy-track-empty{background:var(--ink-band);border:1px solid var(--hair);height:120px;font-family:var(--sp-mono);color:var(--muted);justify-content:center;align-items:center;font-size:11px;display:flex}.swiss .vy-meta{grid-template-columns:1fr 1fr;gap:9px 16px;display:grid}.swiss .vy-m{flex-direction:column;gap:2px;display:flex}.swiss .vy-m .k{font-family:var(--sp-font);letter-spacing:.1em;text-transform:uppercase;color:var(--band-muted);font-size:8.5px;font-weight:700}.swiss .vy-m .val{font-family:var(--sp-mono);color:var(--text);font-size:11.5px}.swiss .vy-m .coord{font-family:var(--sp-mono);color:var(--muted);font-size:10px}.swiss .vy-m-fuel{grid-column:1/-1}.swiss .vy-m .vy-fuel-quiet{font-family:var(--sp-font);color:var(--accent-soft);font-size:11.5px;line-height:1.45}.swiss .vy-m-edit{grid-column:1/-1}.swiss .vy-m-editerr{font-family:var(--sp-font);color:var(--accent);grid-column:1/-1;font-size:11.5px}.swiss .vy-m .vy-fuelsrc{align-self:flex-start;margin-top:2px}.swiss .vy-fuel-sel{font-family:var(--sp-font);letter-spacing:.06em;text-transform:uppercase;color:var(--text);background:var(--cell);border:1.5px solid var(--rule);appearance:none;cursor:pointer;background-image:linear-gradient(45deg, transparent 50%, var(--text) 50%), linear-gradient(135deg, var(--text) 50%, transparent 50%);background-position:right 9px center,right 5px center;background-repeat:no-repeat;background-size:4px 4px,4px 4px;border-radius:0;align-self:flex-start;margin-top:4px;padding:5px 22px 5px 8px;font-size:9.5px;font-weight:700}.swiss .vy-fuel-sel:focus-visible{outline:2px solid var(--text);outline-offset:1px}.swiss .vy-err{font-family:var(--sp-mono);color:var(--alarm);padding:16px;font-size:12px}.flex{display:flex}.grid{display:grid}.flex-col{flex-direction:column}.flex-1{flex:1}.h-full{height:100%}.min-h-0{min-height:0}.max-w-xs{max-width:20rem}.overflow-hidden{overflow:hidden}.items-center{align-items:center}.justify-center{justify-content:center}.text-center{text-align:center}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.border{border:1px solid}.border-current{border-color:currentColor}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.gap-3{gap:.75rem}.gap-4{gap:1rem}.p-6{padding:1.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.sp-auth{z-index:60;background:var(--ink-band,#08090a);color:var(--text,#e8eaed);font-family:var(--sp-font);text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:28px;padding:24px;display:flex;position:fixed;inset:0}.sp-auth .sp-lockup{font-size:21px}.sp-auth-mk{letter-spacing:.04em;font-weight:700}.sp-auth-mk .lt{color:var(--accent);opacity:.9;font-weight:300}.sp-auth-card{background:var(--cell);border-top:2px solid var(--accent);flex-direction:column;align-items:center;gap:14px;max-width:360px;padding:22px 20px 20px;display:flex}.sp-auth-kick{letter-spacing:.12em;text-transform:uppercase;color:var(--accent-soft,var(--accent));font-size:10px;font-weight:700}.sp-auth-body{margin:0;font-size:14px;line-height:1.5}.sp-auth-cta{background:var(--text);color:var(--cell);letter-spacing:.03em;padding:10px 18px;font-size:13px;font-weight:700;text-decoration:none;display:inline-block}.sp-auth-cta:active{transform:scale(.97)}.sp-auth-foot{color:var(--muted);margin:0;font-size:12px;line-height:1.45}.sp-auth-wait{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);align-items:center;gap:8px;font-size:11px;font-weight:600;display:flex}:root[data-theme=day] .sp-auth-wait{color:var(--text)}.sp-auth-pulse{background:var(--accent);border-radius:50%;width:7px;height:7px;animation:1.4s ease-in-out infinite spPulse}@media (prefers-reduced-motion:reduce){.sp-auth-pulse{animation:none}}.swiss .pair{background:var(--cell);border-top:2px solid var(--ink-band);flex:none;justify-content:space-between;align-items:center;gap:14px;padding:12px 14px;display:flex}.swiss .pair .pl{min-width:0}.swiss .pair .t{font-family:var(--sp-font);letter-spacing:.12em;text-transform:uppercase;color:var(--muted);font-size:9.5px;font-weight:700}.swiss .pair .s{font-family:var(--sp-font);color:var(--muted);margin-top:3px;font-size:12px}.swiss .pair .s b{color:var(--text);font-weight:600}.swiss .pair .who{font-family:var(--sp-mono);color:var(--text);text-overflow:ellipsis;white-space:nowrap;margin-top:3px;font-size:14px;overflow:hidden}.swiss .pair .code{font-family:var(--sp-mono);letter-spacing:.14em;color:var(--text);margin-top:4px;font-size:30px;font-weight:500}.swiss .pair .acts{flex:none;gap:8px;display:flex}.swiss .pair .fps{flex-direction:column;gap:2px;margin-top:4px;display:flex}.swiss .pair .fp{font-family:var(--sp-mono);letter-spacing:.08em;color:var(--text);font-size:14px}.swiss .pair.asking{background:var(--accent-block)}.swiss .pair.asking .t{color:var(--on-block)}.swiss .pair.asking .s{color:var(--accent-soft)}.swiss .pair.asking .who{color:#fff}.swiss .pair.err .t{color:var(--accent)}.swiss .pair.warn{border-top:none;border-left:3px solid var(--accent);padding-left:11px}.swiss .pair.warn .t{color:var(--accent)}.swiss .pbtn{font-family:var(--sp-font);letter-spacing:.09em;text-transform:uppercase;border:1px solid var(--rule);color:var(--text);cursor:pointer;white-space:nowrap;background:0 0;padding:9px 13px;font-size:10px;font-weight:700}.swiss .pbtn:active{transform:scale(.96)}.swiss .pbtn:disabled{opacity:.45;cursor:default}.swiss .pbtn.accent{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}.swiss .pbtn.ghost{color:var(--muted)}.swiss .pair.asking .pbtn{color:#fff;border-color:#fff6}.swiss .pair.asking .pbtn.accent{color:var(--accent-block);background:#fff;border-color:#fff}:root[data-theme=day] .swiss .pair .s,:root[data-theme=day] .swiss .pbtn.ghost{color:var(--text)}@media (width<=380px){.swiss .pair{flex-direction:column;align-items:stretch;gap:10px}.swiss .pair .acts{justify-content:flex-end}.swiss .pair .code{font-size:26px}}.swiss .sy-wrap{background:var(--ink-band);flex:1;min-height:0;overflow-y:auto}.swiss .sy-grid{flex-wrap:wrap;align-content:stretch;gap:2px;min-height:100%;display:flex}.swiss .sy-grid .c{flex:min(158px,100%);justify-content:flex-start;min-height:88px}.swiss .sy-n{margin-block:auto;font-size:clamp(22px,7vw,34px);line-height:1}.swiss .sy-grid .c.quiet .sy-n{color:var(--muted)}.swiss .sy-grid .c .sy-age{font-family:var(--sp-mono);color:var(--muted);letter-spacing:.02em;font-size:10px;position:absolute;top:13px;right:14px}.swiss .sy-grid .c.quiet .t{padding-right:68px}.swiss .sy-none{color:var(--muted);background:var(--cell);padding:24px 16px;font-size:12px}.swiss .sm-matrix{grid-template-columns:minmax(90px, auto) repeat(var(--sm-cols,1), minmax(74px, 1fr));background:var(--rule);border:1px solid var(--rule);align-content:start;gap:1px;display:grid}.swiss .sm-corner{background:var(--cell)}.swiss .sm-head{text-align:center;font-family:var(--sp-font);letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;text-overflow:ellipsis;background:var(--cell);color:var(--text);padding:8px 10px;font-size:12px;font-weight:800;overflow:hidden}.swiss .sm-head.sm-port{background:var(--nav-port-bg);color:var(--nav-port-fg)}.swiss .sm-head.sm-stbd{background:var(--nav-stbd-bg);color:var(--nav-stbd-fg)}.swiss .sm-head.sm-center{background:var(--nav-center-bg);color:var(--nav-center-fg)}.swiss .sm-rl{background:var(--cell);letter-spacing:.02em;color:var(--muted);white-space:nowrap;text-overflow:ellipsis;align-items:center;padding:0 12px;font-size:10.5px;display:flex;overflow:hidden}.swiss .sm-cell{background:var(--cell);flex-direction:column;justify-content:center;align-items:center;min-height:46px;padding:9px 8px;display:flex}.swiss .sm-cell .sm-v{font-family:var(--sp-mono);font-variant-numeric:tabular-nums;color:var(--text);white-space:nowrap;font-size:clamp(16px,4.4vw,22px);line-height:1}.swiss .sm-cell.sm-empty{background:var(--cell)}.swiss .sm-cell.quiet .sm-v{color:var(--muted)}.swiss .sm-cell .sm-age{font-family:var(--sp-mono);letter-spacing:.02em;color:var(--muted);margin-top:3px;font-size:9px}.swiss .sp-pane .sy-wrap{overflow-x:auto}.swiss .sp-pane .sm-matrix{grid-template-columns:104px repeat(var(--sm-cols,1), minmax(102px, max-content))}.swiss .sp-pane .sm-corner,.swiss .sp-pane .sm-rl{z-index:2;position:sticky;left:0}.swiss .sp-pane .sm-rl{white-space:normal;padding-block:6px;line-height:1.15}.swiss .tk-list{background:var(--cell);flex-direction:column;display:flex}.swiss .tk{border-bottom:1px solid var(--hair);grid-template-columns:minmax(82px,auto) 1fr 46px;align-items:center;gap:12px;padding:11px 14px;display:grid}.swiss .tk:last-child{border-bottom:none}.swiss .tk-l{letter-spacing:.04em;text-transform:uppercase;color:var(--muted);white-space:nowrap;text-overflow:ellipsis;font-size:10.5px;overflow:hidden}.swiss .tk-bar{background:var(--hair);height:8px;overflow:hidden}.swiss .tk-bar>i{background:var(--band-muted);height:100%;display:block}.swiss .tk.low .tk-bar>i{background:var(--accent)}.swiss .tk.low .tk-pct{color:var(--accent-soft)}.swiss .tk-pct{text-align:right;font-family:var(--sp-mono);font-variant-numeric:tabular-nums;color:var(--text);font-size:14px}.swiss .tk.quiet .tk-bar>i{background:var(--muted);opacity:.5}.swiss .tk.quiet .tk-pct{color:var(--muted)}.swiss .grid[hidden]{display:none}.swiss .sy-tabs{background:var(--ink-band);scrollbar-width:none;flex:none;gap:2px;padding:0 0 2px;display:flex;overflow-x:auto}.swiss .sy-tabs::-webkit-scrollbar{display:none}.swiss .sy-tab{appearance:none;background:var(--cell);min-width:max-content;color:var(--muted);font-family:var(--sp-font);letter-spacing:.13em;text-transform:uppercase;cursor:pointer;border:0;flex:auto;padding:11px 14px;font-size:9.5px;font-weight:700;transition:color .2s,box-shadow .2s;box-shadow:inset 0 -2px #0000}.swiss .sy-tab.on{color:var(--text);box-shadow:inset 0 -2px 0 var(--text)}@media print{.swiss{--cell:#fff;--ink-band:#fff;--text:#000;--muted:#444;--band-muted:#444;--chrome-text:#000;--rule:#bbb;--hair:#ddd;--accent:#a3282c;--accent-block:#a3282c;--on-block:#fff;background:#fff}.swiss.sp-screen,.swiss.sp-screen main,.swiss .vy,.swiss .lb{height:auto!important;min-height:0!important;overflow:visible!important}.swiss .sp-rail,.swiss .tabbar,.swiss .head .back,.swiss .umenu-btn,.swiss .vy-fuelsrc,.swiss .vy-fuel-sel,.swiss .vy-m-track,.swiss .vy-m-edit,.swiss .pair,.swiss .vy-map,.swiss .vy-track-empty{display:none!important}.swiss .vy-print-hd{border-bottom:1px solid var(--rule);justify-content:space-between;align-items:baseline;margin-bottom:10px;padding:0 16px 10px;display:flex}.swiss .vy-print-hd .b{font-family:var(--sp-font);letter-spacing:.06em;text-transform:uppercase;color:var(--text);font-size:13px;font-weight:700}.swiss .vy-print-hd .d{font-family:var(--sp-mono);color:var(--muted);font-size:10px}.swiss .vy-seg button:not(.on){display:none}.swiss .vy-seg button.on{flex:none}.swiss .vy-rowwrap,.swiss .vy-active,.swiss .vy-cards{break-inside:avoid}@page{margin:12mm}}
|