Files
sailing-analytics/docs/index.html
T

1648 lines
84 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 &mdash; 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 &amp; 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">&#8249;</button>
<div class="solutions__dots" id="sol-dots"></div>
<button class="solutions__arrow" id="sol-next" onclick="solNav(1)" aria-label="Next">&#8250;</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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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>&copy; 2011&ndash;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 &amp; In-Memory</div>
</div>
<button class="modal__close" onclick="closeModal('modal-sa')">&times;</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 &amp; 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 &amp; 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')">&times;</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)">&#8249;</button>
<button class="modal__img-btn modal__img-btn--next" onclick="mSlide('mslider-rm',1)">&#8250;</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 &amp; 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 &amp; 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 &amp; Android · GPS Tracking for Sailors</div>
</div>
<button class="modal__close" onclick="closeModal('modal-si')">&times;</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)">&#8249;</button>
<button class="modal__img-btn modal__img-btn--next" onclick="mSlide('mslider-si',1)">&#8250;</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 &amp; 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 &amp; 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')">&times;</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)">&#8249;</button>
<button class="modal__img-btn modal__img-btn--next" onclick="mSlide('mslider-bp',1)">&#8250;</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 &amp; Current Analysis</div>
</div>
<button class="modal__close" onclick="closeModal('modal-ss')">&times;</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 &amp; 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 &amp; 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>