mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-16 10:48:47 +00:00
1648 lines
84 KiB
HTML
1648 lines
84 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Eclipse Azimuth Sailing Analytics — Developer Hub</title>
|
||
<meta name="description" content="Open-source platform for live visualization, analysis, and broadcasting of sailing regattas. Proven at the 2020 and 2024 Olympics.">
|
||
<link rel="icon" href="source/favicon-light.png" type="image/png" media="(prefers-color-scheme: light)">
|
||
<link rel="icon" href="source/favicon-dark.png" type="image/png" media="(prefers-color-scheme: dark)">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* ── Brand tokens ── */
|
||
:root {
|
||
--blue: #008fcc;
|
||
--blue-dark: #006fa0;
|
||
--blue-light: #e6f5fc;
|
||
--yellow: #efaa00;
|
||
--yellow-dark:#c88e00;
|
||
--dark: #333333;
|
||
--dark-mid: #444444;
|
||
--text: #212529;
|
||
--muted: #666666;
|
||
--border: #e0e0e0;
|
||
--surface: #f8f8f8;
|
||
--light: #f6f9fc;
|
||
--white: #ffffff;
|
||
--radius: 8px;
|
||
--radius-lg: 16px;
|
||
--shadow-sm: 0 2px 8px rgba(0,0,0,.08);
|
||
--shadow-md: 0 6px 24px rgba(0,0,0,.12);
|
||
--shadow-lg: 0 12px 48px rgba(0,0,0,.16);
|
||
--transition: .2s ease;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
font-family: 'Open Sans', system-ui, sans-serif;
|
||
color: var(--text);
|
||
background: var(--white);
|
||
background-image:
|
||
radial-gradient(ellipse 340px 600px at 0% 50%, rgba(0,143,204,.06) 0%, transparent 100%),
|
||
radial-gradient(ellipse 340px 600px at 100% 50%, rgba(0,143,204,.06) 0%, transparent 100%);
|
||
line-height: 1.6;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
img { max-width: 100%; display: block; }
|
||
a { color: var(--blue); text-decoration: none; }
|
||
a:hover { text-decoration: underline; }
|
||
|
||
.container { max-width: 1160px; margin: 0 auto; padding: 0 24px; }
|
||
.section { padding: 80px 0; }
|
||
.section--alt { background: var(--surface); }
|
||
.section--light { background: var(--light); }
|
||
|
||
.eyebrow {
|
||
font-size: .75rem; font-weight: 700; letter-spacing: .12em;
|
||
text-transform: uppercase; color: var(--blue); margin-bottom: 10px;
|
||
}
|
||
.eyebrow--yellow { color: var(--yellow-dark); }
|
||
h1, h2, h3 { line-height: 1.25; color: var(--dark); }
|
||
h1 { font-size: clamp(2rem, 4.5vw, 3.2rem); font-weight: 800; }
|
||
h2 { font-size: clamp(1.5rem, 3vw, 2.1rem); font-weight: 700; }
|
||
h3 { font-size: 1.05rem; font-weight: 700; }
|
||
p { color: var(--muted); }
|
||
.lead { font-size: 1.1rem; line-height: 1.75; color: var(--muted); }
|
||
|
||
/* ── Buttons ── */
|
||
.btn {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
padding: 11px 24px; border-radius: 100px;
|
||
font-weight: 700; font-size: .9rem; transition: var(--transition);
|
||
cursor: pointer; border: 2px solid transparent; white-space: nowrap;
|
||
}
|
||
.btn:hover { text-decoration: none; transform: translateY(-2px); }
|
||
.btn-primary { background: var(--blue); color: var(--white); box-shadow: 0 2px 12px rgba(0,143,204,.3); }
|
||
.btn-primary:hover { background: var(--blue-dark); box-shadow: 0 4px 20px rgba(0,143,204,.4); color: var(--white); }
|
||
.btn-yellow { background: var(--yellow); color: var(--white); box-shadow: 0 2px 12px rgba(239,170,0,.3); }
|
||
.btn-yellow:hover { background: var(--yellow-dark); color: var(--white); }
|
||
.btn-outline-white { border-color: rgba(255,255,255,.6); color: var(--white); }
|
||
.btn-outline-white:hover { background: rgba(255,255,255,.12); color: var(--white); }
|
||
.btn-ghost { border-color: var(--border); color: var(--dark); background: var(--white); }
|
||
.btn-ghost:hover { border-color: var(--blue); color: var(--blue); }
|
||
|
||
/* ════════════ NAV ════════════ */
|
||
.nav {
|
||
position: sticky; top: 0; z-index: 200;
|
||
background: linear-gradient(135deg, #006fa0 0%, #008fcc 45%, #0099d6 60%, #006fa0 100%);
|
||
box-shadow: 0 2px 16px rgba(0,0,0,.22);
|
||
}
|
||
.nav__inner {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
height: 68px;
|
||
}
|
||
.nav__brand img { height: 44px; width: auto; }
|
||
.nav__links {
|
||
display: flex; align-items: center; gap: 24px;
|
||
}
|
||
.nav__links a {
|
||
font-size: .875rem; font-weight: 600; color: rgba(255,255,255,.9);
|
||
transition: color var(--transition); padding: 4px 0;
|
||
border-bottom: 2px solid transparent;
|
||
}
|
||
.nav__links a:hover { color: var(--white); text-decoration: none; border-bottom-color: var(--yellow); }
|
||
.nav__cta {
|
||
margin-left: 8px; padding: 8px 18px; font-size: .85rem;
|
||
background: var(--white); border-color: var(--white); color: var(--blue-dark) !important;
|
||
font-weight: 700;
|
||
}
|
||
.nav__cta svg { color: var(--blue-dark) !important; }
|
||
.nav__cta:hover { background: rgba(255,255,255,.9) !important; color: var(--blue-dark) !important; }
|
||
|
||
/* ════════════ HERO ════════════ */
|
||
.hero {
|
||
position: relative; overflow: hidden;
|
||
min-height: 580px;
|
||
display: flex; align-items: center;
|
||
background: var(--dark);
|
||
}
|
||
.hero__bg {
|
||
position: absolute; inset: 0; overflow: hidden;
|
||
}
|
||
.hero__bg img {
|
||
width: 100%; height: 100%; object-fit: cover; object-position: center 40%;
|
||
opacity: .38;
|
||
}
|
||
.hero__content {
|
||
position: relative; z-index: 1;
|
||
padding: 80px 0;
|
||
max-width: 680px;
|
||
}
|
||
.hero__badge {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.2);
|
||
border-radius: 100px; padding: 5px 14px;
|
||
font-size: .78rem; font-weight: 700; color: rgba(255,255,255,.9);
|
||
letter-spacing: .06em; text-transform: uppercase; margin-bottom: 22px;
|
||
}
|
||
.hero__badge-dot {
|
||
width: 7px; height: 7px; border-radius: 50%; background: #4caf50;
|
||
box-shadow: 0 0 0 3px rgba(76,175,80,.3);
|
||
}
|
||
.hero h1 { color: var(--white); margin-bottom: 18px; }
|
||
.hero h1 span { color: var(--yellow); }
|
||
.hero__sub { font-size: 1.1rem; color: rgba(255,255,255,.75); line-height: 1.7; margin-bottom: 36px; max-width: 560px; }
|
||
.hero__actions { display: flex; gap: 12px; flex-wrap: wrap; }
|
||
.hero__stats {
|
||
display: flex; gap: 40px; flex-wrap: wrap;
|
||
margin-top: 52px; padding-top: 32px;
|
||
border-top: 1px solid rgba(255,255,255,.15);
|
||
}
|
||
.hero__stat-value { font-size: 2rem; font-weight: 800; color: var(--white); line-height: 1.1; }
|
||
.hero__stat-label { font-size: .78rem; color: rgba(255,255,255,.75); font-weight: 600; text-transform: uppercase; letter-spacing: .08em; margin-top: 4px; }
|
||
|
||
/* ════════════ SOLUTIONS CAROUSEL ════════════ */
|
||
.solutions__carousel-wrap {
|
||
position: relative; margin-top: 48px;
|
||
}
|
||
.solutions__track-outer {
|
||
overflow: hidden; border-radius: var(--radius-lg);
|
||
}
|
||
.solutions__track {
|
||
display: flex; gap: 20px;
|
||
transition: transform .5s cubic-bezier(.4,0,.2,1);
|
||
cursor: grab; user-select: none;
|
||
}
|
||
.solutions__track.dragging { transition: none; cursor: grabbing; }
|
||
.sol-card {
|
||
flex: 0 0 calc(50% - 10px);
|
||
border-radius: var(--radius-lg);
|
||
overflow: hidden;
|
||
background: var(--dark);
|
||
box-shadow: var(--shadow-md);
|
||
position: relative;
|
||
display: flex; flex-direction: column;
|
||
transition: transform .25s, box-shadow .25s;
|
||
}
|
||
.sol-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
|
||
.sol-card__img {
|
||
width: 100%; aspect-ratio: 16/10; overflow: hidden;
|
||
background: #1a2a3a; position: relative;
|
||
}
|
||
.sol-card__img img {
|
||
width: 100%; height: 100%; object-fit: cover;
|
||
transition: transform .4s ease;
|
||
display: block;
|
||
}
|
||
.sol-card:hover .sol-card__img img { transform: scale(1.04); }
|
||
.sol-card__body {
|
||
padding: 22px 24px 24px;
|
||
background: linear-gradient(160deg, #1c2e3e 0%, #0d1f2d 100%);
|
||
flex: 1; display: flex; flex-direction: column;
|
||
}
|
||
.sol-card__tag {
|
||
display: inline-block;
|
||
font-size: .68rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
|
||
padding: 3px 9px; border-radius: 100px; margin-bottom: 10px;
|
||
border: 1px solid;
|
||
}
|
||
.sol-card__tag--web { color: var(--blue); border-color: rgba(0,143,204,.4); background: rgba(0,143,204,.12); }
|
||
.sol-card__tag--android{ color: #66bb6a; border-color: rgba(102,187,106,.4); background: rgba(102,187,106,.1); }
|
||
.sol-card__tag--ios { color: #ce93d8; border-color: rgba(206,147,216,.4); background: rgba(206,147,216,.1); }
|
||
.sol-card h3 { font-size: 1.1rem; color: #fff; margin-bottom: 8px; }
|
||
.sol-card p { font-size: .85rem; color: rgba(255,255,255,.6); line-height: 1.65; flex: 1; margin-bottom: 18px; }
|
||
.sol-card__links { display: flex; gap: 8px; flex-wrap: wrap; }
|
||
.sol-card__links .btn { padding: 7px 16px; font-size: .8rem; }
|
||
.sol-card__links .btn-ghost {
|
||
border-color: rgba(255,255,255,.2); color: rgba(255,255,255,.8); background: transparent;
|
||
}
|
||
.sol-card__links .btn-ghost:hover { border-color: var(--blue); color: var(--blue); }
|
||
.sol-card__store {
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
padding: 7px 14px; border-radius: 100px;
|
||
background: transparent; border: 1.5px solid rgba(255,255,255,.22);
|
||
color: rgba(255,255,255,.7); font-size: .78rem; font-weight: 600;
|
||
transition: border-color .15s, color .15s; white-space: nowrap;
|
||
}
|
||
.sol-card__store:hover { border-color: rgba(255,255,255,.55); color: #fff; text-decoration: none; }
|
||
.sol-card__store svg { flex-shrink: 0; opacity: .8; }
|
||
.solutions__nav {
|
||
display: flex; align-items: center; justify-content: center;
|
||
gap: 16px; margin-top: 28px;
|
||
}
|
||
.solutions__arrow {
|
||
width: 40px; height: 40px; border-radius: 50%; border: 2px solid var(--border);
|
||
background: var(--white); cursor: pointer; display: flex; align-items: center;
|
||
justify-content: center; font-size: 1.1rem; transition: border-color .18s, background .18s;
|
||
color: var(--dark);
|
||
}
|
||
.solutions__arrow:hover { border-color: var(--blue); background: var(--blue-light); color: var(--blue); }
|
||
.solutions__arrow:disabled { opacity: .35; cursor: default; pointer-events: none; }
|
||
.solutions__dots { display: flex; gap: 7px; }
|
||
.solutions__dot {
|
||
width: 8px; height: 8px; border-radius: 50%; border: none; padding: 0; cursor: pointer;
|
||
background: var(--border); transition: background .2s, transform .2s;
|
||
}
|
||
.solutions__dot.active { background: var(--blue); transform: scale(1.25); }
|
||
|
||
/* ════════════ MODAL ════════════ */
|
||
.modal-overlay {
|
||
display: none; position: fixed; inset: 0; z-index: 1000;
|
||
background: rgba(0,0,0,.5);
|
||
align-items: center; justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
.modal-overlay.open { display: flex; }
|
||
.modal {
|
||
background: var(--white);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: var(--shadow-lg);
|
||
width: 100%; max-width: 540px;
|
||
overflow: hidden;
|
||
animation: modalIn .2s ease;
|
||
}
|
||
@keyframes modalIn {
|
||
from { opacity: 0; transform: translateY(16px) scale(.97); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
.modal__header {
|
||
background: linear-gradient(135deg, #006fa0 0%, #008fcc 100%);
|
||
color: var(--white);
|
||
padding: 20px 24px;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
}
|
||
.modal__header-left { display: flex; flex-direction: column; gap: 4px; }
|
||
.modal__header-title { font-size: 1.1rem; font-weight: 800; }
|
||
.modal__header-sub { font-size: .78rem; color: rgba(255,255,255,.7); }
|
||
.modal__close {
|
||
background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.2);
|
||
color: var(--white); width: 30px; height: 30px; border-radius: 50%;
|
||
font-size: 1.1rem; cursor: pointer; line-height: 1;
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: background var(--transition); flex-shrink: 0;
|
||
}
|
||
.modal__close:hover { background: rgba(255,255,255,.25); }
|
||
.modal__body { padding: 20px 24px 24px; }
|
||
.modal__cards { display: grid; gap: 10px; margin-bottom: 14px; }
|
||
.modal__cards--2 { grid-template-columns: 1fr 1fr; }
|
||
.modal__card {
|
||
display: flex; align-items: center; gap: 12px;
|
||
background: var(--light); border: 1px solid var(--border);
|
||
border-radius: var(--radius); padding: 13px 16px;
|
||
text-decoration: none; color: var(--dark);
|
||
transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
|
||
}
|
||
.modal__card:hover { border-color: var(--blue); box-shadow: 0 2px 12px rgba(0,143,204,.12); background: #f0f9ff; text-decoration: none; }
|
||
.modal__card-icon {
|
||
width: 36px; height: 36px; border-radius: 8px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1.1rem; flex-shrink: 0;
|
||
}
|
||
.modal__card-icon--blue { background: var(--blue-light); }
|
||
.modal__card-icon--yellow { background: #fffbe6; }
|
||
.modal__card-icon--green { background: #e8f5e9; }
|
||
.modal__card-icon--purple { background: #f3e5f5; }
|
||
.modal__card-text { flex: 1; }
|
||
.modal__card-title { font-size: .85rem; font-weight: 700; color: var(--dark); line-height: 1.3; }
|
||
.modal__card-desc { font-size: .75rem; color: var(--muted); line-height: 1.3; margin-top: 2px; }
|
||
.modal__card-arrow { color: var(--blue); font-size: .9rem; opacity: 0; transition: opacity var(--transition), transform var(--transition); }
|
||
.modal__card:hover .modal__card-arrow { opacity: 1; transform: translateX(3px); }
|
||
.modal__section-label {
|
||
font-size: .68rem; font-weight: 700; text-transform: uppercase;
|
||
letter-spacing: .1em; color: var(--muted); margin: 16px 0 8px;
|
||
}
|
||
.modal__store-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||
.modal__store-btn {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
background: var(--dark); color: var(--white);
|
||
border-radius: var(--radius); padding: 8px 14px;
|
||
font-size: .8rem; font-weight: 600;
|
||
transition: background var(--transition);
|
||
}
|
||
.modal__store-btn:hover { background: #444; text-decoration: none; color: var(--white); }
|
||
.modal__store-btn img[src*="logo-app-store"], .sol-card__store img[src*="logo-app-store"] { filter: invert(1); }
|
||
/* Split store button */
|
||
.store-split {
|
||
display: inline-flex; border-radius: var(--radius); overflow: hidden;
|
||
border: 1px solid #555;
|
||
}
|
||
.store-split__half {
|
||
display: inline-flex; align-items: center; gap: 7px;
|
||
background: var(--dark); color: var(--white);
|
||
padding: 8px 14px; font-size: .8rem; font-weight: 600;
|
||
text-decoration: none; transition: background var(--transition);
|
||
white-space: nowrap;
|
||
}
|
||
.store-split__half:hover { background: #444; color: var(--white); text-decoration: none; }
|
||
.store-split__half + .store-split__half { border-left: 1px solid #555; }
|
||
.store-split__half img[src*="logo-app-store"] { filter: invert(1); }
|
||
/* Same split style for sol-cards (dark background) */
|
||
.sol-card__store-split {
|
||
display: inline-flex; border-radius: 100px; overflow: hidden;
|
||
border: 1.5px solid rgba(255,255,255,.22);
|
||
}
|
||
.sol-card__store-split__half {
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
background: transparent; color: rgba(255,255,255,.7);
|
||
padding: 7px 12px; font-size: .78rem; font-weight: 600;
|
||
text-decoration: none; transition: background .15s, color .15s;
|
||
white-space: nowrap;
|
||
}
|
||
.sol-card__store-split__half:hover { background: rgba(255,255,255,.08); color: #fff; text-decoration: none; }
|
||
.sol-card__store-split__half + .sol-card__store-split__half { border-left: 1px solid rgba(255,255,255,.22); }
|
||
.sol-card__store-split__half img[src*="logo-app-store"] { filter: invert(1); opacity: .8; }
|
||
|
||
/* ── Modal image slider ── */
|
||
.modal__img-slider { position: relative; background: #111; overflow: hidden; height: 280px; }
|
||
.modal__img-track { display: flex; height: 100%; transition: transform .4s cubic-bezier(.4,0,.2,1); }
|
||
.modal__img-track img { width: 100%; flex-shrink: 0; object-fit: cover; height: 280px; display: block; }
|
||
.modal__img-btn {
|
||
position: absolute; top: 50%; transform: translateY(-50%);
|
||
background: rgba(0,0,0,.45); border: none; color: #fff;
|
||
width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
|
||
font-size: 1rem; display: flex; align-items: center; justify-content: center;
|
||
z-index: 2; transition: background .15s;
|
||
}
|
||
.modal__img-btn:hover { background: rgba(0,143,204,.7); }
|
||
.modal__img-btn--prev { left: 8px; }
|
||
.modal__img-btn--next { right: 8px; }
|
||
.modal__img-dots {
|
||
position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);
|
||
display: flex; gap: 5px;
|
||
}
|
||
.modal__img-dot {
|
||
width: 6px; height: 6px; border-radius: 50%; border: none; padding: 0;
|
||
background: rgba(255,255,255,.45); cursor: pointer; transition: background .15s;
|
||
}
|
||
.modal__img-dot.active { background: #fff; }
|
||
|
||
/* ════════════ DOCS CARD GRID ════════════ */
|
||
.docs__grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
margin-top: 48px;
|
||
}
|
||
.docs__card {
|
||
background: var(--white);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: var(--shadow-sm);
|
||
overflow: hidden;
|
||
}
|
||
.docs__card-head {
|
||
display: flex; align-items: center; gap: 12px;
|
||
padding: 18px 20px 16px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.docs__card-icon {
|
||
width: 38px; height: 38px; border-radius: 10px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1.2rem; flex-shrink: 0;
|
||
}
|
||
.docs__card-head h3 { font-size: .95rem; color: var(--dark); margin: 0; }
|
||
.docs__card-links { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 2px; }
|
||
.docs__link {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 9px 10px;
|
||
border-radius: 6px;
|
||
font-size: .84rem; font-weight: 600; color: var(--dark-mid);
|
||
transition: background .15s, color .15s;
|
||
text-decoration: none;
|
||
}
|
||
.docs__link:hover { background: var(--light); color: var(--blue); text-decoration: none; }
|
||
.docs__link-arrow { font-size: .75rem; opacity: 0; transition: opacity .15s, transform .15s; flex-shrink: 0; }
|
||
.docs__link:hover .docs__link-arrow { opacity: 1; transform: translateX(3px); }
|
||
@media (max-width: 900px) { .docs__grid { grid-template-columns: repeat(2, 1fr); } }
|
||
@media (max-width: 560px) { .docs__grid { grid-template-columns: 1fr; } }
|
||
/* ════════════ TECH STACK ════════════ */
|
||
.tech__grid {
|
||
display: flex; flex-wrap: wrap; gap: 16px;
|
||
justify-content: center; margin-top: 48px;
|
||
}
|
||
.tech-item {
|
||
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
||
background: var(--white); border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg); padding: 20px 20px 16px;
|
||
min-width: 120px; max-width: 148px;
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
.tech-item img { width: 40px; height: 40px; object-fit: contain; }
|
||
.tech-item span { font-size: .78rem; font-weight: 700; color: var(--dark); text-align: center; }
|
||
.tech-item small { font-size: .7rem; color: var(--muted); text-align: center; line-height: 1.4; }
|
||
/* Tech items on dark background */
|
||
.getting-started .tech-item {
|
||
background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.12);
|
||
box-shadow: none;
|
||
}
|
||
.getting-started .tech-item span { color: rgba(255,255,255,.9); }
|
||
.getting-started .tech-item small { color: rgba(255,255,255,.5); }
|
||
/* Java and Jetty use dark #333 — invert to white on dark bg */
|
||
.getting-started .tech-item--invert img { filter: brightness(0) invert(1) opacity(.85); }
|
||
|
||
/* ════════════ DOCS GRAPH ════════════ */
|
||
|
||
/* ════════════ WHAT'S NEW ════════════ */
|
||
.accordion { margin-top: 48px; display: flex; flex-direction: column; gap: 0; border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-md); border: 1px solid var(--border); }
|
||
.accordion-item { border-bottom: 1px solid var(--border); }
|
||
.accordion-item:last-child { border-bottom: none; }
|
||
.accordion-header {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 20px 28px; background: var(--white);
|
||
cursor: pointer; user-select: none;
|
||
transition: background var(--transition);
|
||
border-left: 4px solid transparent;
|
||
}
|
||
.accordion-header:hover { background: var(--light); }
|
||
.accordion-header.open {
|
||
background: linear-gradient(135deg, #1c2e3e 0%, #0d1f2d 100%);
|
||
border-left-color: var(--blue);
|
||
}
|
||
.accordion-title {
|
||
display: flex; align-items: center; gap: 14px;
|
||
font-size: 1rem; font-weight: 700; color: var(--dark);
|
||
}
|
||
.accordion-header.open .accordion-title { color: var(--white); }
|
||
.accordion-tag {
|
||
font-size: .68rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
|
||
padding: 3px 10px; border-radius: 100px;
|
||
}
|
||
.tag-web { background: rgba(0,143,204,.12); color: var(--blue); border: 1px solid rgba(0,143,204,.25); }
|
||
.tag-android{ background: rgba(46,125,50,.1); color: #2e7d32; border: 1px solid rgba(46,125,50,.2); }
|
||
.tag-ios { background: rgba(106,27,154,.08); color: #6a1b9a; border: 1px solid rgba(106,27,154,.18); }
|
||
.accordion-header.open .tag-web { background: rgba(0,143,204,.2); color: #82d2ff; border-color: rgba(0,143,204,.4); }
|
||
.accordion-header.open .tag-android{ background: rgba(102,187,106,.15); color: #81c784; border-color: rgba(102,187,106,.3); }
|
||
.accordion-header.open .tag-ios { background: rgba(206,147,216,.12); color: #ce93d8; border-color: rgba(206,147,216,.3); }
|
||
.accordion-arrow {
|
||
font-size: 1.1rem; color: var(--muted); transition: transform var(--transition);
|
||
}
|
||
.accordion-header.open .accordion-arrow { transform: rotate(180deg); color: rgba(255,255,255,.6); }
|
||
.accordion-body {
|
||
display: none; background: var(--white);
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
.accordion-body.open { display: block; }
|
||
/* Styles applied to the injected source HTML content */
|
||
.whatsnew-content { padding: 8px 28px 24px; }
|
||
.whatsnew-content h5, .whatsnew-content .articleSubheadline {
|
||
font-size: .78rem; font-weight: 700; text-transform: uppercase;
|
||
letter-spacing: .08em; color: var(--blue);
|
||
margin: 20px 0 8px; padding: 0;
|
||
}
|
||
.whatsnew-content ul, .whatsnew-content .bulletList {
|
||
list-style: none; display: flex; flex-direction: column; gap: 6px;
|
||
padding: 0; margin: 0;
|
||
}
|
||
.whatsnew-content li {
|
||
font-size: .875rem; color: var(--muted); line-height: 1.6;
|
||
padding-left: 14px; position: relative;
|
||
}
|
||
.whatsnew-content li::before { content: '–'; position: absolute; left: 0; color: var(--yellow); font-weight: 700; }
|
||
.whatsnew-content a { color: var(--blue); }
|
||
.whatsnew-content h4, .whatsnew-content .articleHeadline { display: none; }
|
||
.whatsnew-content tt, .whatsnew-content code {
|
||
font-family: 'JetBrains Mono', monospace; font-size: .82em;
|
||
background: #f0f4f8; border-radius: 3px; padding: 1px 4px; color: #c7254e;
|
||
}
|
||
.whatsnew-content p { display: inline; }
|
||
.accordion-loading {
|
||
padding: 24px; text-align: center; color: var(--muted); font-size: .85rem;
|
||
}
|
||
/* Floating close button for open accordions */
|
||
.accordion-close-fab { display: none; }
|
||
/* Sticky "Close all" bar — fixed, only visible while an accordion in #whatsnew is open */
|
||
.whatsnew__header {
|
||
display: flex; align-items: flex-end; justify-content: space-between;
|
||
flex-wrap: wrap; gap: 12px; margin-bottom: 0;
|
||
}
|
||
.whatsnew__sticky-bar {
|
||
display: none;
|
||
position: sticky; top: 68px; z-index: 100;
|
||
justify-content: flex-end;
|
||
pointer-events: none;
|
||
margin-bottom: -48px; padding-top: 8px;
|
||
}
|
||
.whatsnew__sticky-bar.visible { display: flex; pointer-events: auto; }
|
||
.whatsnew__close-btn {
|
||
display: flex; align-items: center; gap: 6px;
|
||
background: var(--blue); color: var(--white);
|
||
border: none; border-radius: 100px; padding: 9px 20px;
|
||
font-size: .82rem; font-weight: 700;
|
||
cursor: pointer; box-shadow: 0 2px 16px rgba(0,143,204,.35);
|
||
transition: background .15s; white-space: nowrap;
|
||
}
|
||
.whatsnew__close-btn:hover { background: var(--blue-dark); }
|
||
|
||
/* ════════════ GETTING STARTED ════════════ */
|
||
.getting-started { background: var(--dark); color: var(--white); }
|
||
.getting-started .eyebrow { color: var(--yellow); }
|
||
.getting-started h2 { color: var(--white); }
|
||
.getting-started .lead { color: rgba(255,255,255,.7); }
|
||
.steps { display: grid; grid-template-columns: repeat(3,1fr); gap: 24px; margin-top: 48px; }
|
||
.step {
|
||
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
|
||
border-radius: var(--radius-lg); padding: 28px;
|
||
}
|
||
.step__num {
|
||
width: 34px; height: 34px; border-radius: 50%;
|
||
background: var(--blue); color: var(--white);
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: .85rem; font-weight: 700; margin-bottom: 16px;
|
||
}
|
||
.step h3 { color: var(--white); font-size: .95rem; margin-bottom: 8px; }
|
||
.step p { color: rgba(255,255,255,.6); font-size: .875rem; }
|
||
.code-block {
|
||
margin-top: 40px;
|
||
background: rgba(0,0,0,.3); border: 1px solid rgba(255,255,255,.1);
|
||
border-radius: var(--radius-lg); overflow: hidden;
|
||
}
|
||
.code-block__bar {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 12px 18px; background: rgba(255,255,255,.04);
|
||
border-bottom: 1px solid rgba(255,255,255,.08);
|
||
}
|
||
.code-block__bar span { width: 10px; height: 10px; border-radius: 50%; }
|
||
.code-block__bar span:nth-child(1) { background: #ff5f57; }
|
||
.code-block__bar span:nth-child(2) { background: #ffbd2e; }
|
||
.code-block__bar span:nth-child(3) { background: #28c840; }
|
||
.code-block pre {
|
||
padding: 22px 24px;
|
||
font-family: 'JetBrains Mono', monospace; font-size: .875rem;
|
||
line-height: 1.8; color: rgba(255,255,255,.85); overflow-x: auto;
|
||
}
|
||
.cc { color: rgba(255,255,255,.35); }
|
||
.ck { color: #4caf50; }
|
||
.cv { color: var(--yellow); }
|
||
|
||
/* ════════════ FOOTER ════════════ */
|
||
.footer { background: var(--dark); color: rgba(255,255,255,.6); padding: 56px 0 28px; }
|
||
.footer__grid {
|
||
display: grid; grid-template-columns: 2fr 1fr 1fr 1fr;
|
||
gap: 48px; margin-bottom: 48px;
|
||
}
|
||
.footer__brand img { height: 36px; margin-bottom: 16px; }
|
||
.footer__brand p { font-size: .875rem; line-height: 1.7; max-width: 260px; color: rgba(255,255,255,.82); }
|
||
.footer__social { display: flex; gap: 12px; margin-top: 20px; }
|
||
.footer__social a {
|
||
width: 36px; height: 36px; border-radius: 50%;
|
||
background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12);
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: background var(--transition);
|
||
}
|
||
.footer__social a:hover { background: rgba(255,255,255,.18); text-decoration: none; }
|
||
.footer__social img { height: 18px; width: auto; filter: invert(1) brightness(2); }
|
||
.footer__col h4 {
|
||
font-size: .75rem; font-weight: 700; text-transform: uppercase;
|
||
letter-spacing: .09em; color: rgba(255,255,255,.35); margin-bottom: 14px;
|
||
}
|
||
.footer__col ul { list-style: none; display: flex; flex-direction: column; gap: 9px; }
|
||
.footer__col ul li a {
|
||
font-size: .875rem; color: rgba(255,255,255,.6);
|
||
transition: color var(--transition);
|
||
}
|
||
.footer__col ul li a:hover { color: var(--white); text-decoration: none; }
|
||
.footer__bottom {
|
||
border-top: 1px solid rgba(255,255,255,.1);
|
||
padding-top: 22px;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
flex-wrap: wrap; gap: 12px;
|
||
}
|
||
.footer__bottom p { font-size: .8rem; color: rgba(255,255,255,.4); }
|
||
.footer__bottom-links { display: flex; gap: 20px; flex-wrap: wrap; }
|
||
.footer__bottom-links a { font-size: .8rem; color: rgba(255,255,255,.4); transition: color var(--transition); }
|
||
.footer__bottom-links a:hover { color: var(--white); text-decoration: none; }
|
||
|
||
/* ════════════ RESPONSIVE ════════════ */
|
||
@media (max-width: 1024px) {
|
||
.footer__grid { grid-template-columns: 1fr 1fr; gap: 32px; }
|
||
}
|
||
@media (max-width: 768px) {
|
||
.section { padding: 56px 0; }
|
||
.steps { grid-template-columns: 1fr; }
|
||
.solutions__track .sol-card { flex: 0 0 calc(100% - 0px); }
|
||
.footer__grid { grid-template-columns: 1fr; }
|
||
.nav__links { display: none; }
|
||
.hero__stats { gap: 24px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ═══════════════════════════════════ NAV ═══════════════════════════════════ -->
|
||
<nav class="nav">
|
||
<div class="container nav__inner">
|
||
<a href="#" style="display:flex;align-items:center;gap:10px;text-decoration:none;">
|
||
<img src="source/logo-azimuth-no-bg.png" alt="Azimuth" style="height:48px;width:auto;filter:brightness(0) invert(1);">
|
||
</a>
|
||
<div class="nav__links">
|
||
<a href="#solutions">Solutions</a>
|
||
<a href="#getting-started">Quick Start</a>
|
||
<a href="#docs">Documentation</a>
|
||
<a href="#tech">Technology</a>
|
||
<a href="#whatsnew">What's New</a>
|
||
<a class="btn btn-ghost nav__cta" href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>GitHub</a>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- ═══════════════════════════════════ HERO ═══════════════════════════════════ -->
|
||
<section class="hero">
|
||
<div class="hero__bg"><img src="source/regatta-bg.webp" alt=""></div>
|
||
<div class="container">
|
||
<div class="hero__content">
|
||
<a class="hero__badge" href="https://github.com/eclipse-sailing-analytics/sailing-analytics?tab=Apache-2.0-1-ov-file#" target="_blank" rel="noopener" style="text-decoration:none;">
|
||
<span class="hero__badge-dot"></span>
|
||
Open Source — Apache 2.0
|
||
</a>
|
||
<h1>Open-source analytics for <span>competitive sailing</span></h1>
|
||
<p class="hero__sub">
|
||
Eclipse Azimuth Sailing Analytics is a community-driven platform for live GPS tracking,
|
||
real-time race analysis, and fan engagement — from club regattas to the Olympic Games.
|
||
Free to use, free to contribute.
|
||
</p>
|
||
<div class="hero__actions">
|
||
<a class="btn btn-primary" href="#getting-started">Get Started</a>
|
||
<a class="btn btn-outline-white" href="https://www.sapsailing.com/gwt/Home.html" target="_blank" rel="noopener">Open Application</a>
|
||
<a class="btn btn-outline-white" href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener">View on GitHub</a>
|
||
</div>
|
||
<div class="hero__stats">
|
||
<div>
|
||
<div class="hero__stat-value">2×</div>
|
||
<div class="hero__stat-label">Olympic Games</div>
|
||
</div>
|
||
<div>
|
||
<div class="hero__stat-value">~1M</div>
|
||
<div class="hero__stat-label">Olympic visitors</div>
|
||
</div>
|
||
<div>
|
||
<div class="hero__stat-value">15+</div>
|
||
<div class="hero__stat-label">GPS connectors</div>
|
||
</div>
|
||
<div>
|
||
<div class="hero__stat-value">225+</div>
|
||
<div class="hero__stat-label">OSGi modules</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════════════════ SOLUTIONS ══════════════════════════════ -->
|
||
<section class="section" id="solutions">
|
||
<div class="container">
|
||
<p class="eyebrow eyebrow--yellow">Solutions</p>
|
||
<h2>A full suite of sailing tools</h2>
|
||
<p class="lead" style="max-width:600px;margin-top:10px;">
|
||
From the main cloud platform to companion mobile apps, every component is open-source
|
||
and designed to work together.
|
||
</p>
|
||
|
||
<div class="solutions__carousel-wrap">
|
||
<div class="solutions__track-outer">
|
||
<div class="solutions__track" id="sol-track">
|
||
|
||
<!-- Sailing Analytics -->
|
||
<div class="sol-card">
|
||
<div class="sol-card__img">
|
||
<img src="source/sailing-analytics-Photoroom.png" alt="Eclipse Azimuth Sailing Analytics">
|
||
</div>
|
||
<div class="sol-card__body">
|
||
<span class="sol-card__tag sol-card__tag--web">Web Platform</span>
|
||
<h3>Eclipse Azimuth Sailing Analytics</h3>
|
||
<p>Real-time GPS analytics for rankings, speeds, maneuvers, and bearings — from club regattas to the Olympic Games. Processes live telemetry in the cloud and visualizes it in multiple frontends.</p>
|
||
<div class="sol-card__links">
|
||
<button class="btn btn-primary" onclick="openModal('modal-sa')">Learn More</button>
|
||
<a class="sol-card__store" href="https://www.sapsailing.com/gwt/Home.html" target="_blank" rel="noopener"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>Open App</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Race Manager -->
|
||
<div class="sol-card">
|
||
<div class="sol-card__img">
|
||
<img src="source/sailing-race-manager-1.png" alt="Sailing Race Manager">
|
||
</div>
|
||
<div class="sol-card__body">
|
||
<span class="sol-card__tag sol-card__tag--android">Android App</span>
|
||
<h3>Sailing Race Manager</h3>
|
||
<p>Android app for race committees. Manage start sequences, record finishes, handle flags, and sync results directly to the Sailing Analytics cloud from a tablet on the committee boat.</p>
|
||
<div class="sol-card__links">
|
||
<button class="btn btn-primary" onclick="openModal('modal-rm')">Learn More</button>
|
||
<a class="sol-card__store" href="https://play.google.com/store/apps/details?id=com.sap.sailing.racecommittee.app" target="_blank" rel="noopener"><img src="source/logo-google-play.png" width="13" height="13" alt="" style="display:inline-block;vertical-align:middle;">Google Play</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sail Insight -->
|
||
<div class="sol-card">
|
||
<div class="sol-card__img">
|
||
<img src="source/sail-insight-1-Photoroom.png" alt="Sail Insight">
|
||
</div>
|
||
<div class="sol-card__body">
|
||
<span class="sol-card__tag sol-card__tag--ios">iOS & Android App</span>
|
||
<h3>Sail Insight</h3>
|
||
<p>Sailors can join, set up, and manage GPS tracking for regattas of various formats. One-design and handicap regattas supported. Connects to the Eclipse Azimuth Sailing Analytics cloud for deep analysis.</p>
|
||
<div class="sol-card__links">
|
||
<button class="btn btn-primary" onclick="openModal('modal-si')">Learn More</button>
|
||
<div class="sol-card__store-split">
|
||
<a class="sol-card__store-split__half" href="https://apps.apple.com/us/app/sail-insight-powered-by-sap/id1495355086" target="_blank" rel="noopener"><img src="source/logo-app-store.png" width="12" height="12" alt="">App Store</a>
|
||
<a class="sol-card__store-split__half" href="https://play.google.com/store/apps/details?id=org.sailyachtresearch.sailinsight" target="_blank" rel="noopener"><img src="source/logo-google-play.png" width="12" height="12" alt="">Google Play</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Buoy Pinger -->
|
||
<div class="sol-card">
|
||
<div class="sol-card__img">
|
||
<img src="source/sailing-buoy-pinger-1-Photoroom.png" alt="Sailing Buoy Pinger">
|
||
</div>
|
||
<div class="sol-card__body">
|
||
<span class="sol-card__tag sol-card__tag--android">Android App</span>
|
||
<h3>Sailing Buoy Pinger</h3>
|
||
<p>Simplify race mark positioning and communication with mark layers. Connects to Eclipse Azimuth Sailing Analytics so regatta managers can track mark positions and enable Sail Insight GPS tracking.</p>
|
||
<div class="sol-card__links">
|
||
<button class="btn btn-primary" onclick="openModal('modal-bp')">Learn More</button>
|
||
<a class="sol-card__store" href="https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app" target="_blank" rel="noopener"><img src="source/logo-google-play.png" width="13" height="13" alt="" style="display:inline-block;vertical-align:middle;">Google Play</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Strategy Simulator -->
|
||
<div class="sol-card">
|
||
<div class="sol-card__img">
|
||
<img src="source/strategy-simulator-Photoroom.png" alt="Strategy Simulator">
|
||
</div>
|
||
<div class="sol-card__body">
|
||
<span class="sol-card__tag sol-card__tag--web">Web Platform</span>
|
||
<h3>Strategy Simulator</h3>
|
||
<p>Determine the best sailing strategy for various wind conditions and currents. Wind and current can be changed in real time, so the robustness of a strategy can be evaluated under uncertainty.</p>
|
||
<div class="sol-card__links">
|
||
<button class="btn btn-primary" onclick="openModal('modal-ss')">Learn More</button>
|
||
<a class="sol-card__store" href="https://www.sapsailing.com/gwt/Simulator.html" target="_blank" rel="noopener"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>Open App</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="solutions__nav">
|
||
<button class="solutions__arrow" id="sol-prev" onclick="solNav(-1)" aria-label="Previous">‹</button>
|
||
<div class="solutions__dots" id="sol-dots"></div>
|
||
<button class="solutions__arrow" id="sol-next" onclick="solNav(1)" aria-label="Next">›</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════════════════ GETTING STARTED ═══════════════════════ -->
|
||
<section class="section getting-started" id="getting-started">
|
||
<div class="container">
|
||
<p class="eyebrow">Quick Start</p>
|
||
<h2>Up and running in minutes</h2>
|
||
<p class="lead" style="max-width:540px;margin-top:10px;">
|
||
The fastest way to explore Eclipse Azimuth Sailing Analytics locally is via Docker Compose —
|
||
no build required, just pull and run.
|
||
</p>
|
||
<div class="steps">
|
||
<div class="step">
|
||
<div class="step__num">1</div>
|
||
<h3>Prerequisites</h3>
|
||
<p>Install <strong style="color:rgba(255,255,255,.85)">Docker</strong> and
|
||
<strong style="color:rgba(255,255,255,.85)">Docker Compose</strong>.
|
||
Works on Linux, macOS, and Windows with bash.</p>
|
||
</div>
|
||
<div class="step">
|
||
<div class="step__num">2</div>
|
||
<h3>Fetch & Launch</h3>
|
||
<p>Download the Compose file and start MongoDB, RabbitMQ, and Sailing Analytics
|
||
with a single command.</p>
|
||
</div>
|
||
<div class="step">
|
||
<div class="step__num">3</div>
|
||
<h3>Open the UI</h3>
|
||
<p>Navigate to <strong style="color:rgba(255,255,255,.85)">http://127.0.0.1:8888</strong>
|
||
and log in to the Admin Console with
|
||
<code style="color:var(--yellow)">admin / admin</code>.</p>
|
||
</div>
|
||
</div>
|
||
<div class="code-block">
|
||
<div class="code-block__bar"><span></span><span></span><span></span></div>
|
||
<pre><span class="cc"># Download the Docker Compose configuration</span>
|
||
<span class="ck">wget</span> <span class="cv">"https://github.com/eclipse-sailing-analytics/sailing-analytics/raw/main/docker/docker-compose.yml"</span>
|
||
|
||
<span class="cc"># Start MongoDB, RabbitMQ, and Sailing Analytics</span>
|
||
<span class="ck">docker-compose</span> up
|
||
|
||
<span class="cc"># Open the Admin Console at http://127.0.0.1:8888/gwt/AdminConsole.html</span>
|
||
<span class="cc"># Default credentials: admin / admin</span></pre>
|
||
</div>
|
||
<div style="display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-top:28px;">
|
||
<a class="btn btn-yellow" href="https://wiki.sapsailing.com/wiki/howto/onboarding.md" target="_blank" rel="noopener">Full Onboarding Guide</a>
|
||
<a class="btn btn-outline-white" href="https://github.com/eclipse-sailing-analytics/sailing-analytics/releases" target="_blank" rel="noopener">Latest Release</a>
|
||
<a class="btn btn-outline-white" href="https://github.com/eclipse-sailing-analytics/sailing-analytics/pkgs/container/sailing-analytics" target="_blank" rel="noopener">Docker Image</a>
|
||
<span style="margin-left:auto;display:inline-flex;align-items:center;gap:8px;padding:9px 16px;background:rgba(239,170,0,.12);border:1px solid rgba(239,170,0,.35);border-radius:var(--radius);border-left:3px solid var(--yellow);">
|
||
<span style="font-size:.9rem;font-weight:900;color:var(--yellow);line-height:1;">!</span>
|
||
<span style="font-size:.82rem;color:rgba(255,255,255,.8);">Found a bug or have a feature request? Use the <a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/issues/" target="_blank" rel="noopener" style="color:var(--yellow);font-weight:700;">issue tracker</a> on GitHub.</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════════════════ DOCUMENTATION ════════════════════════ -->
|
||
<section class="section section--alt" id="docs">
|
||
<div class="container">
|
||
<p class="eyebrow">Documentation</p>
|
||
<h2>Everything you need to go further</h2>
|
||
<p class="lead" style="max-width:560px;margin-top:10px;">
|
||
Browse by category and click any topic to open the <a href="https://wiki.sapsailing.com/Home" target="_blank" rel="noopener">wiki</a>.
|
||
</p>
|
||
<div class="docs__grid" id="docs-grid"></div>
|
||
</div>
|
||
</section>
|
||
<!-- ═══════════════════════════════════ TECHNOLOGY ════════════════════════════ -->
|
||
<section class="section getting-started" id="tech">
|
||
<div class="container">
|
||
<p class="eyebrow">Technology</p>
|
||
<h2>Built on proven open-source foundations</h2>
|
||
<p class="lead" style="max-width:560px;margin-top:10px;">
|
||
A polyglot stack - Java backend, GWT frontend, mobile apps - all tied together
|
||
by battle-tested middleware and cloud infrastructure.
|
||
</p>
|
||
<div class="tech__grid">
|
||
<div class="tech-item tech-item--invert">
|
||
<img src="https://cdn.simpleicons.org/openjdk/333333" alt="Java">
|
||
<span>Java 8+</span>
|
||
<small>Core server runtime & all business logic</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/eclipseide/2C2255" alt="OSGi / Eclipse">
|
||
<span>OSGi / Equinox</span>
|
||
<small>Modular plugin architecture — 225+ bundles</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="source/logo-gwt.svg" alt="GWT">
|
||
<span>GWT</span>
|
||
<small>Java-to-JavaScript web frontend compiler</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/mongodb/47A248" alt="MongoDB">
|
||
<span>MongoDB</span>
|
||
<small>Primary store for race & GPS data</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/rabbitmq/FF6600" alt="RabbitMQ">
|
||
<span>RabbitMQ</span>
|
||
<small>Message broker used for service replication</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/docker/2496ED" alt="Docker">
|
||
<span>Docker</span>
|
||
<small>Containerized deployment & local dev</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="source/logo-gradle.svg" alt="Gradle">
|
||
<span>Gradle</span>
|
||
<small>Build system for Android apps</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="source/logo-aws.svg" alt="AWS">
|
||
<span>AWS</span>
|
||
<small>Cloud hosting — EC2, S3, CloudFront</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/android/34A853" alt="Android">
|
||
<span>Android</span>
|
||
<small>Race Manager & Buoy Pinger apps</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/apple/000000" alt="iOS">
|
||
<span>iOS / Swift</span>
|
||
<small>Sail Insight GPS tracking app</small>
|
||
</div>
|
||
<div class="tech-item">
|
||
<img src="https://cdn.simpleicons.org/apachemaven/C71A36" alt="Maven">
|
||
<span>Maven</span>
|
||
<small>Build system for all Java modules</small>
|
||
</div>
|
||
<div class="tech-item tech-item--invert">
|
||
<img src="https://cdn.simpleicons.org/eclipsejetty/333333" alt="Jetty">
|
||
<span>Jetty</span>
|
||
<small>Embedded HTTP server inside OSGi</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════════════════ WHAT'S NEW ════════════════════════════ -->
|
||
<section class="section" id="whatsnew">
|
||
<div class="container">
|
||
<div class="whatsnew__header">
|
||
<div>
|
||
<p class="eyebrow eyebrow--yellow">What's New</p>
|
||
<h2>Latest updates across the platform</h2>
|
||
<p class="lead" style="max-width:560px;margin-top:10px;">
|
||
Expand a product below to see its changelog.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="whatsnew-sticky" class="whatsnew__sticky-bar">
|
||
<button id="whatsnew-close-btn" class="whatsnew__close-btn" onclick="closeAllAccordions()">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
Close all
|
||
</button>
|
||
</div>
|
||
|
||
<div class="accordion">
|
||
|
||
<!-- Sailing Analytics -->
|
||
<div class="accordion-item">
|
||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||
<div class="accordion-title">
|
||
<span>Sailing Analytics</span>
|
||
<span class="accordion-tag tag-web">Web</span>
|
||
</div>
|
||
<span class="accordion-arrow">▼</span>
|
||
</div>
|
||
<div class="accordion-body" data-src="https://raw.githubusercontent.com/eclipse-sailing-analytics/sailing-analytics/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html">
|
||
<div class="accordion-loading">Loading…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Race Manager -->
|
||
<div class="accordion-item">
|
||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||
<div class="accordion-title">
|
||
<span>Sailing Race Manager</span>
|
||
<span class="accordion-tag tag-android">Android</span>
|
||
</div>
|
||
<span class="accordion-arrow">▼</span>
|
||
</div>
|
||
<div class="accordion-body" data-src="https://raw.githubusercontent.com/eclipse-sailing-analytics/sailing-analytics/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html">
|
||
<div class="accordion-loading">Loading…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sail Insight -->
|
||
<div class="accordion-item">
|
||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||
<div class="accordion-title">
|
||
<span>Sail Insight</span>
|
||
<span class="accordion-tag tag-ios">iOS & Android</span>
|
||
</div>
|
||
<span class="accordion-arrow">▼</span>
|
||
</div>
|
||
<div class="accordion-body" data-src="https://raw.githubusercontent.com/eclipse-sailing-analytics/sailing-analytics/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/InSightAppNotes.html">
|
||
<div class="accordion-loading">Loading…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Buoy Pinger -->
|
||
<div class="accordion-item">
|
||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||
<div class="accordion-title">
|
||
<span>Sailing Buoy Pinger</span>
|
||
<span class="accordion-tag tag-android">Android</span>
|
||
</div>
|
||
<span class="accordion-arrow">▼</span>
|
||
</div>
|
||
<div class="accordion-body" data-src="https://raw.githubusercontent.com/eclipse-sailing-analytics/sailing-analytics/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/BuoyPingerAppNotes.html">
|
||
<div class="accordion-loading">Loading…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Strategy Simulator -->
|
||
<div class="accordion-item">
|
||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||
<div class="accordion-title">
|
||
<span>Strategy Simulator</span>
|
||
<span class="accordion-tag tag-web">Web</span>
|
||
</div>
|
||
<span class="accordion-arrow">▼</span>
|
||
</div>
|
||
<div class="accordion-body" data-src="https://raw.githubusercontent.com/eclipse-sailing-analytics/sailing-analytics/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingSimulatorNotes.html">
|
||
<div class="accordion-loading">Loading…</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ═══════════════════════════════════ FOOTER ════════════════════════════════ -->
|
||
<footer class="footer">
|
||
<div class="container">
|
||
<div class="footer__grid">
|
||
<div class="footer__brand">
|
||
<img src="source/logo-azimuth-no-bg.png" alt="Azimuth" style="height:80px;width:auto;margin-bottom:4px;">
|
||
<p>Open-source regatta analytics platform — live GPS tracking, race analysis, and fan engagement. Proven at the Olympic Games.</p>
|
||
<div class="footer__social">
|
||
<a href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener" title="GitHub">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="white"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
|
||
</a>
|
||
<a href="https://projects.eclipse.org/projects/technology.sailing-analytics" target="_blank" rel="noopener" title="Eclipse Foundation">
|
||
<img src="source/logo-eclipse.svg" alt="Eclipse Foundation" style="width:18px;height:18px;display:block;margin:auto;">
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer__col">
|
||
<h4>Product</h4>
|
||
<ul>
|
||
<li><a href="#solutions">Solutions</a></li>
|
||
<li><a href="#getting-started">Quick Start</a></li>
|
||
<li><a href="#whatsnew">What's New</a></li>
|
||
<li><a href="https://www.sapsailing.com/gwt/Home.html" target="_blank" rel="noopener">Application</a></li>
|
||
<li><a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/releases" target="_blank" rel="noopener">Releases</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="footer__col">
|
||
<h4>Developers</h4>
|
||
<ul>
|
||
<li><a href="wiki.html" target="_blank" rel="noopener">Wiki</a></li>
|
||
<li><a href="https://wiki.sapsailing.com/wiki/howto/onboarding.md" target="_blank" rel="noopener">Onboarding</a></li>
|
||
<li><a href="https://wiki.sapsailing.com/wiki/info/api/api-v1.md" target="_blank" rel="noopener">API Reference</a></li>
|
||
<li><a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/actions" target="_blank" rel="noopener">CI / CD</a></li>
|
||
<li><a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/issues/" target="_blank" rel="noopener">Issue Tracker</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="footer__col">
|
||
<h4>Community</h4>
|
||
<ul>
|
||
<li><a href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener">GitHub</a></li>
|
||
<li><a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener">Contributing</a></li>
|
||
<li><a href="https://wiki.sapsailing.com/wiki/howto/tutorials/home.md" target="_blank" rel="noopener">Support</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer__bottom">
|
||
<p>© 2011–2026 Eclipse Azimuth Sailing Analytics contributors.</p>
|
||
<div class="footer__bottom-links">
|
||
<a href="https://www.sapsailing.com/gwt/Home.html#/imprint/:" target="_blank" rel="noopener">Legal Disclosure</a>
|
||
<a href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener">Source Code</a>
|
||
<a href="https://wiki.sapsailing.com/wiki/howto/tutorials/home.md" target="_blank" rel="noopener">Support</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<!-- ═══════════════════════════════════ MODALS ════════════════════════════════ -->
|
||
|
||
<!-- Modal: Sailing Analytics -->
|
||
<div class="modal-overlay" id="modal-sa" onclick="closeModalOnOverlay(event, 'modal-sa')">
|
||
<div class="modal">
|
||
<div class="modal__header">
|
||
<div class="modal__header-left">
|
||
<div class="modal__header-title">Eclipse Azimuth Sailing Analytics</div>
|
||
<div class="modal__header-sub">Web Platform · Cloud & In-Memory</div>
|
||
</div>
|
||
<button class="modal__close" onclick="closeModal('modal-sa')">×</button>
|
||
</div>
|
||
<div class="modal__img-slider" id="mslider-sa">
|
||
<div class="modal__img-track">
|
||
<img src="source/sailing-analytics-Photoroom.png" alt="Eclipse Azimuth Sailing Analytics">
|
||
</div>
|
||
</div>
|
||
<div class="modal__body">
|
||
<div class="modal__cards">
|
||
<a class="modal__card" href="https://www.sapsailing.com/gwt/Home.html" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">🚀</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Open Application</div>
|
||
<div class="modal__card-desc">sapsailing.com — real events, real data</div>
|
||
</div>
|
||
<span class="modal__card-arrow">→</span>
|
||
</a>
|
||
<a class="modal__card" href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--yellow">⭐</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">GitHub Repository</div>
|
||
<div class="modal__card-desc">Source code, issues, pull requests</div>
|
||
</div>
|
||
<span class="modal__card-arrow">→</span>
|
||
</a>
|
||
</div>
|
||
<div class="modal__section-label">Documentation</div>
|
||
<div class="modal__cards modal__cards--2">
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/tutorials/sailinganalytics/create-regattas.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">🏁</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Create Your First Event</div>
|
||
<div class="modal__card-desc">Step-by-step regatta setup</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/tutorials/sailinganalytics/data-mining-tool.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">📊</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Data Mining Tool</div>
|
||
<div class="modal__card-desc">Race analytics deep-dive</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/info/api/sailing-webservices.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--yellow">🔌</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">REST API Reference</div>
|
||
<div class="modal__card-desc">Web services & integrations</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/onboarding.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--green">⚙️</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Developer Onboarding</div>
|
||
<div class="modal__card-desc">Build & run the project locally</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Race Manager -->
|
||
<div class="modal-overlay" id="modal-rm" onclick="closeModalOnOverlay(event, 'modal-rm')">
|
||
<div class="modal">
|
||
<div class="modal__header">
|
||
<div class="modal__header-left">
|
||
<div class="modal__header-title">Sailing Race Manager</div>
|
||
<div class="modal__header-sub">Android App · Race Committee Tool</div>
|
||
</div>
|
||
<button class="modal__close" onclick="closeModal('modal-rm')">×</button>
|
||
</div>
|
||
<div class="modal__img-slider" id="mslider-rm">
|
||
<div class="modal__img-track">
|
||
<img src="source/sailing-race-manager-1.png" alt="Race Manager 1">
|
||
<img src="source/sailing-race-manager-2.png" alt="Race Manager 2">
|
||
<img src="source/sailing-race-manager-3.png" alt="Race Manager 3">
|
||
<img src="source/sailing-race-manager-4.png" alt="Race Manager 4">
|
||
</div>
|
||
<button class="modal__img-btn modal__img-btn--prev" onclick="mSlide('mslider-rm',-1)">‹</button>
|
||
<button class="modal__img-btn modal__img-btn--next" onclick="mSlide('mslider-rm',1)">›</button>
|
||
<div class="modal__img-dots" id="mdots-rm">
|
||
<button class="modal__img-dot active" onclick="mSlideTo('mslider-rm',0)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-rm',1)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-rm',2)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-rm',3)"></button>
|
||
</div>
|
||
</div>
|
||
<div class="modal__body">
|
||
<div class="modal__section-label">Download</div>
|
||
<div class="modal__store-row">
|
||
<a class="modal__store-btn" href="https://play.google.com/store/apps/details?id=com.sap.sailing.racecommittee.app" target="_blank" rel="noopener">
|
||
<img src="source/logo-google-play.png" width="16" height="16" alt="" style="display:inline-block;vertical-align:middle;">
|
||
Google Play
|
||
</a>
|
||
</div>
|
||
<div class="modal__section-label">Documentation</div>
|
||
<div class="modal__cards modal__cards--2">
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/tutorials/sailingracemanager/sailing-race-guide.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">📖</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">User Guide</div>
|
||
<div class="modal__card-desc">Complete race manager walkthrough</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/tutorials/sailingracemanager/configure-wind-settings.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--yellow">💨</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Configure Wind</div>
|
||
<div class="modal__card-desc">Wind sensor setup & settings</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/info/mobile/racecommittee-app.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--green">🏗️</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">App Architecture</div>
|
||
<div class="modal__card-desc">Technical design & concepts</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/tutorials/sailinganalytics/sailing-race-manager.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--purple">🔗</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Connect to Analytics</div>
|
||
<div class="modal__card-desc">Link Race Manager to the platform</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Sail Insight -->
|
||
<div class="modal-overlay" id="modal-si" onclick="closeModalOnOverlay(event, 'modal-si')">
|
||
<div class="modal">
|
||
<div class="modal__header">
|
||
<div class="modal__header-left">
|
||
<div class="modal__header-title">Sail Insight</div>
|
||
<div class="modal__header-sub">iOS & Android · GPS Tracking for Sailors</div>
|
||
</div>
|
||
<button class="modal__close" onclick="closeModal('modal-si')">×</button>
|
||
</div>
|
||
<div class="modal__img-slider" id="mslider-si">
|
||
<div class="modal__img-track">
|
||
<img src="source/sail-insight-1-Photoroom.png" alt="Sail Insight 1">
|
||
<img src="source/sail-insight-2.png" alt="Sail Insight 2">
|
||
<img src="source/sail-insight-3.png" alt="Sail Insight 3">
|
||
<img src="source/sail-insight-4-Photoroom.png" alt="Sail Insight 4">
|
||
</div>
|
||
<button class="modal__img-btn modal__img-btn--prev" onclick="mSlide('mslider-si',-1)">‹</button>
|
||
<button class="modal__img-btn modal__img-btn--next" onclick="mSlide('mslider-si',1)">›</button>
|
||
<div class="modal__img-dots" id="mdots-si">
|
||
<button class="modal__img-dot active" onclick="mSlideTo('mslider-si',0)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-si',1)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-si',2)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-si',3)"></button>
|
||
</div>
|
||
</div>
|
||
<div class="modal__body">
|
||
<div class="modal__section-label">Download</div>
|
||
<div class="modal__store-row">
|
||
<div class="store-split">
|
||
<a class="store-split__half" href="https://apps.apple.com/us/app/sail-insight-powered-by-sap/id1495355086" target="_blank" rel="noopener">
|
||
<img src="source/logo-app-store.png" width="15" height="15" alt="">App Store
|
||
</a>
|
||
<a class="store-split__half" href="https://play.google.com/store/apps/details?id=org.sailyachtresearch.sailinsight" target="_blank" rel="noopener">
|
||
<img src="source/logo-google-play.png" width="15" height="15" alt="">Google Play
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div class="modal__section-label">Documentation</div>
|
||
<div class="modal__cards modal__cards--2">
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/tutorials/sailinsight/getting-started.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">🚀</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Getting Started</div>
|
||
<div class="modal__card-desc">First-time setup & tracking</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/info/mobile/mobilesailinganalytics.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--purple">📱</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Mobile Overview</div>
|
||
<div class="modal__card-desc">Architecture & design goals</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Buoy Pinger -->
|
||
<div class="modal-overlay" id="modal-bp" onclick="closeModalOnOverlay(event, 'modal-bp')">
|
||
<div class="modal">
|
||
<div class="modal__header">
|
||
<div class="modal__header-left">
|
||
<div class="modal__header-title">Sailing Buoy Pinger</div>
|
||
<div class="modal__header-sub">Android App · Race Mark Positioning</div>
|
||
</div>
|
||
<button class="modal__close" onclick="closeModal('modal-bp')">×</button>
|
||
</div>
|
||
<div class="modal__img-slider" id="mslider-bp">
|
||
<div class="modal__img-track">
|
||
<img src="source/sailing-buoy-pinger-1-Photoroom.png" alt="Buoy Pinger 1">
|
||
<img src="source/sailing-buoy-pinger-2-Photoroom.png" alt="Buoy Pinger 2">
|
||
<img src="source/sailing-buoy-pinger-3-Photoroom.png" alt="Buoy Pinger 3">
|
||
</div>
|
||
<button class="modal__img-btn modal__img-btn--prev" onclick="mSlide('mslider-bp',-1)">‹</button>
|
||
<button class="modal__img-btn modal__img-btn--next" onclick="mSlide('mslider-bp',1)">›</button>
|
||
<div class="modal__img-dots" id="mdots-bp">
|
||
<button class="modal__img-dot active" onclick="mSlideTo('mslider-bp',0)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-bp',1)"></button>
|
||
<button class="modal__img-dot" onclick="mSlideTo('mslider-bp',2)"></button>
|
||
</div>
|
||
</div>
|
||
<div class="modal__body">
|
||
<div class="modal__section-label">Download</div>
|
||
<div class="modal__store-row">
|
||
<a class="modal__store-btn" href="https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app" target="_blank" rel="noopener">
|
||
<img src="source/logo-google-play.png" width="16" height="16" alt="" style="display:inline-block;vertical-align:middle;">
|
||
Google Play
|
||
</a>
|
||
</div>
|
||
<div class="modal__section-label">Documentation</div>
|
||
<div class="modal__cards modal__cards--2">
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/info/mobile/smartphone-tracking-steps.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">📍</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Tracking Setup</div>
|
||
<div class="modal__card-desc">Configure mark GPS tracking</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/info/mobile/smartphone-tracking-general.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--green">📡</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">How Tracking Works</div>
|
||
<div class="modal__card-desc">GPS signal pipeline overview</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Strategy Simulator -->
|
||
<div class="modal-overlay" id="modal-ss" onclick="closeModalOnOverlay(event, 'modal-ss')">
|
||
<div class="modal">
|
||
<div class="modal__header">
|
||
<div class="modal__header-left">
|
||
<div class="modal__header-title">Strategy Simulator</div>
|
||
<div class="modal__header-sub">Web Platform · Wind & Current Analysis</div>
|
||
</div>
|
||
<button class="modal__close" onclick="closeModal('modal-ss')">×</button>
|
||
</div>
|
||
<div class="modal__img-slider" id="mslider-ss">
|
||
<div class="modal__img-track">
|
||
<img src="source/strategy-simulator-Photoroom.png" alt="Strategy Simulator">
|
||
</div>
|
||
</div>
|
||
<div class="modal__body">
|
||
<div class="modal__cards">
|
||
<a class="modal__card" href="https://www.sapsailing.com/gwt/Simulator.html" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">🧭</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Strategy Simulator Application</div>
|
||
<div class="modal__card-desc">Live wind & current strategy tool</div>
|
||
</div>
|
||
<span class="modal__card-arrow">→</span>
|
||
</a>
|
||
</div>
|
||
<div class="modal__section-label">Documentation</div>
|
||
<div class="modal__cards modal__cards--2">
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/misc/windestimation-core-concepts.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--blue">💨</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Wind Estimation</div>
|
||
<div class="modal__card-desc">Core concepts & algorithms</div>
|
||
</div>
|
||
</a>
|
||
<a class="modal__card" href="https://wiki.sapsailing.com/wiki/howto/misc/polars.md" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--yellow">⛵</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">Polar Performance Data</div>
|
||
<div class="modal__card-desc">Boat speed curves explained</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
<div class="modal__section-label">Source Code</div>
|
||
<div class="modal__cards">
|
||
<a class="modal__card" href="https://github.com/eclipse-sailing-analytics/sailing-analytics" target="_blank" rel="noopener">
|
||
<div class="modal__card-icon modal__card-icon--green">💻</div>
|
||
<div class="modal__card-text">
|
||
<div class="modal__card-title">GitHub Repository</div>
|
||
<div class="modal__card-desc">Simulator module in java/com.sap.sailing.simulator</div>
|
||
</div>
|
||
<span class="modal__card-arrow">→</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ── Documentation categories (shared data) ────────────────────────────────
|
||
var CATS = [
|
||
{ id:'general', label:'General', icon:'🚀', color:'#008fcc',
|
||
topics:[
|
||
{label:'Onboarding', url:'https://wiki.sapsailing.com/wiki/howto/onboarding.md'},
|
||
{label:'Tutorials', url:'https://wiki.sapsailing.com/wiki/howto/tutorials/home.md'},
|
||
{label:'Project Info', url:'https://wiki.sapsailing.com/wiki/info/general/general-information.md'},
|
||
{label:'Project History', url:'https://wiki.sapsailing.com/wiki/info/general/project-history.md'},
|
||
{label:'Sailing Glossary', url:'https://wiki.sapsailing.com/wiki/howto/glossary.md'},
|
||
{label:'About Wiki', url:'https://wiki.sapsailing.com/wiki/info/general/wiki.md'},
|
||
]},
|
||
{ id:'events', label:'Event Mgrs', icon:'🏁', color:'#efaa00',
|
||
topics:[
|
||
{label:'Create Regattas', url:'https://wiki.sapsailing.com/wiki/howto/tutorials/sailinganalytics/create-regattas.md'},
|
||
{label:'Publish Event', url:'https://wiki.sapsailing.com/wiki/howto/tutorials/sailinganalytics/publish-event.md'},
|
||
{label:'Import Results', url:'https://wiki.sapsailing.com/wiki/howto/eventmanagers/results-import.md'},
|
||
{label:'WindBots', url:'https://wiki.sapsailing.com/wiki/howto/eventmanagers/windbot-operations.md'},
|
||
{label:'Media Content', url:'https://wiki.sapsailing.com/wiki/howto/eventmanagers/manage-media-content.md'},
|
||
{label:'ORC Regattas', url:'https://wiki.sapsailing.com/wiki/howto/setup-orc-regatta.md'},
|
||
]},
|
||
{ id:'mobile', label:'Mobile', icon:'📱', color:'#7c3aed',
|
||
topics:[
|
||
{label:'Sail Insight', url:'https://wiki.sapsailing.com/wiki/howto/tutorials/sailinsight/getting-started.md'},
|
||
{label:'Race Manager', url:'https://wiki.sapsailing.com/wiki/howto/tutorials/sailingracemanager/sailing-race-guide.md'},
|
||
{label:'Committee App', url:'https://wiki.sapsailing.com/wiki/info/mobile/racecommittee-app.md'},
|
||
{label:'Smartphone Track', url:'https://wiki.sapsailing.com/wiki/info/mobile/smartphone-tracking-steps.md'},
|
||
{label:'Push Notifications', url:'https://wiki.sapsailing.com/wiki/info/mobile/push-notifications.md'},
|
||
]},
|
||
{ id:'api', label:'API', icon:'🔌', color:'#16a34a',
|
||
topics:[
|
||
{label:'REST API v1', url:'https://wiki.sapsailing.com/wiki/info/api/api-v1.md'},
|
||
{label:'Web Services API', url:'https://wiki.sapsailing.com/wiki/info/api/sailing-webservices.md'},
|
||
{label:'Tracking API', url:'https://wiki.sapsailing.com/wiki/info/api/tracking-app-api.md'},
|
||
{label:'Expedition Import', url:'https://wiki.sapsailing.com/wiki/howto/expeditionimport.md'},
|
||
{label:'Wind Estimation', url:'https://wiki.sapsailing.com/wiki/howto/windestimation.md'},
|
||
]},
|
||
{ id:'infra', label:'Infra', icon:'☁️', color:'#e05a2b',
|
||
topics:[
|
||
{label:'Architecture', url:'https://wiki.sapsailing.com/wiki/info/landscape/basic-architectural-principles.md'},
|
||
{label:'Build & Deploy', url:'https://wiki.sapsailing.com/wiki/info/landscape/building-and-deploying.md'},
|
||
{label:'Runtime Env', url:'https://wiki.sapsailing.com/wiki/info/landscape/runtime-environment.md'},
|
||
{label:'Amazon EC2', url:'https://wiki.sapsailing.com/wiki/info/landscape/amazon-ec2.md'},
|
||
{label:'Docker Registry', url:'https://wiki.sapsailing.com/wiki/info/landscape/docker-registry.md'},
|
||
{label:'Server Replication', url:'https://wiki.sapsailing.com/wiki/howto/misc/server-replication.md'},
|
||
]},
|
||
{ id:'dev', label:'Development', icon:'⚙️', color:'#0891b2',
|
||
topics:[
|
||
{label:'Dev Environment', url:'https://wiki.sapsailing.com/wiki/info/landscape/development-environment.md'},
|
||
{label:'Dev Scenarios', url:'https://wiki.sapsailing.com/wiki/info/landscape/typical-development-scenarios.md'},
|
||
{label:'GWT Super Dev', url:'https://wiki.sapsailing.com/wiki/howto/development/super-dev-mode.md'},
|
||
{label:'CI / Jenkins', url:'https://wiki.sapsailing.com/wiki/howto/development/ci.md'},
|
||
{label:'JVM Profiling', url:'https://wiki.sapsailing.com/wiki/howto/development/profiling.md'},
|
||
{label:'AI Agent', url:'https://wiki.sapsailing.com/wiki/info/landscape/ai-agent.md'},
|
||
]},
|
||
];
|
||
|
||
// ── Solutions carousel ────────────────────────────────────────────────────
|
||
(function() {
|
||
const track = document.getElementById('sol-track');
|
||
const dotsEl = document.getElementById('sol-dots');
|
||
const cards = Array.from(track.querySelectorAll('.sol-card'));
|
||
const n = cards.length;
|
||
let cur = 0;
|
||
let visCount = 3;
|
||
const GAP = 20;
|
||
|
||
function getVis() {
|
||
return window.innerWidth < 640 ? 1 : 2;
|
||
}
|
||
function cardWidth() {
|
||
return cards[0].offsetWidth;
|
||
}
|
||
function pages() {
|
||
return n - visCount + 1;
|
||
}
|
||
function buildDots() {
|
||
dotsEl.innerHTML = '';
|
||
for (let i = 0; i < pages(); i++) {
|
||
const d = document.createElement('button');
|
||
d.className = 'solutions__dot' + (i === 0 ? ' active' : '');
|
||
(function(idx) { d.onclick = function() { goto(idx); }; })(i);
|
||
dotsEl.appendChild(d);
|
||
}
|
||
}
|
||
function goto(idx) {
|
||
cur = Math.max(0, Math.min(idx, pages() - 1));
|
||
track.style.transform = 'translateX(-' + (cur * (cardWidth() + GAP)) + 'px)';
|
||
dotsEl.querySelectorAll('.solutions__dot').forEach(function(d, i) {
|
||
d.classList.toggle('active', i === cur);
|
||
});
|
||
document.getElementById('sol-prev').disabled = cur === 0;
|
||
document.getElementById('sol-next').disabled = cur >= pages() - 1;
|
||
}
|
||
window.solNav = function(dir) { goto(cur + dir); };
|
||
|
||
// Click anywhere on card opens modal (except clicking actual buttons/links)
|
||
cards.forEach(function(card) {
|
||
card.style.cursor = 'pointer';
|
||
card.addEventListener('click', function(e) {
|
||
if (e.target.closest('button') || e.target.closest('a')) return;
|
||
const btn = card.querySelector('button.btn-primary');
|
||
if (btn) btn.click();
|
||
});
|
||
});
|
||
|
||
// Drag / swipe
|
||
let dragStart = null;
|
||
track.addEventListener('mousedown', function(e) { dragStart = e.clientX; track.classList.add('dragging'); });
|
||
track.addEventListener('touchstart', function(e) { dragStart = e.touches[0].clientX; }, {passive:true});
|
||
function dragEnd(x) {
|
||
if (dragStart === null) return;
|
||
track.classList.remove('dragging');
|
||
const dx = dragStart - x;
|
||
if (Math.abs(dx) > 40) goto(cur + (dx > 0 ? 1 : -1));
|
||
dragStart = null;
|
||
}
|
||
track.addEventListener('mouseup', function(e) { dragEnd(e.clientX); });
|
||
track.addEventListener('touchend', function(e) { dragEnd(e.changedTouches[0].clientX); });
|
||
track.addEventListener('mouseleave', function(e) { if (dragStart !== null) dragEnd(e.clientX); });
|
||
|
||
function init() {
|
||
visCount = getVis();
|
||
buildDots();
|
||
goto(0);
|
||
}
|
||
// Wait for layout before first goto so offsetWidth is valid
|
||
if (document.readyState === 'complete') { init(); }
|
||
else { window.addEventListener('load', init); }
|
||
window.addEventListener('resize', function() { init(); });
|
||
})();
|
||
|
||
// ── Modal image slider ────────────────────────────────────────────────────
|
||
var mSliderState = {};
|
||
function mSlideTo(id, idx) {
|
||
const el = document.getElementById(id);
|
||
const track = el.querySelector('.modal__img-track');
|
||
const imgs = track.querySelectorAll('img');
|
||
const n = imgs.length;
|
||
idx = ((idx % n) + n) % n;
|
||
mSliderState[id] = idx;
|
||
track.style.transform = 'translateX(-' + (idx * 100) + '%)';
|
||
const dots = document.getElementById('mdots-' + id.replace('mslider-', ''));
|
||
if (dots) dots.querySelectorAll('.modal__img-dot').forEach(function(d, i) {
|
||
d.classList.toggle('active', i === idx);
|
||
});
|
||
}
|
||
function mSlide(id, dir) { mSlideTo(id, (mSliderState[id] || 0) + dir); }
|
||
|
||
// ── Documentation card grid ──────────────────────────────────────────────
|
||
(function() {
|
||
const grid = document.getElementById('docs-grid');
|
||
if (!grid) return;
|
||
CATS.forEach(function(cat) {
|
||
const card = document.createElement('div');
|
||
card.className = 'docs__card';
|
||
const headBg = cat.color + '1a'; // 10% opacity tint
|
||
card.innerHTML =
|
||
'<div class="docs__card-head">' +
|
||
'<div class="docs__card-icon" style="background:' + headBg + ';color:' + cat.color + '">' + cat.icon + '</div>' +
|
||
'<h3>' + cat.label + '</h3>' +
|
||
'</div>' +
|
||
'<div class="docs__card-links">' +
|
||
cat.topics.map(function(t) {
|
||
return '<a class="docs__link" href="' + t.url + '" target="_blank" rel="noopener">' +
|
||
'<span>' + t.label + '</span>' +
|
||
'<span class="docs__link-arrow" style="color:' + cat.color + '">→</span>' +
|
||
'</a>';
|
||
}).join('') +
|
||
'</div>';
|
||
grid.appendChild(card);
|
||
});
|
||
})();
|
||
|
||
// Accordion: lazy-load source HTML on first open
|
||
function toggleAccordion(header) {
|
||
const body = header.nextElementSibling;
|
||
const isOpen = header.classList.contains('open');
|
||
header.classList.toggle('open', !isOpen);
|
||
body.classList.toggle('open', !isOpen);
|
||
updateAccordionFab();
|
||
if (!isOpen && body.dataset.src && !body.dataset.loaded) {
|
||
body.dataset.loaded = '1';
|
||
fetch(body.dataset.src)
|
||
.then(function(r) { return r.ok ? r.text() : Promise.reject(r.status); })
|
||
.then(function(html) {
|
||
// Extract the innerContent div from the source file
|
||
const tmp = document.createElement('div');
|
||
tmp.innerHTML = html;
|
||
const inner = tmp.querySelector('.innerContent') || tmp.querySelector('#mainContent') || tmp;
|
||
const div = document.createElement('div');
|
||
div.className = 'whatsnew-content';
|
||
div.innerHTML = inner.innerHTML;
|
||
body.innerHTML = '';
|
||
body.appendChild(div);
|
||
})
|
||
.catch(function() {
|
||
body.innerHTML = '<div class="accordion-loading">Could not load changelog — <a href="' + body.dataset.src + '" target="_blank">view source file</a></div>';
|
||
});
|
||
}
|
||
}
|
||
// Close-all button: fixed position, visible only while #whatsnew is in viewport AND an accordion is open
|
||
var whatsnewVisible = false;
|
||
(function() {
|
||
var section = document.getElementById('whatsnew');
|
||
if (!section) return;
|
||
var obs = new IntersectionObserver(function(entries) {
|
||
whatsnewVisible = entries[0].isIntersecting;
|
||
updateAccordionFab();
|
||
}, { threshold: 0 });
|
||
obs.observe(section);
|
||
})();
|
||
function updateAccordionFab() {
|
||
var anyOpen = document.querySelector('.accordion-header.open') !== null;
|
||
document.getElementById('whatsnew-sticky').classList.toggle('visible', anyOpen && whatsnewVisible);
|
||
}
|
||
function closeAllAccordions() {
|
||
document.querySelectorAll('.accordion-header.open').forEach(function(h) {
|
||
h.classList.remove('open');
|
||
h.nextElementSibling.classList.remove('open');
|
||
});
|
||
updateAccordionFab();
|
||
}
|
||
|
||
function openModal(id) {
|
||
document.getElementById(id).classList.add('open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeModal(id) {
|
||
document.getElementById(id).classList.remove('open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
function closeModalOnOverlay(event, id) {
|
||
if (event.target === document.getElementById(id)) {
|
||
closeModal(id);
|
||
}
|
||
}
|
||
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
document.querySelectorAll('.modal-overlay.open').forEach(function(m) {
|
||
m.classList.remove('open');
|
||
});
|
||
document.body.style.overflow = '';
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|