1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta name="yandex-verification" content="30c101154cf20748" /> 7 <meta name="naver-site-verification" content="e7a0092bbb012fac0c72ce9bb9054e0b02f7e67e" /> 8 <title>ArcNautical â Vessel Sanctions & Ownership Screening</title> 9 <meta name="description" content="Screen any vessel for sanctions and ownership-chain exposure â checked against OFAC, EU, UN, and the GLEIF ownership registry. Plus voyage risk scoring, vetting grades, fleet monitoring, and automated PDF reports for maritime insurers, charterers, and traders."> 10 <meta name="keywords" content="maritime intelligence, voyage risk, war risk premium, P&I club, hull machinery, marine insurance, JWC listed areas, piracy risk, AIS monitoring, vessel tracking, fleet management, Monte Carlo, sanctions screening, CII rating, PML, underwriting"> 11 <meta property="og:title" content="ArcNautical â Vessel Sanctions & Ownership Screening"> 12 <meta property="og:description" content="Screen any vessel before you fix, insure, or finance it. Sanctions, ownership exposure, and a vetting grade for any IMO â checked against OFAC, EU, UN, and the ownership chain. Free vessel check, no signup."> 13 <meta property="og:type" content="website"> 14 <meta property="og:url" content="https://arcnautical.com/"> 15 <meta property="og:image" content="https://arcnautical.com/og-image.png"> 16 <meta property="og:image:width" content="1200"> 17 <meta property="og:image:height" content="630"> 18 <meta name="twitter:card" content="summary_large_image"> 19 <meta name="twitter:image" content="https://arcnautical.com/og-image.png"> 20 <meta name="theme-color" content="#070a10"> 21 <link rel="canonical" href="https://arcnautical.com/"> 22 <link rel="alternate" hreflang="ko-KR" href="https://arcnautical.com/ko/"> 23 <link rel="icon" type="image/svg+xml" href="/logo.svg" /> 24 <link rel="preconnect" href="https://fonts.googleapis.com"> 25 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 26 <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> 27 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" crossorigin=""> 28
28<script src="/posthog-init.js" defer></script>
28 29
29<script src="/lane-capture.js" defer></script>
29 30
30<script src="/api-access.js?v=20260730" defer></script>
30 31 <style> 32 /* ================================================================ 33 RESET & BASE 34 ================================================================ */ 35 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } 36 html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } 37 body { 38 background: #070a10; 39 color: #d0d8e8; 40 font-family: 'Inter', system-ui, -apple-system, sans-serif; 41 line-height: 1.6; 42 overflow-x: hidden; 43 } 44 img { max-width: 100%; display: block; } 45 a { color: inherit; text-decoration: none; } 46 h2, h3 { font-weight: inherit; font-size: inherit; } 47 48 /* ================================================================ 49 DESIGN TOKENS 50 ================================================================ */ 51 :root { 52 --bg: #070a10; 53 --surface: #0d1420; 54 --surface-el: #152030; 55 --border: #1c2a3e; 56 --text: #d0d8e8; 57 --text-sec: #7e8fa6; 58 --text-dim: #445068; 59 --accent: #0891b2; 60 --accent-hover: #06b6d4; 61 --risk-low: #059669; 62 --risk-mod: #818cf8; 63 --risk-elev: #d97706; 64 --risk-high: #ea580c; 65 --risk-crit: #dc2626; 66 --mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace; 67 --display: 'DM Serif Display', 'Georgia', serif; 68 --body: 'Inter', system-ui, sans-serif; 69 } 70 71 /* ================================================================ 72 UTILITY 73 ================================================================ */ 74 .container { max-width: 1200px; margin: 0 auto; padding: 0 24px; } 75 .mono { font-family: var(--mono); } 76 .text-dim { color: var(--text-dim); } 77 .text-sec { color: var(--text-sec); } 78 .text-accent { color: var(--accent); } 79 80 /* Scroll-triggered reveal */ 81 .reveal { 82 opacity: 0; 83 transform: translateY(30px); 84 transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1); 85 } 86 .reveal.visible { opacity: 1; transform: translateY(0); } 87 .reveal-delay-1 { transition-delay: 0.1s; } 88 .reveal-delay-2 { transition-delay: 0.2s; } 89 .reveal-delay-3 { transition-delay: 0.3s; } 90 .reveal-delay-4 { transition-delay: 0.4s; } 91 .reveal-delay-5 { transition-delay: 0.5s; } 92 93 /* ================================================================ 94 SCROLL PROGRESS 95 ================================================================ */ 96 .scroll-progress { 97 position: fixed; 98 top: 0; 99 left: 0; 100 height: 2px; 101 width: 0%; 102 background: linear-gradient(90deg, var(--accent), var(--accent-hover)); 103 z-index: 1001; 104 transition: width 0.05s linear; 105 } 106 107 /* ================================================================ 108 NAVIGATION 109 ================================================================ */ 110 .nav { 111 position: fixed; 112 top: 2px; 113 left: 0; 114 right: 0; 115 z-index: 1000; 116 background: rgba(12, 14, 18, 0.85); 117 backdrop-filter: blur(12px); 118 -webkit-backdrop-filter: blur(12px); 119 border-bottom: 1px solid rgba(42, 47, 58, 0.5); 120 transition: background 0.3s; 121 } 122 .nav-inner { 123 max-width: 1200px; 124 margin: 0 auto; 125 padding: 0 24px; 126 height: 56px; 127 display: flex; 128 align-items: center; 129 justify-content: space-between; 130 } 131 .nav-brand { 132 display: flex; 133 align-items: center; 134 gap: 10px; 135 font-family: var(--mono); 136 font-weight: 600; 137 font-size: 14px; 138 letter-spacing: 2.5px; 139 color: var(--text); 140 } 141 .nav-brand-mark { 142 width: 24px; 143 height: 24px; 144 object-fit: contain; 145 } 146 .nav-links { 147 display: flex; 148 align-items: center; 149 gap: 28px; 150 list-style: none; 151 } 152 .nav-links a { 153 font-size: 13px; 154 font-weight: 500; 155 color: var(--text-sec); 156 transition: color 0.2s; 157 letter-spacing: 0.02em; 158 } 159 .nav-links a:hover { color: var(--text); } 160 .nav-cta { 161 font-size: 13px; 162 font-weight: 600; 163 padding: 8px 20px; 164 background: var(--accent); 165 color: #fff; 166 border-radius: 4px; 167 transition: background 0.2s, transform 0.2s; 168 letter-spacing: 0.02em; 169 } 170 .nav-cta:hover { background: var(--accent-hover); transform: translateY(-1px); } 171 /* `.nav-links a` (0,1,1) outranks `.nav-cta` (0,1,0), so the CTA inherited the grey link colour: 1.12:1 on teal (measured 2026-09-23). */ 172 .nav-links a.nav-cta, .nav-links a.nav-cta:hover { color: #fff; } 173 .nav-signin { 174 font-size: 13px; 175 font-weight: 600; 176 padding: 8px 16px; 177 border: 1px solid rgba(255,255,255,0.18); 178 border-radius: 4px; 179 transition: border-color 0.2s, color 0.2s; 180 letter-spacing: 0.02em; 181 } 182 .nav-signin:hover { border-color: var(--accent); color: var(--accent) !important; } 183 184 /* Hamburger */ 185 .nav-hamburger { 186 display: none; 187 flex-direction: column; 188 gap: 5px; 189 background: none; 190 border: none; 191 cursor: pointer; 192 padding: 4px; 193 } 194 .nav-hamburger span { 195 display: block; 196 width: 22px; 197 height: 2px; 198 background: var(--text-sec); 199 border-radius: 1px; 200 transition: transform 0.3s, opacity 0.3s; 201 } 202 .nav-hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); } 203 .nav-hamburger.open span:nth-child(2) { opacity: 0; } 204 .nav-hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); } 205 206 @media (max-width: 768px) { 207 .nav-links { 208 display: none; 209 position: absolute; 210 top: 56px; 211 left: 0; 212 right: 0; 213 flex-direction: column; 214 background: rgba(12, 14, 18, 0.97); 215 backdrop-filter: blur(16px); 216 padding: 24px; 217 gap: 20px; 218 border-bottom: 1px solid var(--border); 219 } 220 .nav-links.open { display: flex; } 221 .nav-hamburger { display: flex; } 222 } 223 224 /* ================================================================ 225 HERO 226 ================================================================ */ 227 .hero { 228 position: relative; 229 min-height: 100vh; 230 display: flex; 231 align-items: center; 232 padding-top: 58px; 233 overflow: hidden; 234 } 235 .hero-grid { 236 position: absolute; 237 inset: 0; 238 background-image: 239 linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px), 240 linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px); 241 background-size: 60px 60px; 242 animation: gridDrift 20s linear infinite; 243 pointer-events: none; /* decorative; same reason as .hero-routes below */ 244 }
245 @keyframes gridDrift { 246 0% { background-position: 0 0; } 247 100% { background-position: 60px 60px; } 248 } 249 /* Decorative, like .hero-grain and .hero-glow below â and like them it must 250 not eat clicks. It is position:absolute inside #hero, so it paints above 251 every UNPOSITIONED descendant, and #scorerResults is one. Without this the 252 whole lane-result panel is click-dead: share, score-again, and the 253 lane-brief email capture all sit under it. .hero-content escapes only 254 because it sets z-index:2. */ 255 .hero-routes { 256 position: absolute; 257 inset: 0; 258 opacity: 0.12; 259 pointer-events: none; 260 } 261 .hero-routes svg { width: 100%; height: 100%; } 262 .hero-grain { 263 position: absolute; 264 inset: 0; 265 opacity: 0.035; 266 background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); 267 background-repeat: repeat; 268 background-size: 256px 256px; 269 pointer-events: none; 270 } 271 .hero-glow { 272 position: absolute; 273 top: -20%; 274 right: -10%; 275 width: 70%; 276 height: 70%; 277 background: radial-gradient(circle, rgba(59, 130, 246, 0.06) 0%, transparent 70%); 278 pointer-events: none; 279 } 280 .hero-content { 281 position: relative; 282 z-index: 2; 283 display: grid; 284 grid-template-columns: 1fr 1fr; 285 gap: 64px; 286 align-items: center; 287 padding: 80px 0; 288 } 289 .hero-text h1 { 290 font-family: var(--display); 291 font-size: clamp(2.4rem, 4.5vw, 3.6rem); 292 line-height: 1.15; 293 color: var(--text); 294 margin-bottom: 24px; 295 font-weight: 400; 296 } 297 .hero-text h1 em { font-style: italic; color: var(--accent); } 298 .hero-text .hero-sub { 299 font-size: 17px; 300 color: var(--text-sec); 301 line-height: 1.7; 302 margin-bottom: 40px; 303 max-width: 520px; 304 } 305 .hero-actions { display: flex; gap: 16px; flex-wrap: wrap; } 306 .btn-primary { 307 display: inline-flex; 308 align-items: center; 309 gap: 8px; 310 padding: 14px 32px; 311 background: var(--accent); 312 color: #fff; 313 font-size: 14px; 314 font-weight: 600; 315 border-radius: 4px; 316 border: none; 317 cursor: pointer; 318 transition: background 0.2s, transform 0.15s, box-shadow 0.2s; 319 letter-spacing: 0.02em; 320 } 321 .btn-primary:hover { 322 background: var(--accent-hover); 323 transform: translateY(-2px); 324 box-shadow: 0 8px 24px rgba(59, 130, 246, 0.25); 325 } 326 .btn-ghost { 327 display: inline-flex; 328 align-items: center; 329 gap: 8px; 330 padding: 14px 32px; 331 background: transparent; 332 color: var(--text-sec); 333 font-size: 14px; 334 font-weight: 500; 335 border-radius: 4px; 336 border: 1px solid var(--border); 337 cursor: pointer; 338 transition: border-color 0.2s, color 0.2s, transform 0.15s; 339 letter-spacing: 0.02em; 340 } 341 .btn-ghost:hover { 342 border-color: var(--text-dim); 343 color: var(--text); 344 transform: translateY(-2px); 345 } 346 347 .hero-visual { 348 position: relative; 349 display: flex; 350 flex-direction: column; 351 align-items: center; 352 gap: 24px; 353 } 354 .hero-gauge-wrap { position: relative; } 355 .hero-route-card { 356 background: var(--surface); 357 border: 1px solid var(--border); 358 border-radius: 6px; 359 padding: 16px 20px; 360 width: 100%; 361 max-width: 380px; 362 } 363 .hero-route-label { 364 font-family: var(--mono); 365 font-size: 11px; 366 color: var(--text-dim); 367 text-transform: uppercase; 368 letter-spacing: 0.1em; 369 margin-bottom: 8px; 370 } 371 .hero-route-ports { 372 font-family: var(--mono); 373 font-size: 15px; 374 color: var(--text); 375 font-weight: 600; 376 margin-bottom: 6px; 377 } 378 .hero-route-meta { 379 font-family: var(--mono); 380 font-size: 11px; 381 color: var(--text-dim); 382 } 383 .hero-route-waypoints { 384 display: flex; 385 align-items: center; 386 gap: 6px; 387 margin-top: 10px; 388 flex-wrap: wrap; 389 } 390 .hero-waypoint { 391 font-family: var(--mono); 392 font-size: 10px; 393 color: var(--text-sec); 394 background: var(--surface-el); 395 padding: 2px 8px; 396 border-radius: 3px; 397 } 398 .hero-waypoint-arrow { color: var(--text-dim); font-size: 10px; } 399 400 /* Hero cycling preview */ 401 .hero-preview { 402 width: 100%; 403 max-width: 380px; 404 height: 42px; 405 position: relative; 406 overflow: hidden; 407 } 408 .hero-preview-item { 409 position: absolute; 410 inset: 0; 411 display: flex; 412 align-items: center; 413 gap: 10px; 414 padding: 8px 14px; 415 background: rgba(8, 145, 178, 0.06); 416 border: 1px solid rgba(8, 145, 178, 0.15); 417 border-radius: 4px; 418 opacity: 0; 419 transform: translateY(8px); 420 transition: opacity 0.4s, transform 0.4s; 421 } 422 .hero-preview-item.active { 423 opacity: 1; 424 transform: translateY(0); 425 } 426 .hero-preview-icon {
427 width: 20px; 428 height: 20px; 429 flex-shrink: 0; 430 } 431 .hero-preview-icon svg { 432 width: 20px; 433 height: 20px; 434 stroke: var(--accent); 435 fill: none; 436 stroke-width: 1.5; 437 stroke-linecap: round; 438 stroke-linejoin: round; 439 } 440 .hero-preview-text { 441 font-family: var(--mono); 442 font-size: 11px; 443 color: var(--text-sec); 444 } 445 .hero-preview-text strong { color: var(--text); font-weight: 600; } 446 447 @media (max-width: 900px) { 448 .hero-content { grid-template-columns: 1fr; gap: 40px; text-align: center; } 449 .hero-text .hero-sub { margin-left: auto; margin-right: auto; } 450 .hero-actions { justify-content: center; } 451 .hero-visual { order: -1; } 452 } 453 454 /* ================================================================ 455 METRICS BAR 456 ================================================================ */ 457 .proof { 458 border-top: 1px solid var(--border); 459 border-bottom: 1px solid var(--border); 460 background: var(--surface); 461 padding: 48px 0; 462 } 463 .proof-label { 464 text-align: center; 465 font-size: 13px; 466 color: var(--text-dim); 467 letter-spacing: 0.08em; 468 text-transform: uppercase; 469 margin-bottom: 32px; 470 } 471 .proof-grid { 472 display: grid; 473 grid-template-columns: repeat(6, 1fr); 474 gap: 24px; 475 text-align: center; 476 } 477 .proof-metric { 478 display: flex; 479 flex-direction: column; 480 align-items: center; 481 gap: 6px; 482 } 483 .proof-number { 484 font-family: var(--mono); 485 font-size: 32px; 486 font-weight: 600; 487 color: var(--text); 488 line-height: 1; 489 } 490 .proof-desc { 491 font-size: 12px; 492 color: var(--text-sec); 493 font-weight: 500; 494 } 495 496 @media (max-width: 900px) { 497 .proof-grid { grid-template-columns: repeat(3, 1fr); gap: 24px; } 498 } 499 @media (max-width: 600px) { 500 .proof-grid { grid-template-columns: repeat(2, 1fr); gap: 20px; } 501 .proof-number { font-size: 26px; } 502 } 503 504 /* ================================================================ 505 HOW IT WORKS 506 ================================================================ */ 507 .hiw-section { 508 padding: 80px 0; 509 background: var(--bg); 510 border-bottom: 1px solid var(--border); 511 } 512 .hiw-inner { 513 max-width: 860px; 514 margin: 0 auto; 515 } 516 .hiw-steps { 517 display: grid; 518 grid-template-columns: repeat(4, 1fr); 519 gap: 24px; 520 margin-bottom: 48px; 521 } 522 .hiw-step { 523 position: relative; 524 background: var(--surface); 525 border: 1px solid var(--border); 526 border-radius: 8px; 527 padding: 28px 20px 24px; 528 text-align: center; 529 } 530 .hiw-step-num { 531 display: inline-flex; 532 align-items: center; 533 justify-content: center; 534 width: 32px; 535 height: 32px; 536 border-radius: 50%; 537 background: rgba(8, 145, 178, 0.1); 538 border: 1px solid rgba(8, 145, 178, 0.3); 539 font-family: var(--mono); 540 font-size: 13px; 541 font-weight: 700; 542 color: var(--accent); 543 margin-bottom: 16px; 544 } 545 .hiw-step-title { 546 font-family: var(--heading); 547 font-size: 18px; 548 color: var(--text); 549 margin-bottom: 8px; 550 } 551 .hiw-step-desc { 552 font-size: 13px; 553 color: var(--text-dim); 554 line-height: 1.6; 555 } 556 .hiw-step-connector { 557 display: none; 558 } 559 @media (min-width: 601px) { 560 .hiw-step-connector { 561 display: block; 562 position: absolute; 563 top: 42px; 564 right: -14px; 565 color: var(--border); 566 font-size: 14px; 567 z-index: 1; 568 } 569 } 570 .hiw-built-for { 571 text-align: center; 572 margin-bottom: 36px; 573 } 574 .hiw-personas { 575 display: flex; 576 align-items: center; 577 justify-content: center; 578 gap: 24px; 579 flex-wrap: wrap; 580 margin-top: 16px; 581 } 582 .hiw-persona { 583 display: flex; 584 align-items: center; 585 gap: 8px; 586 font-size: 13px; 587 color: var(--text-sec); 588 } 589 .hiw-persona-icon { 590 width: 16px; 591 height: 16px; 592 flex-shrink: 0; 593 opacity: 0.6; 594 } 595 .hiw-persona-icon svg { 596 width: 16px; 597 height: 16px; 598 stroke: var(--accent); 599 fill: none; 600 stroke-width: 1.5; 601 stroke-linecap: round; 602 stroke-linejoin: round; 603 } 604 .hiw-persona-workflow { 605 font-family: var(--mono); 606 font-size: 11px; 607 color: var(--text-dim); 608 margin-top: 2px; 609 } 610 .hiw-cta { 611 display: flex; 612 align-items: center; 613 justify-content: center; 614 gap: 16px; 615 flex-wrap: wrap; 616 } 617 .hiw-free-note { 618 text-align: center; 619 font-family: var(--mono); 620 font-size: 11px; 621 color: var(--text-dim); 622 margin-top: 16px; 623 letter-spacing: 0.03em; 624 } 625 626 @media (max-width: 600px) { 627 .hiw-steps { grid-template-columns: 1fr; gap: 16px; } 628 .hiw-personas { flex-direction: column; gap: 12px; } 629 } 630 631 /* ================================================================ 632 INTELLIGENCE TICKER 633 ================================================================ */ 634 .ticker-section { 635 padding: 32px 0; 636 background: var(--bg); 637 border-bottom: 1px solid var(--border); 638 overflow: hidden; 639 } 640 .ticker-header { 641 display: flex; 642 align-items: center; 643 gap: 10px; 644 margin-bottom: 16px; 645 padding: 0 24px; 646 } 647 .ticker-dot { 648 width: 6px; 649 height: 6px; 650 background: var(--risk-low); 651 border-radius: 50%; 652 animation: tickerPulse 2s ease-in-out infinite; 653 }
654 @keyframes tickerPulse { 655 0%, 100% { opacity: 1; } 656 50% { opacity: 0.3; } 657 } 658 .ticker-title { 659 font-family: var(--mono); 660 font-size: 11px; 661 color: var(--text-dim); 662 text-transform: uppercase; 663 letter-spacing: 0.12em; 664 } 665 .ticker-track { 666 display: flex; 667 animation: tickerScroll 80s linear infinite; 668 width: max-content; 669 } 670 .ticker-track:hover { animation-play-state: paused; } 671 @keyframes tickerScroll { 672 0% { transform: translateX(0); } 673 100% { transform: translateX(-50%); } 674 } 675 .ticker-item { 676 display: flex; 677 align-items: stretch; 678 background: var(--surface); 679 border: 1px solid var(--border); 680 border-radius: 4px; 681 margin-right: 12px; 682 flex-shrink: 0; 683 min-width: 400px; 684 overflow: hidden; 685 transition: border-color 0.2s; 686 } 687 .ticker-item:hover { border-color: var(--text-dim); } 688 .ticker-accent { width: 3px; flex-shrink: 0; } 689 .ticker-accent[data-type="navwarn"] { background: var(--risk-elev); } 690 .ticker-accent[data-type="piracy"] { background: var(--risk-crit); } 691 .ticker-accent[data-type="jwc"] { background: var(--accent); } 692 .ticker-accent[data-type="ais"] { background: #a78bfa; } 693 .ticker-accent[data-type="risk"] { background: var(--risk-low); } 694 .ticker-accent[data-type="weather"] { background: #38bdf8; } 695 .ticker-accent[data-type="sanctions"] { background: #f472b6; } 696 .ticker-accent[data-type="disaster"] { background: var(--risk-high); } 697 .ticker-body { 698 padding: 10px 14px; 699 display: flex; 700 flex-direction: column; 701 gap: 3px; 702 flex: 1; 703 } 704 .ticker-type { 705 font-family: var(--mono); 706 font-size: 10px; 707 font-weight: 600; 708 letter-spacing: 0.08em; 709 text-transform: uppercase; 710 } 711 .ticker-type[data-type="navwarn"] { color: var(--risk-elev); } 712 .ticker-type[data-type="piracy"] { color: var(--risk-crit); } 713 .ticker-type[data-type="jwc"] { color: var(--accent); } 714 .ticker-type[data-type="ais"] { color: #a78bfa; } 715 .ticker-type[data-type="risk"] { color: var(--risk-low); } 716 .ticker-type[data-type="weather"] { color: #38bdf8; } 717 .ticker-type[data-type="sanctions"] { color: #f472b6; } 718 .ticker-type[data-type="disaster"] { color: var(--risk-high); } 719 .ticker-msg { 720 font-family: var(--mono); 721 font-size: 12px; 722 color: var(--text-sec); 723 line-height: 1.4; 724 white-space: nowrap; 725 } 726 .ticker-time { 727 font-family: var(--mono); 728 font-size: 10px; 729 color: var(--text-dim); 730 } 731 732 /* ================================================================ 733 SECTION SHARED 734 ================================================================ */ 735 .section { padding: 100px 0; } 736 .section-label { 737 font-family: var(--mono); 738 font-size: 11px; 739 color: var(--accent); 740 text-transform: uppercase; 741 letter-spacing: 0.15em; 742 margin-bottom: 12px; 743 } 744 .section-title { 745 font-family: var(--display); 746 font-size: clamp(1.8rem, 3vw, 2.4rem); 747 line-height: 1.2; 748 margin-bottom: 20px; 749 color: var(--text); 750 font-weight: 400; 751 } 752 .section-desc { 753 font-size: 16px; 754 color: var(--text-sec); 755 line-height: 1.7; 756 max-width: 600px; 757 margin-bottom: 32px; 758 } 759 .section-center { text-align: center; margin-bottom: 56px; } 760 .section-center .section-desc { margin-left: auto; margin-right: auto; } 761 .section-divider { height: 1px; background: linear-gradient(90deg, transparent, var(--border), transparent); } 762 763 /* ================================================================ 764 "10 SIGNALS, ONE SCORE" â SIGNAL CONVERGENCE 765 ================================================================ */ 766 .signals-section { 767 background: var(--surface); 768 border-top: 1px solid var(--border); 769 border-bottom: 1px solid var(--border); 770 } 771 .signals-layout { 772 display: grid; 773 grid-template-columns: 1fr 1fr; 774 gap: 64px; 775 align-items: center; 776 } 777 .signals-svg-wrap { 778 position: relative; 779 width: 100%; 780 max-width: 480px; 781 margin: 0 auto; 782 } 783 .signals-svg-wrap svg { width: 100%; height: auto; } 784 .signal-node { 785 opacity: 0; 786 transition: opacity 0.5s ease; 787 } 788 .signal-node.visible { opacity: 1; } 789 .signal-line { 790 stroke-dasharray: 200; 791 stroke-dashoffset: 200; 792 transition: stroke-dashoffset 0.8s ease; 793 } 794 .signal-line.drawn { stroke-dashoffset: 0; } 795 .signal-list { 796 list-style: none; 797 display: grid; 798 grid-template-columns: 1fr 1fr; 799 gap: 10px; 800 } 801 .signal-item { 802 display: flex; 803 align-items: center; 804 gap: 10px; 805 font-size: 13px; 806 color: var(--text-sec); 807 padding: 8px 12px; 808 background: var(--bg); 809 border: 1px solid var(--border); 810 border-radius: 4px; 811 transition: border-color 0.2s; 812 } 813 .signal-item:hover { border-color: var(--text-dim); } 814 .signal-dot { 815 width: 8px; 816 height: 8px; 817 border-radius: 2px; 818 flex-shrink: 0; 819 } 820 .signal-num { 821 font-family: var(--mono); 822 font-size: 10px; 823 color: var(--text-dim); 824 margin-left: auto; 825 flex-shrink: 0; 826 } 827 828 @media (max-width: 900px) { 829 .signals-layout { grid-template-columns: 1fr; gap: 40px; } 830 .signal-list { grid-template-columns: 1fr; } 831 } 832 833 /* ================================================================ 834 DATA SOURCES BANNER 835 ================================================================ */ 836 .sources-banner { 837 border-bottom: 1px solid var(--border); 838 padding: 40px 0; 839 background: var(--bg); 840 } 841 .sources-label { 842 text-align: center; 843 font-family: var(--mono); 844 font-size: 10px; 845 color: var(--text-dim); 846 text-transform: uppercase; 847 letter-spacing: 0.14em; 848 margin-bottom: 20px; 849 } 850 .sources-categories { 851 display: flex; 852 flex-direction: column; 853 gap: 16px; 854 } 855 .sources-cat-label { 856 font-family: var(--mono); 857 font-size: 9px; 858 color: var(--text-dim); 859 text-transform: uppercase; 860 letter-spacing: 0.12em; 861 width: 100px; 862 flex-shrink: 0; 863 padding-top: 8px; 864 } 865 .sources-cat-row { 866 display: flex; 867 align-items: center; 868 gap: 12px; 869 flex-wrap: wrap; 870 } 871 .source-badge { 872 display: flex; 873 align-items: center; 874 gap: 8px; 875 padding: 6px 14px; 876 background: var(--surface); 877 border: 1px solid var(--border); 878 border-radius: 4px; 879 transition: border-color 0.2s; 880 } 881 .source-badge:hover { border-color: var(--text-dim); } 882 .source-badge-icon { 883 width: 8px; 884 height: 8px; 885 border-radius: 2px; 886 flex-shrink: 0; 887 } 888 .source-badge-name { 889 font-family: var(--mono); 890 font-size: 11px; 891 font-weight: 600; 892 color: var(--text-sec); 893 letter-spacing: 0.04em; 894 } 895 .source-badge-org { 896 font-family: var(--mono); 897 font-size: 10px; 898 color: var(--text-dim); 899 } 900 901 @media (max-width: 768px) { 902 .sources-cat-row { justify-content: center; } 903 .sources-cat-label { width: auto; text-align: center; padding-top: 0; } 904 .source-badge { padding: 5px 10px; } 905 } 906 907 /* ================================================================ 908 TABBED PRODUCT SHOWCASE 909 ================================================================ */ 910 .showcase-section { background: var(--bg); } 911 .showcase-tabs { 912 display: flex; 913 gap: 4px; 914 margin-bottom: 40px; 915 overflow-x: auto; 916 padding-bottom: 4px; 917 -webkit-overflow-scrolling: touch; 918 } 919 .showcase-tab { 920 font-family: var(--mono); 921 font-size: 12px; 922 font-weight: 500; 923 color: var(--text-dim); 924 background: transparent; 925 border: 1px solid var(--border); 926 border-radius: 4px; 927 padding: 10px 20px; 928 cursor: pointer; 929 white-space: nowrap; 930 transition: color 0.2s, border-color 0.2s, background 0.2s; 931 } 932 .showcase-tab:hover { color: var(--text-sec); border-color: var(--text-dim); } 933 .showcase-tab.active { 934 color: var(--accent); 935 border-color: var(--accent); 936 background: rgba(8, 145, 178, 0.06); 937 } 938 .showcase-panels { position: relative; min-height: 400px; } 939 .showcase-panel { 940 display: none; 941 opacity: 0; 942 transform: translateY(12px); 943 transition: opacity 0.4s ease, transform 0.4s ease; 944 } 945 .showcase-panel.active { 946 display: grid; 947 grid-template-columns: 1fr 1fr; 948 gap: 48px; 949 align-items: center; 950 opacity: 1; 951 transform: translateY(0); 952 } 953 954 @media (max-width: 900px) { 955 .showcase-panel.active { grid-template-columns: 1fr; gap: 32px; } 956 } 957 958 /* Mockup card shared */ 959 .mockup-card { 960 background: var(--surface); 961 border: 1px solid var(--border); 962 border-radius: 8px; 963 overflow: hidden; 964 transition: transform 0.3s, box-shadow 0.3s; 965 } 966 .mockup-card:hover { 967 transform: translateY(-4px); 968 box-shadow: 0 16px 48px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(59, 130, 246, 0.1); 969 } 970 .mockup-titlebar { 971 display: flex; 972 align-items: center; 973 gap: 6px; 974 padding: 10px 16px; 975 background: var(--surface-el); 976 border-bottom: 1px solid var(--border); 977 } 978 .mockup-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); } 979 .mockup-dot:first-child { background: #ef4444; } 980 .mockup-dot:nth-child(2) { background: #eab308; } 981 .mockup-dot:nth-child(3) { background: #22c55e; } 982 .mockup-titlebar-text { 983 margin-left: 8px; 984 font-family: var(--mono); 985 font-size: 11px; 986 color: var(--text-dim); 987 } 988 .mockup-body { padding: 20px; } 989 990 /* Breakdown bar */ 991 .breakdown-bar { 992 display: flex; 993 height: 6px; 994 border-radius: 3px; 995 overflow: hidden; 996 margin: 16px 0 12px; 997 } 998 .breakdown-segment { transition: flex 0.6s ease; } 999 .breakdown-legend { 1000 display: flex; 1001 flex-wrap: wrap; 1002 gap: 10px; 1003 } 1004 .breakdown-legend-item { 1005 display: flex; 1006 align-items: center; 1007 gap: 5px; 1008 font-family: var(--mono); 1009 font-size: 10px; 1010 color: var(--text-dim); 1011 } 1012 .breakdown-legend-dot { 1013 width: 6px; 1014 height: 6px; 1015 border-radius: 1px; 1016 flex-shrink: 0; 1017 } 1018 1019 /* ETA distribution bar (stochastic tab) */ 1020 .eta-bar-wrap { 1021 margin: 20px 0; 1022 padding: 16px; 1023 background: var(--bg); 1024 border: 1px solid var(--border); 1025 border-radius: 6px; 1026 } 1027 .eta-bar-label { 1028 font-family: var(--mono); 1029 font-size: 10px; 1030 color: var(--text-dim); 1031 text-transform: uppercase; 1032 letter-spacing: 0.08em; 1033 margin-bottom: 12px; 1034 } 1035 .eta-bar { 1036 position: relative; 1037 height: 32px; 1038 background: var(--surface-el); 1039 border-radius: 4px; 1040 overflow: hidden; 1041 } 1042 .eta-bar-range { 1043 position: absolute; 1044 top: 0; 1045 bottom: 0; 1046 background: linear-gradient(90deg, rgba(8, 145, 178, 0.15), rgba(8, 145, 178, 0.3), rgba(8, 145, 178, 0.15)); 1047 border-left: 2px solid var(--accent); 1048 border-right: 2px solid var(--accent); 1049 } 1050 .eta-bar-median { 1051 position: absolute; 1052 top: 0; 1053 bottom: 0; 1054 width: 3px; 1055 background: #fff; 1056 border-radius: 1px; 1057 } 1058 .eta-bar-labels { 1059 display: flex; 1060 justify-content: space-between; 1061 margin-top: 6px; 1062 font-family: var(--mono); 1063 font-size: 10px; 1064 color: var(--text-dim); 1065 } 1066 1067 /* EP curve (underwriting tab) */ 1068 .ep-curve-wrap { 1069 margin: 16px 0; 1070 padding: 16px; 1071 background: var(--bg); 1072 border: 1px solid var(--border); 1073 border-radius: 6px; 1074 } 1075 .ep-curve-svg { 1076 width: 100%; 1077 height: 140px; 1078 } 1079 .ep-curve-path { 1080 stroke-dasharray: 400; 1081 stroke-dashoffset: 400; 1082 transition: stroke-dashoffset 1.2s ease; 1083 } 1084 .ep-curve-path.drawn { stroke-dashoffset: 0; } 1085 1086 /* PDF stack (reports tab) */ 1087 .pdf-stack { 1088 position: relative; 1089 height: 200px; 1090 margin: 16px 0; 1091 } 1092 .pdf-page { 1093 position: absolute; 1094 width: 140px; 1095 height: 180px; 1096 background: var(--surface-el); 1097 border: 1px solid var(--border); 1098 border-radius: 4px; 1099 padding: 12px; 1100 transition: transform 0.4s ease; 1101 } 1102 .pdf-page:nth-child(1) { left: 0; top: 10px; transform: rotate(-5deg); z-index: 1; } 1103 .pdf-page:nth-child(2) { left: 60px; top: 0; transform: rotate(-1deg); z-index: 2; } 1104 .pdf-page:nth-child(3) { left: 120px; top: 5px; transform: rotate(3deg); z-index: 3; } 1105 .pdf-page-title { 1106 font-family: var(--mono); 1107 font-size: 8px; 1108 color: var(--text-dim); 1109 text-transform: uppercase; 1110 letter-spacing: 0.08em; 1111 margin-bottom: 8px; 1112 } 1113 .pdf-page-line { 1114 height: 2px; 1115 background: var(--border); 1116 border-radius: 1px; 1117 margin-bottom: 4px; 1118 } 1119 .pdf-page-line.short { width: 60%; } 1120 1121 /* ================================================================ 1122 OWNERSHIP & SANCTIONS 1123 ================================================================ */ 1124 .ownership-section { background: var(--bg); } 1125 .ownership-layout { 1126 display: grid; 1127 grid-template-columns: 1fr 1fr; 1128 gap: 64px; 1129 align-items: center; 1130 } 1131 .ownership-chain { 1132 display: flex; 1133 flex-direction: column; 1134 gap: 0; 1135 align-items: center; 1136 } 1137 .ownership-entity { 1138 background: var(--surface); 1139 border: 1px solid var(--border); 1140 border-radius: 6px; 1141 padding: 16px 24px; 1142 width: 280px; 1143 text-align: center; 1144 opacity: 0; 1145 transform: translateY(20px); 1146 transition: opacity 0.5s ease, transform 0.5s ease, border-color 0.3s; 1147 } 1148 .ownership-entity.visible { 1149 opacity: 1; 1150 transform: translateY(0); 1151 } 1152 .ownership-entity:hover { border-color: var(--text-dim); } 1153 .ownership-entity-type { 1154 font-family: var(--mono); 1155 font-size: 9px; 1156 color: var(--text-dim); 1157 text-transform: uppercase; 1158 letter-spacing: 0.1em; 1159 margin-bottom: 6px; 1160 } 1161 .ownership-entity-name { 1162 font-size: 14px; 1163 font-weight: 600; 1164 color: var(--text); 1165 margin-bottom: 4px; 1166 } 1167 .ownership-entity-detail { 1168 font-family: var(--mono); 1169 font-size: 11px; 1170 color: var(--text-sec); 1171 } 1172 .ownership-entity.flagged { border-color: rgba(220, 38, 38, 0.4); } 1173 .ownership-entity.flagged .ownership-entity-detail { color: var(--risk-crit); } 1174 .ownership-connector { 1175 width: 1px; 1176 height: 28px; 1177 background: var(--border); 1178 opacity: 0; 1179 transition: opacity 0.4s ease; 1180 } 1181 .ownership-connector.visible { opacity: 1; } 1182 1183 .ownership-metrics { 1184 display: grid; 1185 grid-template-columns: 1fr 1fr; 1186 gap: 12px; 1187 margin-top: 20px; 1188 } 1189 .ownership-metric-card { 1190 background: var(--surface); 1191 border: 1px solid var(--border); 1192 border-radius: 6px; 1193 padding: 16px; 1194 } 1195 .ownership-metric-label { 1196 font-family: var(--mono); 1197 font-size: 10px; 1198 color: var(--text-dim); 1199 text-transform: uppercase; 1200 letter-spacing: 0.08em; 1201 margin-bottom: 6px; 1202 } 1203 .ownership-metric-value { 1204 font-family: var(--mono); 1205 font-size: 24px; 1206 font-weight: 700; 1207 line-height: 1; 1208 } 1209 1210 @media (max-width: 900px) { 1211 .ownership-layout { grid-template-columns: 1fr; gap: 40px; } 1212 } 1213 1214 /* ================================================================ 1215 FUEL & CII 1216 ================================================================ */ 1217 .fuel-section { 1218 background: var(--surface); 1219 border-top: 1px solid var(--border); 1220 border-bottom: 1px solid var(--border); 1221 } 1222 .fuel-layout { 1223 display: grid; 1224 grid-template-columns: 1fr 1fr; 1225 gap: 64px; 1226 align-items: center; 1227 } 1228 .cii-scale { 1229 display: flex; 1230 flex-direction: column; 1231 gap: 16px; 1232 margin: 24px 0; 1233 } 1234 .cii-bar-wrap { position: relative; } 1235 .cii-bar
1235 { 1236 display: flex; 1237 height: 40px; 1238 border-radius: 6px; 1239 overflow: hidden; 1240 } 1241 .cii-bar-seg { 1242 flex: 1; 1243 display: flex; 1244 align-items: center; 1245 justify-content: center; 1246 font-family: var(--mono); 1247 font-size: 14px; 1248 font-weight: 700; 1249 color: #fff; 1250 } 1251 .cii-bar-seg.a { background: #059669; } 1252 .cii-bar-seg.b { background: #34d399; color: #0d1420; } 1253 .cii-bar-seg.c { background: #fbbf24; color: #0d1420; } 1254 .cii-bar-seg.d { background: #f97316; } 1255 .cii-bar-seg.e { background: #dc2626; } 1256 .cii-pointer { 1257 position: absolute; 1258 bottom: -12px; 1259 width: 0; 1260 height: 0; 1261 border-left: 8px solid transparent; 1262 border-right: 8px solid transparent; 1263 border-bottom: 10px solid var(--text); 1264 transition: left 1s cubic-bezier(0.16, 1, 0.3, 1); 1265 left: 10%; 1266 } 1267 .cii-labels { 1268 display: flex; 1269 font-family: var(--mono); 1270 font-size: 10px; 1271 color: var(--text-dim); 1272 } 1273 .cii-labels span { flex: 1; text-align: center; } 1274 .fuel-metrics { 1275 display: grid; 1276 grid-template-columns: repeat(2, 1fr); 1277 gap: 12px; 1278 } 1279 .fuel-metric { 1280 background: var(--bg); 1281 border: 1px solid var(--border); 1282 border-radius: 6px; 1283 padding: 16px; 1284 } 1285 .fuel-metric-label { 1286 font-family: var(--mono); 1287 font-size: 10px; 1288 color: var(--text-dim); 1289 text-transform: uppercase; 1290 letter-spacing: 0.08em; 1291 margin-bottom: 6px; 1292 } 1293 .fuel-metric-value { 1294 font-family: var(--mono); 1295 font-size: 22px; 1296 font-weight: 700; 1297 color: var(--text); 1298 line-height: 1; 1299 } 1300 .fuel-metric-unit { 1301 font-family: var(--mono); 1302 font-size: 11px; 1303 color: var(--text-dim); 1304 margin-top: 4px; 1305 } 1306 1307 @media (max-width: 900px) { 1308 .fuel-layout { grid-template-columns: 1fr; gap: 40px; } 1309 } 1310 1311 /* ================================================================ 1312 OCEAN ROUTING ANIMATION 1313 ================================================================ */ 1314 .routing-section { background: var(--bg); } 1315 .routing-layout { 1316 display: grid; 1317 grid-template-columns: 1fr 1fr; 1318 gap: 64px; 1319 align-items: center; 1320 } 1321 .routing-map-wrap { 1322 width: 100%; 1323 position: relative; 1324 } 1325 .routing-map { 1326 width: 100%; 1327 height: 340px; 1328 border-radius: 8px; 1329 border: 1px solid var(--border); 1330 background: var(--surface); 1331 } 1332 .routing-map .leaflet-control-attribution { background: rgba(12, 14, 18, 0.8) !important; color: var(--text-dim) !important; font-size: 9px !important; } 1333 .routing-map .leaflet-control-attribution a { color: var(--text-dim) !important; } 1334 .routing-map .leaflet-control-zoom a { background: var(--surface) !important; color: var(--text) !important; border-color: var(--border) !important; } 1335 .routing-map-label { 1336 position: absolute; 1337 bottom: 12px; 1338 left: 12px; 1339 font-family: var(--mono); 1340 font-size: 10px; 1341 color: var(--text-dim); 1342 background: rgba(7, 10, 16, 0.85); 1343 padding: 4px 10px; 1344 border-radius: 4px; 1345 border: 1px solid var(--border); 1346 z-index: 500; 1347 pointer-events: none; 1348 } 1349 .routing-map-loading { 1350 display: flex; 1351 align-items: center; 1352 justify-content: center; 1353 height: 100%; 1354 color: var(--text-dim); 1355 font-family: var(--mono); 1356 font-size: 12px; 1357 } 1358 .route-showcase-label { 1359 background: rgba(7, 10, 16, 0.85) !important; 1360 border: 1px solid var(--border) !important; 1361 color: var(--text) !important; 1362 font-family: var(--mono) !important; 1363 font-size: 10px !important; 1364 padding: 2px 8px !important; 1365 border-radius: 3px !important; 1366 box-shadow: none !important; 1367 } 1368 .route-showcase-label::before { border-top-color: rgba(7, 10, 16, 0.85) !important; } 1369 1370 @media (max-width: 900px) { 1371 .routing-layout { grid-template-columns: 1fr; gap: 40px; } 1372 } 1373 1374 /* ================================================================ 1375 VESSELWATCH ALERTS 1376 ================================================================ */ 1377 .alerts-section { background: var(--bg); } 1378 .alerts-layout { 1379 display: grid; 1380 grid-template-columns: 1fr 1fr; 1381 gap: 64px; 1382 align-items: center; 1383 } 1384 .alerts-layout-reverse { direction: rtl; } 1385 .alerts-layout-reverse > * { direction: ltr; } 1386 .alert-feed { display: flex; flex-direction: column; gap: 1px; } 1387 .alert-item { 1388 display: flex; 1389 align-items: flex-start; 1390 gap: 12px; 1391 padding: 12px 16px; 1392 background: rgba(22, 26, 34, 0.6); 1393 border-left: 3px solid; 1394 transition: background 0.2s; 1395 } 1396 .alert-item:hover { background: rgba(32, 37, 46, 0.6); } 1397 .alert-item[data-severity="critical"] { border-left-color: var(--risk-crit); } 1398 .alert-item[data-severity="high"] { border-left-color: var(--risk-high); } 1399 .alert-item[data-severity="medium"] { border-left-color: var(--risk-mod); } 1400 .alert-item[data-severity="low"] { border-left-color: var(--risk-low); } 1401 .alert-badge { 1402 font-family: var(--mono); 1403 font-size: 9px; 1404 font-weight: 600; 1405 letter-spacing: 0.06em; 1406 padding: 2px 6px; 1407 border-radius: 2px; 1408 flex-shrink: 0; 1409 text-transform: uppercase; 1410 } 1411 .alert-badge[data-severity="critical"] { background: rgba(220, 38, 38, 0.15); color: var(--risk-crit); } 1412 .alert-badge[data-severity="high"] { background: rgba(234, 88, 12, 0.15); color: var(--risk-high); } 1413 .alert-badge[data-severity="medium"] { background: rgba(37, 99, 235, 0.15); color: var(--risk-mod); } 1414 .alert-badge[data-severity="low"] { background: rgba(22, 163, 74, 0.15); color: var(--risk-low); } 1415 .alert-content { flex: 1; min-width: 0; } 1416 .alert-type { 1417 font-family: var(--mono); 1418 font-size: 10px; 1419 font-weight: 600; 1420 text-transform: uppercase; 1421 letter-spacing: 0.06em; 1422 color: var(--text-dim); 1423 margin-bottom: 3px; 1424 } 1425 .alert-msg { font-size: 13px; color: var(--text-sec); line-height: 1.5; } 1426 .alert-vessel { color: var(--text); font-weight: 600; } 1427 .alert-time { font-family: var(--mono); font-size: 10px; color: var(--text-dim); margin-top: 4px; } 1428 .alert-types-grid { 1429 display: grid; 1430 grid-template-columns: 1fr 1fr 1fr; 1431 gap: 8px; 1432 margin-top: 16px; 1433 } 1434 .alert-type-badge { 1435 display: flex; 1436 align-items: center; 1437 gap: 6px; 1438 padding: 6px 10px; 1439 background: var(--surface); 1440 border: 1px solid var(--border); 1441 border-radius: 4px; 1442 font-family: var(--mono); 1443 font-size: 10px; 1444 color: var(--text-sec); 1445 } 1446 .alert-type-dot { 1447 width: 6px; 1448 height: 6px; 1449 border-radius: 50%; 1450 flex-shrink: 0; 1451 } 1452 1453 @media (max-width: 900px) { 1454 .alerts-layout { grid-template-columns: 1fr; gap: 40px; } 1455 .alerts-layout-reverse { direction: ltr; } 1456 .alert-types-grid { grid-template-columns: 1fr 1fr; } 1457 } 1458 @media (max-width: 600px) { 1459 .alert-types-grid { grid-template-columns: 1fr; } 1460 } 1461 1462 1463 /* ================================================================ 1464 API PREVIEW â 3 TABBED EXAMPLES 1465 ================================================================ */ 1466 .api-section { background: var(--bg); } 1467 .api-grid { 1468 display: grid; 1469 grid-template-columns: 1fr 1fr; 1470 gap: 48px; 1471 align-items: start; 1472 } 1473 .api-grid > * { min-width: 0; } /* a long <pre> line must scroll inside .code-body, not widen the grid track */ 1474 .api-tabs { 1475 display: flex; 1476 flex-wrap: wrap; 1477 gap: 4px; 1478 margin-bottom: 16px; 1479 } 1480 .api-tab { 1481 font-family: var(--mono); 1482 font-size: 11px; 1483 font-weight: 500; 1484 color: var(--text-dim); 1485 background: transparent; 1486 border: 1px solid var(--border); 1487 border-radius: 4px; 1488 padding: 6px 14px; 1489 cursor: pointer; 1490 transition: color 0.2s, border-color 0.2s, background 0.2s; 1491 } 1492 .api-tab:hover { color: var(--text-sec); border-color: var(--text-dim); } 1493 .api-tab.active { 1494 color: var(--accent); 1495 border-color: var(--accent); 1496 background: rgba(8, 145, 178, 0.06); 1497 } 1498 .api-panel { display: none; } 1499 .api-panel.active { display: flex; flex-direction: column; gap: 16px; } 1500 .code-block { 1501 background: var(--surface); 1502 border: 1px solid var(--border); 1503 border-radius: 6px; 1504 overflow: hidden; 1505 } 1506 .code-header { 1507 display: flex; 1508 align-items: center; 1509 gap: 8px; 1510 padding: 10px 16px; 1511 background: var(--surface-el); 1512 border-bottom: 1px solid var(--border); 1513 font-family: var(--mono); 1514 font-size: 11px; 1515 color: var(--text-dim); 1516 } 1517 .code-method { color: var(--risk-low); font-weight: 600; } 1518 .code-body { 1519 padding: 16px; 1520 font-family: var(--mono); 1521 font-size: 12px; 1522 line-height: 1.7; 1523 color: var(--text-sec); 1524 overflow-x: auto; 1525 } 1526 .code-body .ck { color: var(--accent); }
1527 .code-body .cv { color: var(--risk-low); } 1528 .code-body .cn { color: var(--risk-elev); } 1529 .code-body .cs { color: #f472b6; } 1530 .code-body .cb { color: var(--text-dim); } 1531 .api-features { list-style: none; display: flex; flex-direction: column; gap: 12px; margin-top: 24px; } 1532 .api-feature { display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--text-sec); } 1533 .api-feature-check { 1534 width: 16px; 1535 height: 16px; 1536 display: flex; 1537 align-items: center; 1538 justify-content: center; 1539 background: rgba(22, 163, 74, 0.1); 1540 border-radius: 3px; 1541 flex-shrink: 0; 1542 } 1543 .api-feature-check svg { width: 10px; height: 10px; stroke: var(--risk-low); fill: none; stroke-width: 2.5; } 1544 1545 @media (max-width: 900px) { .api-grid { grid-template-columns: 1fr; } } 1546 1547 /* ================================================================ 1548 CTA 1549 ================================================================ */ 1550 .cta-section { 1551 padding: 100px 0; 1552 text-align: center; 1553 position: relative; 1554 background: linear-gradient(180deg, var(--bg) 0%, rgba(22, 26, 34, 0.5) 50%, var(--bg) 100%); 1555 } 1556 .cta-section::before { 1557 content: ''; 1558 position: absolute; 1559 top: 50%; 1560 left: 50%; 1561 transform: translate(-50%, -50%); 1562 width: 600px; 1563 height: 400px; 1564 background: radial-gradient(circle, rgba(59, 130, 246, 0.06) 0%, transparent 70%); 1565 pointer-events: none; 1566 } 1567 .cta-title { 1568 font-family: var(--display); 1569 font-size: clamp(1.8rem, 3vw, 2.6rem); 1570 color: var(--text); 1571 margin-bottom: 16px; 1572 position: relative; 1573 font-weight: 400; 1574 } 1575 .cta-badge { 1576 display: inline-block; 1577 font-family: var(--mono); 1578 font-size: 11px; 1579 font-weight: 600; 1580 color: var(--risk-low); 1581 background: rgba(22, 163, 74, 0.1); 1582 border: 1px solid rgba(22, 163, 74, 0.25); 1583 padding: 4px 12px; 1584 border-radius: 3px; 1585 margin-bottom: 24px; 1586 letter-spacing: 0.06em; 1587 position: relative; 1588 } 1589 .cta-sub { 1590 font-size: 16px; 1591 color: var(--text-sec); 1592 margin-bottom: 36px; 1593 position: relative; 1594 max-width: 600px; 1595 margin-left: auto; 1596 margin-right: auto; 1597 } 1598 .cta-actions { 1599 display: flex; 1600 align-items: center; 1601 justify-content: center; 1602 gap: 24px; 1603 position: relative; 1604 flex-wrap: wrap; 1605 } 1606 .cta-email { 1607 font-family: var(--mono); 1608 font-size: 14px; 1609 color: var(--text-dim); 1610 margin-top: 24px; 1611 position: relative; 1612 } 1613 .cta-email a { 1614 color: var(--text-sec); 1615 border-bottom: 1px solid var(--border); 1616 transition: color 0.2s, border-color 0.2s; 1617 } 1618 .cta-email a:hover { color: var(--accent); border-color: var(--accent); } 1619 1620 /* ================================================================ 1621 FOOTER 1622 ================================================================ */ 1623 .footer { 1624 border-top: 1px solid var(--border); 1625 padding: 40px 0; 1626 } 1627 .footer-inner { 1628 display: flex; 1629 align-items: center; 1630 justify-content: space-between; 1631 flex-wrap: wrap; 1632 gap: 24px; 1633 } 1634 .footer-brand { display: flex; align-items: center; gap: 8px; } 1635 .footer-brand-mark { width: 18px; height: 18px; object-fit: contain; } 1636 .footer-brand-text { 1637 font-family: var(--mono); 1638 font-size: 12px; 1639 font-weight: 600; 1640 letter-spacing: 2px; 1641 color: var(--text-dim); 1642 } 1643 .footer-links { display: flex; gap: 24px; list-style: none; flex-wrap: wrap; } 1644 .footer-links a { font-size: 13px; color: var(--text-dim); transition: color 0.2s; } 1645 .footer-links a:hover { color: var(--text-sec); } 1646 .footer-right { font-size: 12px; color: var(--text-dim); } 1647 .footer-powered { 1648 font-size: 11px; 1649 color: var(--text-dim); 1650 margin-top: 16px; 1651 text-align: center; 1652 font-family: var(--mono); 1653 letter-spacing: 0.03em; 1654 } 1655 1656 /* ================================================================ 1657 LIVE / DEMO BADGES 1658 ================================================================ */ 1659 .live-badge { 1660 display: inline-flex; 1661 align-items: center; 1662 gap: 5px; 1663 font-family: var(--mono); 1664 font-size: 9px; 1665 font-weight: 700; 1666 letter-spacing: 0.1em; 1667 color: var(--risk-low); 1668 background: rgba(22, 163, 74, 0.1); 1669 border: 1px solid rgba(22, 163, 74, 0.25); 1670 padding: 2px 7px; 1671 border-radius: 3px; 1672 } 1673 .live-dot { 1674 width: 5px; 1675 height: 5px; 1676 background: var(--risk-low); 1677 border-radius: 50%; 1678 animation: tickerPulse 1.5s ease-in-out infinite; 1679 flex-shrink: 0; 1680 } 1681 .demo-badge { 1682 display: inline-flex; 1683 align-items: center; 1684 font-family: var(--mono); 1685 font-size: 9px; 1686 font-weight: 600; 1687 letter-spacing: 0.08em; 1688 color: var(--text-dim); 1689 background: rgba(106, 110, 122, 0.08); 1690 border: 1px solid rgba(106, 110, 122, 0.2); 1691 padding: 2px 7px; 1692 border-radius: 3px; 1693 } 1694 1695 /* ================================================================ 1696 FREE VOYAGE SCORER 1697 ================================================================ */ 1698 .scorer-form { margin-bottom: 24px; } 1699 .scorer-fields { display: flex; gap: 8px; align-items: flex-end; margin-bottom: 16px; } 1700 .scorer-field { flex: 1; position: relative; } 1701 .scorer-field label { 1702 display: block; 1703 font-family: var(--mono); 1704 font-size: 10px; 1705 font-weight: 600; 1706 color: var(--text-dim); 1707 text-transform: uppercase; 1708 letter-spacing: 0.1em; 1709 margin-bottom: 6px; 1710 } 1711 .scorer-input { 1712 width: 100%; 1713 padding: 12px 14px; 1714 background: var(--surface); 1715 border: 1px solid var(--border); 1716 border-radius: 4px; 1717 color: var(--text); 1718 font-family: var(--mono); 1719 font-size: 13px; 1720 outline: none; 1721 transition: border-color 0.2s, box-shadow 0.2s; 1722 } 1723 .scorer-input::placeholder { color: var(--text-dim); font-family: var(--body); font-size: 12px; } 1724 .scorer-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } 1725 .scorer-input.has-value { font-weight: 500; } 1726 .scorer-arrow-icon { font-size: 18px; color: var(--text-dim); padding-bottom: 12px; flex-shrink: 0; } 1727 1728 .scorer-dropdown { 1729 display: none; 1730 position: absolute; 1731 top: 100%; 1732 left: 0; 1733 right: 0; 1734 margin-top: 4px; 1735 background: var(--surface-el); 1736 border: 1px solid var(--border); 1737 border-radius: 4px; 1738 max-height: 240px; 1739 overflow-y: auto; 1740 z-index: 100; 1741 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); 1742 } 1743 .scorer-dropdown.open { display: block; } 1744 .scorer-dd-item { 1745 display: flex; 1746 align-items: center; 1747 gap: 8px; 1748 padding: 10px 14px; 1749 cursor: pointer; 1750 font-size: 13px; 1751 color: var(--text); 1752 border-left: 3px solid transparent; 1753 transition: background 0.1s, border-color 0.1s; 1754 } 1755 .scorer-dd-item:hover, 1756 .scorer-dd-item.hl { 1757 background: rgba(255, 255, 255, 0.04); 1758 border-left-color: var(--accent); 1759 } 1760 .scorer-dd-flag { font-size: 15px; flex-shrink: 0; } 1761 .scorer-dd-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 1762 .scorer-dd-code { font-family: var(--mono); font-size: 11px; color: var(--text-dim); flex-shrink: 0; } 1763 .scorer-dd-msg { padding: 12px 14px; font-size: 12px; color: var(--text-dim); } 1764 1765 .scorer-btn { 1766 width: 100%; 1767 padding: 14px 24px; 1768 background: var(--accent); 1769 color: #fff; 1770 font-size: 14px; 1771 font-weight: 600; 1772 border: none; 1773 border-radius: 4px; 1774 cursor: pointer; 1775 display: flex; 1776 align-items: center; 1777 justify-content: center; 1778 gap: 8px; 1779 transition: background 0.2s, transform 0.15s, box-shadow 0.2s; 1780 letter-spacing: 0.02em; 1781 } 1782 .scorer-btn:hover:not(:disabled) { 1783 background: var(--accent-hover); 1784 transform: translateY(-1px); 1785 box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3); 1786 } 1787 .scorer-btn:disabled { opacity: 0.4;
1787 cursor: not-allowed; transform: none; } 1788 .scorer-btn:active:not(:disabled) { transform: translateY(0); } 1789 .scorer-spinner { width: 18px; height: 18px; animation: scorerSpin 0.8s linear infinite; } 1790 @keyframes scorerSpin { to { transform: rotate(360deg); } } 1791 1792 .scorer-popular { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } 1793 .scorer-popular-label { font-family: var(--mono); font-size: 11px; color: var(--text-dim); } 1794 .scorer-popular-btn { 1795 font-family: var(--mono); 1796 font-size: 11px; 1797 color: var(--text-sec); 1798 background: var(--surface); 1799 border: 1px solid var(--border); 1800 padding: 4px 10px; 1801 border-radius: 3px; 1802 cursor: pointer; 1803 transition: border-color 0.2s, color 0.2s, background 0.2s; 1804 } 1805 .scorer-popular-btn:hover { border-color: var(--accent); color: var(--text); background: var(--surface-el); } 1806 1807 .scorer-error { 1808 margin-top: 12px; 1809 padding: 10px 14px; 1810 background: rgba(220, 38, 38, 0.08); 1811 border: 1px solid rgba(220, 38, 38, 0.2); 1812 border-radius: 4px; 1813 font-family: var(--mono); 1814 font-size: 12px; 1815 color: var(--risk-crit); 1816 display: none; 1817 } 1818 .scorer-error.visible { display: block; } 1819 1820 /* Results Panel */ 1821 .scorer-results { 1822 /* Belt and braces with the pointer-events fixes on the hero's decorative 1823 layers: lift the results panel into the same stacking position 1824 .hero-content uses, so a future absolutely-positioned hero decoration 1825 cannot silently make this panel unclickable again. */ 1826 position: relative; 1827 z-index: 2; 1828 max-height: 0; 1829 overflow: hidden; 1830 opacity: 0; 1831 transition: max-height 0.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.5s ease, margin-top 0.5s ease; 1832 } 1833 .scorer-results.visible { max-height: 4000px; opacity: 1; margin-top: 48px; } 1834 .results-inner { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; } 1835 .results-header { 1836 display: flex; 1837 align-items: center; 1838 justify-content: space-between; 1839 padding: 16px 24px; 1840 border-bottom: 1px solid var(--border); 1841 background: var(--surface-el); 1842 flex-wrap: wrap; 1843 gap: 12px; 1844 } 1845 .results-route-name { font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--text); } 1846 .results-route-name span { color: var(--text-dim); } 1847 .results-scored-at { font-family: var(--mono); font-size: 10px; color: var(--text-dim); margin-top: 2px; } 1848 .results-score-again-btn { 1849 font-family: var(--mono); 1850 font-size: 12px; 1851 color: var(--accent); 1852 background: none; 1853 border: 1px solid var(--accent); 1854 padding: 6px 14px; 1855 border-radius: 4px; 1856 cursor: pointer; 1857 transition: background 0.2s, color 0.2s; 1858 } 1859 .results-score-again-btn:hover { background: var(--accent); color: #fff; } 1860 .results-body { padding: 24px; } 1861 .results-grid { display: grid; grid-template-columns: 220px 1fr; gap: 24px; margin-bottom: 24px; } 1862 .results-score-col { display: flex; flex-direction: column; align-items: center; gap: 16px; } 1863 .results-meta { text-align: center; } 1864 .results-meta-item { font-family: var(--mono); font-size: 12px; color: var(--text-sec); margin-bottom: 4px; } 1865 .results-meta-item strong { color: var(--text); font-weight: 600; } 1866 .results-confidence { 1867 font-family: var(--mono); 1868 font-size: 10px; 1869 padding: 3px 8px; 1870 border-radius: 3px; 1871 margin-top: 4px; 1872 display: inline-block; 1873 } 1874 .results-confidence.good { color: var(--risk-low); background: rgba(22, 163, 74, 0.1); } 1875 .results-confidence.limited { color: var(--risk-elev); background: rgba(217, 119, 6, 0.1); } 1876 .results-map-col { position: relative; } 1877 .results-map { 1878 height: 320px; 1879 border-radius: 6px; 1880 border: 1px solid var(--border); 1881 overflow: hidden; 1882 background: #070a10; 1883 } 1884 .results-map .leaflet-control-attribution { background: rgba(12, 14, 18, 0.8) !important; color: var(--text-dim) !important; font-size: 9px !important; } 1885 .results-map .leaflet-control-attribution a { color: var(--text-dim) !important; } 1886 .results-map .leaflet-control-zoom a { background: var(--surface) !important; color: var(--text) !important; border-color: var(--border) !important; } 1887 .results-waypoints { 1888 display: flex; 1889 align-items: center; 1890 gap: 6px; 1891 flex-wrap: wrap; 1892 padding: 16px 0; 1893 border-bottom: 1px solid var(--border); 1894 margin-bottom: 24px; 1895 } 1896 .results-wp-label { font-family: var(--mono); font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.08em; } 1897 .results-wp-tag { font-family: var(--mono); font-size: 11px; color: var(--text-sec); background: rgba(255,255,255,0.04); border: 1px solid var(--border); padding: 3px 10px; border-radius: 3px; } 1898 .results-wp-arrow { color: var(--text-dim); font-size: 10px; } 1899 .results-drivers-title { font-family: var(--mono); font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 12px; } 1900 .results-drivers { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; } 1901 .driver-card { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 16px; transition: border-color 0.2s; } 1902 .driver-card:hover { border-color: var(--text-dim); } 1903 .driver-card-name { font-family: var(--mono); font-size: 10px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px; } 1904 .driver-card-score { font-family: var(--mono); font-size: 28px; font-weight: 700; line-height: 1; margin-bottom: 4px; } 1905 .driver-card-weight { font-family: var(--mono); font-size: 10px; color: var(--text-dim); margin-bottom: 10px; } 1906 .driver-card-bar { height: 3px; background: var(--border); border-radius: 2px; margin-bottom: 10px; overflow: hidden; } 1907 .driver-card-bar-fill { height: 100%; border-radius: 2px; transition: width 0.6s ease; } 1908 .driver-card-detail { font-size: 12px; color: var(--text-sec); line-height: 1.5; } 1909 /* (removed) .results-gate* â the blur/lock signup gate on the lane result. The full 1910 computed result is shown unconditionally now;
1910 capture lives in /lane-capture.js. */ 1911 .results-footer { display: flex; align-items: center; justify-content: center; gap: 16px; padding: 20px 24px; border-top: 1px solid var(--border); flex-wrap: wrap; } 1912 1913 @media (max-width: 900px) { 1914 .scorer-fields { flex-direction: column; gap: 12px; } 1915 .scorer-arrow-icon { display: none; } 1916 .results-grid { grid-template-columns: 1fr; } 1917 .results-score-col { flex-direction: row; justify-content: center; flex-wrap: wrap; } 1918 } 1919 @media (max-width: 600px) { 1920 .results-drivers { grid-template-columns: 1fr; } 1921 .results-map { height: 220px; } 1922 .scorer-popular-btn { font-size: 10px; padding: 3px 7px; } 1923 } 1924 /* Mobile: body{overflow-x:hidden} alone propagates to the viewport and stops clipping the body, so decorative overflow made mobile Chrome zoom the layout viewport out (innerWidth 495 on a 390 screen, hamburger off-screen). Setting html too makes the body clip. Measured 2026-09-13. */ 1925 html { overflow-x: hidden; } 1926 /* The hero check form is a non-wrapping flex row; at phone width the button sat past the edge (right=457 on 390). */ 1927 #landingCheckForm { flex-wrap: wrap; } 1928 #landing-imo { min-width: 0; flex: 1 1 200px; } 1929 #landing-check-btn { flex: 0 0 auto; } 1930 </style> 1931 <link rel="alternate" type="application/rss+xml" title="ArcNautical Blog" href="/blog/feed.xml"> 1932
1932<script type="application/ld+json"> 1933 { 1934 "@context": "https://schema.org", 1935 "@type": "SoftwareApplication", 1936 "name": "ArcNautical", 1937 "applicationCategory": "BusinessApplication", 1938 "operatingSystem": "Web", 1939 "url": "https://arcnautical.com", 1940 "description": "Maritime intelligence platform providing route-level voyage risk scoring, vessel vetting, sanctions screening, and fleet monitoring for marine insurance underwriters and compliance teams.", 1941 "offers": { 1942 "@type": "Offer", 1943 "price": "0", 1944 "priceCurrency": "USD", 1945 "description": "Free tier available" 1946 }, 1947 "author": { 1948 "@type": "Organization", 1949 "name": "ArcNautical", 1950 "url": "https://arcnautical.com" 1951 }, 1952 "featureList": [ 1953 "Voyage risk scoring with 11 intelligence signals", 1954 "Real-time vessel tracking via AIS", 1955 "Container tracking across 21 major carriers", 1956 "Real-time port congestion via IMF PortWatch", 1957 "Freight market index monitoring", 1958 "Sanctions screening (OFAC, UN, EU)", 1959 "Ownership chain resolution via GLEIF", 1960 "Monte Carlo stochastic analysis", 1961 "Fuel and CII compliance calculations", 1962 "PDF report generation", 1963 "Fleet monitoring with threat overlays" 1964 ] 1965 } 1966 </script>
1966 1967
1967<script type="application/ld+json"> 1968 { 1969 "@context": "https://schema.org", 1970 "@type": "Organization", 1971 "@id": "https://arcnautical.com/#org", 1972 "name": "ArcNautical", 1973 "url": "https://arcnautical.com", 1974 "logo": "https://arcnautical.com/og-image.png", 1975 "description": "Maritime intelligence platform for voyage risk scoring, vessel vetting, and fleet monitoring.", 1976 "sameAs": [ 1977 "https://github.com/arcnautical", 1978 "https://www.npmjs.com/org/arcnautical" 1979 ] 1980 } 1981 </script>
1981 1982</head> 1983<body> 1984 1985 <!-- SCROLL PROGRESS --> 1986 <div class="scroll-progress" id="scrollProgress"></div> 1987 1988 <!-- ============================================================ 1989 NAVIGATION 1990 ============================================================ --> 1991 <nav class="nav" id="nav"> 1992 <div class="nav-inner"> 1993 <a href="#" class="nav-brand"> 1994 <img class="nav-brand-mark" src="/logo.svg" alt="ArcNautical" width="24" height="24"> 1995 ARCNAUTICAL 1996 </a> 1997 <button class="nav-hamburger" id="navHamburger" aria-label="Toggle menu"> 1998 <span></span><span></span><span></span> 1999 </button> 2000 <ul class="nav-links" id="navLinks"> 2001 <li><a href="#how-it-works">How It Works</a></li> 2002 <li><a href="#api">API</a></li> 2003 <li><a href="#signals">Signals</a></li> 2004 <li><a href="#products">Products</a></li> 2005 <li><a href="#pricing">Pricing</a></li> 2006 <li><a href="/blog">Blog</a></li> 2007 <li><a href="/arcnautical.html#/congestion">Port Congestion</a></li> 2008 <li><a href="/check">Vessel Check</a></li> 2009 <li><a href="/arcnautical.html#/login" class="nav-signin">Sign In</a></li> 2010 <li><a href="/arcnautical.html#/register" class="nav-cta">Sign Up Free</a></li> 2011 </ul> 2012 </div> 2013 </nav> 2014 2015 <!-- ============================================================ 2016 HERO 2017 ============================================================ --> 2018 <section class="hero" id="hero"> 2019 <div class="hero-grid"></div> 2020 <div class="hero-routes"> 2021 <svg viewBox="0 0 1200 700" preserveAspectRatio="xMidYMid slice"> 2022 <defs> 2023 <linearGradient id="routeGrad" x1="0%" y1="0%" x2="100%" y2="0%"> 2024 <stop offset="0%" stop-color="#0891b2" stop-opacity="0"/> 2025 <stop offset="50%" stop-color="#0891b2" stop-opacity="0.6"/> 2026 <stop offset="100%" stop-color="#0891b2" stop-opacity="0"/> 2027 </linearGradient> 2028 </defs> 2029 <!-- Route: Asia to Europe --> 2030 <path id="heroRoute1" d="M950,380 Q850,300 750,340 Q600,380 500,350 Q400,320 300,300 Q200,280 150,320" stroke="url(#routeGrad)" fill="none" stroke-width="1" opacity="0.4"/> 2031 <!-- Route: Gulf to East Africa --> 2032 <path d="M700,300 Q650,350 620,400 Q580,450 550,480" stroke="url(#routeGrad)" fill="none" stroke-width="0.8" opacity="0.3"> 2033 <animate attributeName="stroke-dasharray" values="0,1000;1000,0" dur="6s" repeatCount="indefinite"/> 2034 </path> 2035 <!-- Route: Europe to Americas --> 2036 <path d="M200,280 Q150,250 100,260 Q50,300 30,350" stroke="url(#routeGrad)" fill="none" stroke-width="0.8" opacity="0.25"> 2037 <animate attributeName="stroke-dasharray" values="0,800;800,0" dur="7s" repeatCount="indefinite"/> 2038 </path> 2039 <!-- Port dots --> 2040 <circle cx="950" cy="380" r="3" fill="#0891b2" opacity="0.5"><animate attributeName="r" values="2;4;2" dur="3s" repeatCount="indefinite"/></circle> 2041 <circle cx="750" cy="340" r="2.5" fill="#0891b2" opacity="0.4"/> 2042 <circle cx="500" cy="350" r="2.5" fill="#0891b2" opacity="0.4"/> 2043 <circle cx="300" cy="300" r="2.5" fill="#0891b2" opacity="0.4"/> 2044 <circle cx="150" cy="320" r="3" fill="#0891b2" opacity="0.5"><animate attributeName="r" values="2;4;2" dur="4s" repeatCount="indefinite"/></circle> 2045 <circle cx="620" cy="400" r="2" fill="#0891b2" opacity="0.3"/> 2046 <circle cx="700" cy="300" r="2" fill="#d97706" opacity="0.4"><animate attributeName="opacity" values="0.4;0.7;0.4" dur="2s" repeatCount="indefinite"/></circle> 2047 <!-- Traveling dots --> 2048 <circle r="2.5" fill="#0891b2" opacity="0.8"> 2049 <animateMotion dur="8s" repeatCount="indefinite" path="M950,380 Q850,300 750,340 Q600,380 500,350 Q400,320 300,300 Q200,280 150,320"/> 2050 </circle> 2051 <circle r="2" fill="#06b6d4" opacity="0.6"> 2052 <animateMotion dur="6s" repeatCount="indefinite" path="M700,300 Q650,350 620,400 Q580,450 550,480"/> 2053 </circle> 2054 </svg> 2055 </div> 2056 <div class="hero-grain"></div> 2057 <div class="hero-glow"></div> 2058 2059 <div class="container"> 2060 <div class="hero-content"> 2061 <div class="hero-text"> 2062 <h1>Screen any vessel before you<br><em>fix, insure, or finance</em> it.</h1> 2063 <p class="hero-sub">
2064 Sanctions, ownership exposure, and a vetting grade for any IMO — checked against 2065 OFAC, EU, UN and the full ownership chain. Free. No signup. 2066 </p> 2067 <a href="/check" class="btn-primary" style="margin-bottom:12px;">Check a vessel →</a> 2068 <div style="font-family:var(--mono);font-size:12px;color:var(--text-dim);margin-bottom:24px;letter-spacing:0.02em;">Or from code, no key: <a href="#api" style="color:var(--accent);text-decoration:none;">GET /api/v1/vessels/{imo}/check →</a></div> 2069 <div class="scorer-secondary-label" style="font-family:var(--mono);font-size:12px;color:var(--text-dim);margin-bottom:10px;letter-spacing:0.02em;">Also — planning a voyage? Score any route:</div> 2070 <div class="scorer-form" id="scorerForm"> 2071 <div class="scorer-fields"> 2072 <div class="scorer-field"> 2073 <label for="originInput">Origin Port</label> 2074 <input type="text" class="scorer-input" id="originInput" 2075 placeholder="e.g. Singapore, SGSIN" 2076 autocomplete="off" spellcheck="false"> 2077 <div class="scorer-dropdown" id="originDropdown"></div> 2078 </div> 2079 <span class="scorer-arrow-icon">→</span> 2080 <div class="scorer-field"> 2081 <label for="destInput">Destination Port</label> 2082 <input type="text" class="scorer-input" id="destInput" 2083 placeholder="e.g. Rotterdam, NLRTM" 2084 autocomplete="off" spellcheck="false"> 2085 <div class="scorer-dropdown" id="destDropdown"></div> 2086 </div> 2087 </div> 2088 <button class="scorer-btn" id="scoreBtn" disabled> 2089 <span id="scoreBtnText">Score This Route</span> 2090 <svg id="scoreBtnSpinner" class="scorer-spinner" viewBox="0 0 24 24" style="display:none;"> 2091 <circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2.5" 2092 stroke-dasharray="31.4 31.4" stroke-linecap="round"/> 2093 </svg> 2094 </button> 2095 <div class="scorer-error" id="scorerError"></div> 2096 </div> 2097 <div class="scorer-popular"> 2098 <span class="scorer-popular-label">Try:</span> 2099 <button class="scorer-popular-btn" data-origin="SGSIN" data-dest="NLRTM">Singapore → Rotterdam</button> 2100 <button class="scorer-popular-btn" data-origin="SGSIN" data-dest="EGPSD">Singapore → Port Said</button> 2101 <button class="scorer-popular-btn" data-origin="CNSHA" data-dest="DEHAM">Shanghai → Hamburg</button> 2102 <button class="scorer-popular-btn" data-origin="USHOU" data-dest="AEJEA">Houston → Jebel Ali</button> 2103 </div> 2104 </div> 2105 <div class="hero-visual"> 2106 <div class="hero-gauge-wrap"> 2107 <svg id="heroGauge" width="240" height="240" viewBox="0 0 240 240"></svg> 2108 </div> 2109 <div class="hero-route-card"> 2110 <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;"> 2111 <div class="hero-route-label" style="margin-bottom:0;">Scored Route</div> 2112 <span class="live-badge" id="scoreLiveBadge" style="display:none;"> 2113 <span class="live-dot"></span>LIVE 2114 </span> 2115 <span class="demo-badge" id="scoreDemoBadge">DEMO</span> 2116 </div> 2117 <div class="hero-route-ports" id="heroRoutePorts">SGSIN <span style="color:var(--text-dim);">→</span> NLRTM</div> 2118 <div class="hero-route-meta" id="heroRouteMeta">8,441 nm · Risk Score: <span id="heroRiskLabel" style="color:#d97706;font-weight:600;">62 ELEVATED</span></div> 2119 <div id="heroUpdatedAt" style="font-family:var(--mono);font-size:10px;color:var(--text-dim);margin-top:4px;display:none;"></div> 2120 <div class="hero-route-waypoints" id="heroWaypoints"> 2121 <span class="hero-waypoint">Malacca</span>
2122 <span class="hero-waypoint-arrow">→</span> 2123 <span class="hero-waypoint">Indian Ocean</span> 2124 <span class="hero-waypoint-arrow">→</span> 2125 <span class="hero-waypoint">Bab el-Mandeb</span> 2126 <span class="hero-waypoint-arrow">→</span> 2127 <span class="hero-waypoint">Suez</span> 2128 <span class="hero-waypoint-arrow">→</span> 2129 <span class="hero-waypoint">Mediterranean</span> 2130 </div> 2131 </div> 2132 <!-- Cycling preview --> 2133 <div class="hero-preview" id="heroPreview"> 2134 <div class="hero-preview-item active"> 2135 <span class="hero-preview-icon"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span> 2136 <span class="hero-preview-text"><strong>Sanctions & Ownership</strong> — OFAC, UN, EU screening + ownership-chain exposure</span> 2137 </div> 2138 <div class="hero-preview-item"> 2139 <span class="hero-preview-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span> 2140 <span class="hero-preview-text"><strong>Fleet Tracking</strong> — 50K+ vessels, dark activity detection</span> 2141 </div> 2142 <div class="hero-preview-item"> 2143 <span class="hero-preview-icon"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></span> 2144 <span class="hero-preview-text"><strong>7 Report Types</strong> — AI-narrated PDF intelligence</span> 2145 </div> 2146 <div class="hero-preview-item"> 2147 <span class="hero-preview-icon"><svg viewBox="0 0 24 24"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></span> 2148 <span class="hero-preview-text"><strong>Monte Carlo</strong> — 500 stochastic simulations per route</span> 2149 </div> 2150 </div> 2151 </div> 2152 </div> 2153 2154 <!-- Results panel --> 2155 <div class="scorer-results" id="scorerResults"></div> 2156 </div> 2157 </section> 2158 2159 <!-- ============================================================ 2160 METRICS BAR 2161 ============================================================ --> 2162 <section class="proof"> 2163 <div class="container"> 2164 <div class="proof-label reveal">Maritime intelligence at scale</div> 2165 <div class="proof-grid"> 2166 <div class="proof-metric reveal reveal-delay-1"> 2167 <div class="proof-number" data-count="11" data-suffix="">0</div> 2168 <div class="proof-desc">Intelligence Signals</div> 2169 </div> 2170 <div class="proof-metric reveal reveal-delay-1"> 2171 <div class="proof-number" data-count="510" data-suffix="+">0</div> 2172 <div class="proof-desc">Global Ports</div> 2173 </div> 2174 <div class="proof-metric reveal reveal-delay-2"> 2175 <div class="proof-number" data-count="500" data-suffix="">0</div> 2176 <div class="proof-desc">MC Simulations</div> 2177 </div> 2178 <div class="proof-metric reveal reveal-delay-2"> 2179 <div class="proof-number" data-count="50" data-suffix="K+">0</div> 2180 <div class="proof-desc">Vessels Tracked</div> 2181 </div> 2182 <div class="proof-metric reveal reveal-delay-3"> 2183 <div class="proof-number" data-count="9" data-suffix="">0</div> 2184 <div class="proof-desc">Alert Types</div> 2185 </div> 2186 <div class="proof-metric reveal reveal-delay-3"> 2187 <div class="proof-number" data-count="6" data-suffix="">0</div> 2188 <div class="proof-desc">Report Types</div> 2189 </div> 2190 </div> 2191 <div class="reveal reveal-delay-3" style="text-align:center;margin-top:24px;color:var(--text-dim);font-size:12px;font-family:var(--mono);letter-spacing:0.02em;line-height:1.6;"> 2192 Live coverage updated every 5 minutes from OFAC SDN, EU FSF, UN Consolidated, GDELT, NGA, IMB, ReCAAP, GLEIF, Tokyo MoU, and the Paris MoU. 2193 </div> 2194 </div> 2195 </section> 2196 2197 <!-- ============================================================ 2198 COMPETITIVE WEDGE 2199 ============================================================ --> 2200 <section style="padding:56px 0;background:var(--bg);border-top:1px solid var(--accent);border-bottom:1px solid var(--border);"> 2201 <div class="container" style="max-width:820px;text-align:center;"> 2202 <div style="font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:14px;" class="reveal">Why ArcNautical</div> 2203 <h3 style="font-family:var(--display);font-size:clamp(1.4rem,2.2vw,1.9rem);color:var(--text);margin-bottom:14px;font-weight:400;" class="reveal">Published methodology. Defensible numbers.</h3> 2204 <p style="color:var(--text-sec);font-size:15px;line-height:1.7;" class="reveal reveal-delay-1">Most maritime intel platforms are closed black boxes. ArcNautical publishes every signal, every weight, every data source. If your team can’t defend the number to a regulator, it’s noise — not intelligence.</p> 2205 </div> 2206 </section> 2207 2208 <!-- ============================================================ 2209 INTELLIGENCE TICKER 2210 ============================================================ --> 2211 <section class="ticker-section"> 2212 <div class="ticker-header">
2213 <span class="ticker-dot" id="tickerDot"></span> 2214 <span class="ticker-title">Intelligence Feed</span> 2215 <span class="live-badge" id="feedLiveBadge" style="display:none;margin-left:8px;"> 2216 <span class="live-dot"></span>LIVE 2217 </span> 2218 <span id="feedUpdatedAt" style="font-family:var(--mono);font-size:10px;color:var(--text-dim);margin-left:12px;"></span> 2219 </div> 2220 <div class="ticker-track" id="tickerTrack"></div> 2221 </section> 2222 2223 <!-- ============================================================ 2224 HOW IT WORKS 2225 ============================================================ --> 2226 2227 <section class="hiw-section" id="how-it-works"> 2228 <div class="container"> 2229 <div class="hiw-inner"> 2230 <div class="section-label reveal" style="text-align:center;">How it works</div> 2231 <h2 class="section-title reveal" style="text-align:center;">Vessel vetting in minutes, not hours</h2> 2232 2233 <div class="hiw-steps reveal reveal-delay-1"> 2234 <div class="hiw-step"> 2235 <span class="hiw-step-connector">→</span> 2236 <div class="hiw-step-num">1</div> 2237 <div class="hiw-step-title">Enter a voyage</div> 2238 <div class="hiw-step-desc">IMO number, origin and destination ports, or paste a full itinerary</div> 2239 </div> 2240 <div class="hiw-step"> 2241 <span class="hiw-step-connector">→</span> 2242 <div class="hiw-step-num">2</div> 2243 <div class="hiw-step-title">Get a risk score</div> 2244 <div class="hiw-step-desc">10 live intelligence sources scored in under 2 seconds with full explainability</div> 2245 </div> 2246 <div class="hiw-step"> 2247 <span class="hiw-step-connector">→</span> 2248 <div class="hiw-step-num">3</div> 2249 <div class="hiw-step-title">Screen the entity</div> 2250 <div class="hiw-step-desc">Sanctions, ownership chain, flag performance, and PSC detention history</div> 2251 </div> 2252 <div class="hiw-step"> 2253 <div class="hiw-step-num">4</div> 2254 <div class="hiw-step-title">Export the report</div> 2255 <div class="hiw-step-desc">Defensible PDF for your compliance file, audit trail included</div> 2256 </div> 2257 </div> 2258 2259 <div class="hiw-built-for reveal reveal-delay-2"> 2260 <div class="section-label" style="text-align:center;margin-bottom:16px;">Built for</div> 2261 <div class="hiw-personas"> 2262 <div class="hiw-persona"> 2263 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span> 2264 <div> 2265 <div>Marine hull underwriters</div> 2266 <div class="hiw-persona-workflow">Replaces manual vessel vetting</div> 2267 </div> 2268 </div> 2269 <div class="hiw-persona"> 2270 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span> 2271 <div> 2272 <div>War risk brokers</div> 2273 <div class="hiw-persona-workflow">Replaces JWC + NAVAREA cross-referencing</div> 2274 </div> 2275 </div> 2276 <div class="hiw-persona"> 2277 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg></span> 2278 <div> 2279 <div>P&I club analysts</div> 2280 <div class="hiw-persona-workflow">Replaces multi-source loss analysis</div> 2281 </div> 2282 </div> 2283 <div class="hiw-persona"> 2284 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg></span> 2285 <div> 2286 <div>Fleet operations teams</div> 2287 <div class="hiw-persona-workflow">Replaces manual route risk assessment</div> 2288 </div> 2289 </div> 2290 <div class="hiw-persona">
2291 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span> 2292 <div> 2293 <div>Compliance & legal teams</div> 2294 <div class="hiw-persona-workflow">Audit trail for OFAC, EU, UNSC screening</div> 2295 </div> 2296 </div> 2297 <div class="hiw-persona"> 2298 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg></span> 2299 <div> 2300 <div>Cargo owners & charterers</div> 2301 <div class="hiw-persona-workflow">Vets vessels before loading decisions</div> 2302 </div> 2303 </div> 2304 <div class="hiw-persona"> 2305 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><path d="M12 2l3 7h7l-5.5 4.5L18 21l-6-4-6 4 1.5-7.5L2 9h7z"/></svg></span> 2306 <div> 2307 <div>Ship managers</div> 2308 <div class="hiw-persona-workflow">Port-call risk screening for day-to-day ops</div> 2309 </div> 2310 </div> 2311 <div class="hiw-persona"> 2312 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg></span> 2313 <div> 2314 <div>Ship finance & maritime lenders</div> 2315 <div class="hiw-persona-workflow">Counterparty risk before loan origination</div> 2316 </div> 2317 </div> 2318 <div class="hiw-persona"> 2319 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span> 2320 <div> 2321 <div>Port state control & authorities</div> 2322 <div class="hiw-persona-workflow">Pre-arrival sanctions & flag-performance check</div> 2323 </div> 2324 </div> 2325 <div class="hiw-persona"> 2326 <span class="hiw-persona-icon"><svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span> 2327 <div> 2328 <div>Commodity traders & brokers</div> 2329 <div class="hiw-persona-workflow">Voyage risk before chartering</div> 2330 </div> 2331 </div> 2332 </div> 2333 </div> 2334 </div> 2335 </div> 2336 </section> 2337 2338 <!-- ============================================================ 2339 DEVELOPER API â keyless check first, then the full record 2340 ============================================================ --> 2341 <section class="section api-section" id="api"> 2342 <div class="container"> 2343 <div class="api-grid"> 2344 <div class="reveal"> 2345 <div class="section-label">Developer API</div> 2346 <h2 class="section-title">Screen a vessel from your own system</h2> 2347 <p class="section-desc">Sanctions status, ownership opacity and a vetting grade for any IMO, as JSON — wired into an order pipeline, a chartering desk or a spreadsheet. Start with one unauthenticated GET. Mint a key when you want the full evidence record, batches, standing monitors and webhooks.</p> 2348 <ul class="api-features"> 2349 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></span>Keyless check: 100 requests an hour per IP, no signup</li> 2350 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></span>Self-serve test and live keys — no approval step, no waiting list</li> 2351 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></span>5,000 vessel screenings to start and 10,000 voyage assessments a month, included</li> 2352 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24">
2352<path d="M20 6L9 17l-5-5"/></svg></span>Batches of 50 hulls, standing vessel monitors, fleet change feeds</li> 2353 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></span>HMAC-SHA256 signed webhooks, 24-hour idempotent replay</li> 2354 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></span>Rate and quota headers on every response, OpenAPI 3.1 contract</li> 2355 <li class="api-feature"><span class="api-feature-check"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></span>Screening records retained ten years, every source dated</li> 2356 </ul> 2357 <div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:20px"> 2358 <a href="/arcnautical.html?utm_source=landing&utm_campaign=get_a_key&intent=api#/register" class="btn-primary" style="text-decoration:none;">Get an API key</a> 2359 <a href="/developers/" class="btn-ghost" style="text-decoration:none;">Read API documentation →</a> 2360 </div> 2361 <p id="api-access" style="margin-top:18px;color:var(--text-sec);font-size:13.5px;line-height:1.65">The keyless call returns the verdict. A key returns what the engine already computed underneath it: every sanctions match with its list, programme and confidence class, the five weighted vetting factors, and per-source freshness. Both run the same engine. Machine-readable: <a href="/api/v1/openapi.json" style="color:var(--accent)">OpenAPI</a> · <a href="/llms.txt" style="color:var(--accent)">llms.txt</a> · <a href="/developers/AGENTS.md" style="color:var(--accent)">AGENTS.md</a> · <a href="https://www.postman.com/arcnautical-6322764/arcnautical-s-workspace" target="_blank" rel="noopener" style="color:var(--accent)">Postman</a></p> 2362 </div> 2363 <div class="reveal reveal-delay-2"> 2364 <div class="api-tabs" id="apiTabs"> 2365 <button class="api-tab active" data-api="check">Check · no key</button> 2366 <button class="api-tab" data-api="screening">Screening</button> 2367 <button class="api-tab" data-api="batch">Batch</button> 2368 <button class="api-tab" data-api="monitor">Monitor</button> 2369 <button class="api-tab" data-api="voyage">Voyage Risk</button> 2370 </div> 2371 2372 <!-- Keyless check --> 2373 <div class="api-panel active" data-api-panel="check"> 2374 <div class="code-block"> 2375 <div class="code-header"><span class="code-method">GET</span> /api/v1/vessels/{imo}/check <span style="margin-left:auto;color:var(--text-dim)">no key</span></div> 2376 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">curl https://arcnautical.com/api/v1/vessels/9274446/check</pre></div> 2377 </div> 2378 <div class="code-block"> 2379 <div class="code-header"><span style="color:var(--risk-low);font-weight:600;">200</span> Response <span style="margin-left:auto">x-ratelimit-limit: 100</span></div> 2380 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2381 <span class="ck">"imo"</span>: <span class="cs">"9274446"</span>, 2382 <span class="ck">"sanctions"</span>: { 2383 <span class="ck">"status"</span>: <span class="cs">"RED"</span>, 2384 <span class="ck">"detail"</span>: <span class="cs">"4 confirmed matches on vessel identifier."</span>, 2385 <span class="ck">"coverageComplete"</span>: <span class="cv">true</span> 2386 }, 2387 <span class="ck">"ownership"</span>: { <span class="ck">"opacity"</span>: <span class="cs">"MEDIUM"</span>, <span class="ck">"score"</span>: <span class="cn">40</span> }, 2388 <span class="ck">"vetting"</span>: { <span class="ck">"grade"</span>: <span class="cs">"E"</span>, <span class="ck">"score"</span>: <span class="cn">90</span>, <span class="ck">"status"</span>: <span class="cs">"unacceptable"</span> }, 2389 <span class="ck">"assessed"</span>: <span class="cv">true</span>, 2390 <span class="ck">"tier"</span>: <span class="cs">"keyless"</span> 2391}</pre></div> 2392 </div> 2393 <a href="/api/v1/vessels/9274446/check" target="_blank" rel="noopener" style="font-family:var(--mono);font-size:11.5px;color:var(--accent);text-decoration:none;">Run this call in your browser →</a> 2394 </div> 2395 2396 <!-- Screening with a key --> 2397 <div class="api-panel" data-api-panel="screening"> 2398 <div class="code-block"> 2399 <div class="code-header"><span class="code-method">POST</span> /api/v1/screenings</div> 2400 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;"><span class="cb">Authorization: Bearer arc_live_... 2401Idempotency-Key: screening:9274446:2026-09-12</span> 2402{ <span class="ck">"imo"</span>: <span class="cs">"9274446"</span>, <span class="ck">"customer_reference"</span>: <span class="cs">"order-48213"</span> }</pre></div> 2403 </div> 2404 <div class="code-block"> 2405 <div class="code-header"><span style="color:var(--risk-low);font-weight:600;">201</span> Response</div> 2406 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2407 <span class="ck">"id"</span>: <span class="cs">"dfa1bde9-..."</span>, 2408 <span class="ck">"imo"</span>: <span class="cs">"9274446"</span>, 2409 <span class="ck">"vessel_name"</span>: <span class="cs">"HS STAR"</span>, 2410 <span class="ck">"sanctions"</span>: { 2411 <span class="ck">"status"</span>: <span class="cs">"RED"</span>, 2412 <span class="ck">"matches"</span>: [ 2413 { <span class="ck">"name"</span>: <span class="cs">"HS STAR"</span>, <span class="ck">"source"</span>: <span class="cs">"ofac_sdn"</span>,
2414 <span class="ck">"programs"</span>: [<span class="cs">"RUSSIA-EO14024"</span>], 2415 <span class="ck">"match_type"</span>: <span class="cs">"exact_imo"</span>, 2416 <span class="ck">"confidence_class"</span>: <span class="cs">"confirmed"</span> }, 2417 <span class="cb">...</span> 2418 ], 2419 <span class="ck">"coverage_complete"</span>: <span class="cv">true</span> 2420 }, 2421 <span class="ck">"ownership"</span>: { <span class="ck">"opacity"</span>: <span class="cs">"MEDIUM"</span>, <span class="ck">"score"</span>: <span class="cn">40</span> }, 2422 <span class="ck">"vetting"</span>: { <span class="ck">"grade"</span>: <span class="cs">"E"</span>, <span class="ck">"detention_count"</span>: <span class="cn">1</span>, <span class="ck">"factors"</span>: [ <span class="cb">...</span> ] }, 2423 <span class="ck">"sources"</span>: [ 2424 { <span class="ck">"source"</span>: <span class="cs">"ofac_sdn"</span>, <span class="ck">"status"</span>: <span class="cs">"ok"</span>, <span class="ck">"checked_at"</span>: <span class="cs">"..."</span> }, 2425 <span class="cb">...</span> 2426 ], 2427 <span class="ck">"customer_reference"</span>: <span class="cs">"order-48213"</span>, 2428 <span class="ck">"expires_at"</span>: <span class="cs">"..."</span> 2429}</pre></div> 2430 </div> 2431 </div> 2432 2433 <!-- Batch --> 2434 <div class="api-panel" data-api-panel="batch"> 2435 <div class="code-block"> 2436 <div class="code-header"><span class="code-method">POST</span> /api/v1/screening-batches</div> 2437 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2438 <span class="ck">"imos"</span>: [ 2439 <span class="cs">"9274446"</span>, 2440 { <span class="ck">"imo"</span>: <span class="cs">"9141285"</span>, <span class="ck">"customer_reference"</span>: <span class="cs">"order-48214"</span> }, 2441 <span class="cb">... up to 50</span> 2442 ] 2443}</pre></div> 2444 </div> 2445 <div class="code-block"> 2446 <div class="code-header"><span style="color:var(--risk-low);font-weight:600;">202</span> Response</div> 2447 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2448 <span class="ck">"id"</span>: <span class="cs">"batch-id"</span>, 2449 <span class="ck">"status"</span>: <span class="cs">"queued"</span>, 2450 <span class="ck">"submitted_count"</span>: <span class="cn">50</span>, 2451 <span class="ck">"accepted_count"</span>: <span class="cn">49</span>, 2452 <span class="ck">"duplicate_count"</span>: <span class="cn">1</span>, 2453 <span class="ck">"failed_count"</span>: <span class="cn">0</span> 2454} 2455<span class="cb">// poll GET /screening-batches/{id}/items, or subscribe to screening_batch.completed</span></pre></div> 2456 </div> 2457 </div> 2458 2459 <!-- Monitor --> 2460 <div class="api-panel" data-api-panel="monitor"> 2461 <div class="code-block"> 2462 <div class="code-header"><span class="code-method">POST</span> /api/v1/vessel-monitors</div> 2463 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ <span class="ck">"imo"</span>: <span class="cs">"9274446"</span>, <span class="ck">"customer_reference"</span>: <span class="cs">"supplier-book"</span> }</pre></div> 2464 </div> 2465 <div class="code-block"> 2466 <div class="code-header"><span style="color:var(--risk-low);font-weight:600;">201</span> Response · then a webhook only when the verdict moves</div> 2467 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2468 <span class="ck">"id"</span>: <span class="cs">"monitor-id"</span>, 2469 <span class="ck">"imo"</span>: <span class="cs">"9274446"</span>, 2470 <span class="ck">"status"</span>: <span class="cs">"active"</span>, 2471 <span class="ck">"next_run_at"</span>: <span class="cs">"..."</span> 2472} 2473<span class="cb">// webhook vessel_risk.changed: 2474// { event_type: "changed", sanctions_status, vetting_grade, change_body: [...] }</span></pre></div> 2475 </div> 2476 </div> 2477 2478 <!-- Voyage Risk --> 2479 <div class="api-panel" data-api-panel="voyage"> 2480 <div class="code-block"> 2481 <div class="code-header"><span class="code-method">POST</span> /api/v1/voyage-assessments</div> 2482 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2483 <span class="ck">"customer_reference"</span>: <span class="cs">"lane-001"</span>, 2484 <span class="ck">
2484"route"</span>: { 2485 <span class="ck">"origin"</span>: { <span class="ck">"locode"</span>: <span class="cs">"NLRTM"</span> }, 2486 <span class="ck">"destination"</span>: { <span class="ck">"locode"</span>: <span class="cs">"CNSHA"</span> }, 2487 <span class="ck">"vessel_type"</span>: <span class="cs">"container"</span> 2488 } 2489}</pre></div> 2490 </div> 2491 <div class="code-block"> 2492 <div class="code-header"><span style="color:var(--risk-low);font-weight:600;">201</span> Response</div> 2493 <div class="code-body"><pre style="margin:0;white-space:pre;tab-size:2;">{ 2494 <span class="ck">"id"</span>: <span class="cs">"76e0b22d-..."</span>, 2495 <span class="ck">"score"</span>: <span class="cn">55</span>, 2496 <span class="ck">"risk_level"</span>: <span class="cs">"elevated"</span>, 2497 <span class="ck">"route"</span>: { 2498 <span class="ck">"origin_locode"</span>: <span class="cs">"NLRTM"</span>, <span class="ck">"dest_locode"</span>: <span class="cs">"CNSHA"</span>, 2499 <span class="ck">"distance_nm"</span>: <span class="cn">10082.3</span> 2500 }, 2501 <span class="ck">"drivers"</span>: [ 2502 { <span class="ck">"source"</span>: <span class="cs">"cii"</span>, <span class="ck">"signal_score"</span>: <span class="cn">74</span>, <span class="ck">"weight_pct"</span>: <span class="cn">12</span>, 2503 <span class="ck">"contribution_band"</span>: <span class="cs">"medium"</span>, <span class="ck">"relative_rank"</span>: <span class="cn">1</span> } 2504 ] 2505}</pre></div> 2506 </div> 2507 </div> 2508 </div> 2509 </div> 2510 </div> 2511 </section> 2512 2513 <!-- ============================================================ 2514 "10 SIGNALS, ONE SCORE" 2515 ============================================================ --> 2516 <section class="section signals-section" id="signals"> 2517 <div class="container"> 2518 <div class="section-center"> 2519 <div class="section-label reveal">Core Intelligence</div> 2520 <h2 class="section-title reveal">11 signals, one score</h2> 2521 <p class="section-desc reveal">Every voyage risk score fuses ten independent intelligence signals through weighted, active-signal scoring. Zero-count signals are excluded automatically—only real threats drive the composite.</p> 2522 </div> 2523 <div class="signals-layout"> 2524 <div class="signals-svg-wrap reveal"> 2525 <svg id="signalsSvg" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg"> 2526 <!-- Background arc --> 2527 <path d="M 50,350 A 250,250 0 0,1 350,350" fill="none" stroke="#1c2a3e" stroke-width="1" opacity="0.5"/> 2528 <!-- Central gauge circle --> 2529 <circle cx="200" cy="350" r="44" fill="#0d1420" stroke="#1c2a3e" stroke-width="1.5"/> 2530 <text x="200" y="345" text-anchor="middle" dominant-baseline="central" fill="#d0d8e8" font-family="'JetBrains Mono',monospace" font-size="20" font-weight="600" id="signalsCenterScore">0</text> 2531 <text x="200" y="368" text-anchor="middle" fill="#0891b2" font-family="'JetBrains Mono',monospace" font-size="8" letter-spacing="0.1em">COMPOSITE</text> 2532 <!-- Signal nodes + connecting lines (generated by JS) --> 2533 <g id="signalLines"></g> 2534 <g id="signalNodes"></g> 2535 </svg> 2536 </div> 2537 <div class="reveal reveal-delay-2"> 2538 <ul class="signal-list"> 2539 <li class="signal-item"><span class="signal-dot" style="background:var(--risk-crit)"></span>GDELT Conflict<span class="signal-num">01</span></li> 2540 <li class="signal-item"><span class="signal-dot" style="background:var(--risk-high)"></span>Piracy Intel<span class="signal-num">02</span></li> 2541 <li class="signal-item"><span class="signal-dot" style="background:var(--risk-elev)"></span>NGA NAVWARNs<span class="signal-num">03</span></li> 2542 <li class="signal-item"><span class="signal-dot" style="background:var(--accent)"></span>JWC Listed Areas<span class="signal-num">04</span></li> 2543 <li class="signal-item"><span class="signal-dot" style="background:#a78bfa"></span>AIS Disruption<span class="signal-num">05</span></li> 2544 <li class="signal-item"><span class="signal-dot" style="background:#6366f1"></span>Country Instability<span class="signal-num">06</span></li> 2545 <li class="signal-item"><span class="signal-dot" style="background:#38bdf8"></span>Weather &
2545 Sea State<span class="signal-num">07</span></li> 2546 <li class="signal-item"><span class="signal-dot" style="background:#f472b6"></span>Sanctions Exposure<span class="signal-num">08</span></li> 2547 <li class="signal-item"><span class="signal-dot" style="background:#fb923c"></span>Dark-Fleet Activity<span class="signal-num">09</span></li> 2548 <li class="signal-item"><span class="signal-dot" style="background:#2dd4bf"></span>Chokepoint Disruption<span class="signal-num">10</span></li> 2549 <li class="signal-item"><span class="signal-dot" style="background:#fbbf24"></span>Natural Disasters<span class="signal-num">11</span></li> 2550 </ul> 2551 </div> 2552 </div> 2553 </div> 2554 </section> 2555 2556 <!-- ============================================================ 2557 DATA SOURCES BANNER 2558 ============================================================ --> 2559 <section class="sources-banner" aria-label="Intelligence data sources"> 2560 <div class="container"> 2561 <div class="sources-label reveal">Intelligence sourced from 17+ open & commercial data providers</div> 2562 <div class="sources-categories"> 2563 <div class="sources-cat-row reveal reveal-delay-1"> 2564 <span class="sources-cat-label">Security</span> 2565 <div class="source-badge"><span class="source-badge-icon" style="background:var(--risk-crit)"></span><span class="source-badge-name">GDELT</span><span class="source-badge-org">Conflict Events</span></div> 2566 <div class="source-badge"><span class="source-badge-icon" style="background:var(--risk-high)"></span><span class="source-badge-name">ICC-IMB</span><span class="source-badge-org">Live Piracy</span></div> 2567 <div class="source-badge"><span class="source-badge-icon" style="background:var(--risk-high)"></span><span class="source-badge-name">ReCAAP</span><span class="source-badge-org">Asia-Pacific</span></div> 2568 <div class="source-badge"><span class="source-badge-icon" style="background:var(--accent)"></span><span class="source-badge-name">JWC</span><span class="source-badge-org">Listed Areas</span></div> 2569 <div class="source-badge"><span class="source-badge-icon" style="background:var(--risk-elev)"></span><span class="source-badge-name">NGA</span><span class="source-badge-org">Broadcast Warnings</span></div> 2570 </div> 2571 <div class="sources-cat-row reveal reveal-delay-2"> 2572 <span class="sources-cat-label">Compliance</span> 2573 <div class="source-badge"><span class="source-badge-icon" style="background:#f472b6"></span><span class="source-badge-name">OFAC SDN</span><span class="source-badge-org">US Sanctions</span></div> 2574 <div class="source-badge"><span class="source-badge-icon" style="background:#f472b6"></span><span class="source-badge-name">UN SCRL</span><span class="source-badge-org">Int'l Sanctions</span></div> 2575 <div class="source-badge"><span class="source-badge-icon" style="background:#f472b6"></span><span class="source-badge-name">OpenSanctions</span><span class="source-badge-org">Maritime Dataset</span></div> 2576 <div class="source-badge"><span class="source-badge-icon" style="background:var(--risk-low)"></span><span class="source-badge-name">GLEIF</span><span class="source-badge-org">Corporate LEI</span></div> 2577 </div> 2578 <div class="sources-cat-row reveal reveal-delay-3"> 2579 <span class="sources-cat-label">Operations</span> 2580 <div class="source-badge"><span class="source-badge-icon" style="background:#38bdf8"></span><span class="source-badge-name">Open-Meteo</span><span class="source-badge-org">Marine Weather</span></div> 2581 <div class="source-badge"><span class="source-badge-icon" style="background:var(--risk-high)"></span><span class="source-badge-name">GDACS</span><span class="source-badge-org">Disasters</span></div> 2582 <div class="source-badge"><span class="source-badge-icon" style="background:#fbbf24"></span><span class="source-badge-name">NHC</span><span class="source-badge-org">Cyclones</span></div> 2583 <div class="source-badge"><span class="source-badge-icon" style="background:#fb923c"></span><span class="source-badge-name">Tokyo MOU</span><span class="source-badge-org">PSC Detentions</span></div> 2584 <div class="source-badge"><span class="source-badge-icon" style="background:#fb923c"></span><span class="source-badge-name">Paris MoU</span><span class="source-badge-org">THETIS PSC Inspections</span></div> 2585 <div class="source-badge"><span class="source-badge-icon" style="background:#fb923c"></span><span class="source-badge-name">Regional MoUs</span><span class="source-badge-org">6 more PSC regimes</span></div> 2586 </div> 2587 <div class="sources-cat-row reveal reveal-delay-4">
2588 <span class="sources-cat-label">Logistics</span> 2589 <div class="source-badge"><span class="source-badge-icon" style="background:#22d3ee"></span><span class="source-badge-name">IMF PortWatch</span><span class="source-badge-org">Port Activity & Disruptions</span></div> 2590 <div class="source-badge"><span class="source-badge-icon" style="background:#34d399"></span><span class="source-badge-name">Terminal49</span><span class="source-badge-org">Container Tracking</span></div> 2591 <div class="source-badge"><span class="source-badge-icon" style="background:#a78bfa"></span><span class="source-badge-name">FRED</span><span class="source-badge-org">Freight Market Index</span></div> 2592 </div> 2593 </div> 2594 </div> 2595 </section> 2596 2597 <!-- ============================================================ 2598 TABBED PRODUCT SHOWCASE 2599 ============================================================ --> 2600 <section class="section showcase-section" id="products"> 2601 <div class="container"> 2602 <div class="section-center"> 2603 <div class="section-label reveal">Platform</div> 2604 <h2 class="section-title reveal">Everything a maritime underwriter needs</h2> 2605 </div> 2606 <div class="showcase-tabs reveal" id="showcaseTabs"> 2607 <button class="showcase-tab active" data-tab="voyage">Voyage Risk Scoring</button> 2608 <button class="showcase-tab" data-tab="stochastic">Stochastic Monte Carlo</button> 2609 <button class="showcase-tab" data-tab="fleet">Fleet & Dark Activity</button> 2610 <button class="showcase-tab" data-tab="tracking">Container Tracking</button> 2611 <button class="showcase-tab" data-tab="underwriting">Underwriting & PML</button> 2612 <button class="showcase-tab" data-tab="reports">Reports & Compliance</button> 2613 </div> 2614 <div class="showcase-panels"> 2615 <!-- VOYAGE RISK TAB --> 2616 <div class="showcase-panel active" data-panel="voyage"> 2617 <div> 2618 <h3 class="section-title" style="font-size:clamp(1.4rem,2.5vw,1.8rem);">VoyageRiskScore</h3> 2619 <p class="section-desc">Quantified port-to-port risk assessment fusing ten independent intelligence sources into a single 0–100 score. Full driver breakdown shows exactly what's driving the risk.</p> 2620 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;"> 2621 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-crit)"></span>11 weighted signal sources with active-signal redistribution</li> 2622 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>CII safety floors prevent under-scoring war zones</li> 2623 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-low)"></span>Data confidence indicator with missing source warnings</li> 2624 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>Sub-2s scoring across 510+ port pairs</li> 2625 </ul> 2626 </div> 2627 <div> 2628 <div class="mockup-card"> 2629 <div class="mockup-titlebar"> 2630 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 2631 <span class="mockup-titlebar-text">arcnautical.com/voyage-scorer</span> 2632 </div> 2633 <div class="mockup-body"> 2634 <div style="display:flex;align-items:center;gap:20px;margin-bottom:16px;"> 2635 <svg id="mockGauge" width="100" height="100" viewBox="0 0 100 100"></svg> 2636 <div> 2637 <div style="font-family:var(--mono);font-size:14px;font-weight:600;color:var(--text);margin-bottom:4px;">Singapore → Rotterdam</div> 2638 <div style="font-family:var(--mono);font-size:11px;color:var(--text-dim);line-height:1.6;">SGSIN → NLRTM<br>Distance: 8,441 nm</div> 2639 </div> 2640 </div> 2641 <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:6px;">Signal Contribution</div> 2642 <div class="breakdown-bar"> 2643 <div class="breakdown-segment" style="flex:10;background:var(--risk-crit);"></div> 2644 <div class="breakdown-segment" style="flex:18;background:var(--risk-high);"></div> 2645 <div class="breakdown-segment" style="flex:12;background:var(--risk-elev);"></div> 2646 <div class="breakdown-segment" style="flex:15;background:var(--accent);"></div> 2647 <div class="breakdown-segment" style="flex:8;background:#a78bfa;"></div> 2648 <div class="breakdown-segment" style="flex:12;background:#6366f1;"></div> 2649 <div class="breakdown-segment" style="flex:8;background:#38bdf8;"></div> 2650 <div class="breakdown-segment" style="flex:7;background:#f472b6;"></div> 2651 <div class="breakdown-segment" style="flex:5;background:#fb923c;"></div> 2652 <div class="breakdown-segment" style="flex:5;background:#fbbf24;"></div> 2653 </div> 2654 <div class="breakdown-legend"> 2655 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:var(--risk-crit)"></span>Conflict</div> 2656 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:var(--risk-high)"></span>Piracy</div> 2657 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:var(--risk-elev)"></span>NAVWARN</div> 2658 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:var(--accent)"></span>JWC</div> 2659 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:#a78bfa"></span>AIS</div> 2660 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:#6366f1"></span>CII</div> 2661 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:#38bdf8"></span>Weather</div> 2662 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:#f472b6"></span>Sanctions</div> 2663 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:#fb923c"></span>PSC</div> 2664 <div class="breakdown-legend-item"><span class="breakdown-legend-dot" style="background:#fbbf24"></span>Disaster</div> 2665 </div> 2666 </div> 2667 </div> 2668 </div> 2669 </div> 2670 2671 <!-- STOCHASTIC TAB --> 2672 <div class="showcase-panel" data-panel="stochastic"> 2673 <div> 2674 <h3 class="section-title" style="font-size:clamp(1.4rem,2.5vw,1.8rem);">Stochastic Monte Carlo</h3> 2675 <p class="section-desc">500 weather-coupled simulations per voyage. AR(1) correlated weather sequences produce realistic ETA distributions, not just point estimates.</p> 2676 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;"> 2677 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>Beaufort speed curves per vessel type & load condition</li> 2678 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:#38bdf8"></span>Per-segment weather from Open-Meteo marine API</li> 2679 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>P10–P90 ETA distribution with median highlight</li> 2680 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-low)"></span>Container, bulk, tanker, LNG, general cargo models</li> 2681 </ul> 2682 </div> 2683 <div> 2684 <div class="mockup-card"> 2685 <div class="mockup-titlebar">
2686 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 2687 <span class="mockup-titlebar-text">Stochastic Voyage Analysis</span> 2688 </div> 2689 <div class="mockup-body"> 2690 <div style="font-family:var(--mono);font-size:11px;color:var(--text-dim);margin-bottom:6px;">CONTAINER · LADEN · 18 KT DESIGN SPEED</div> 2691 <div style="display:flex;gap:24px;margin-bottom:16px;"> 2692 <div> 2693 <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;">Risk (P50)</div> 2694 <div style="font-family:var(--mono);font-size:28px;font-weight:700;color:var(--risk-elev);">42</div> 2695 </div> 2696 <div> 2697 <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;">ETA (Median)</div> 2698 <div style="font-family:var(--mono);font-size:28px;font-weight:700;color:var(--text);">18.2d</div> 2699 </div> 2700 <div> 2701 <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;">Simulations</div> 2702 <div style="font-family:var(--mono);font-size:28px;font-weight:700;color:var(--accent);">500</div> 2703 </div> 2704 </div> 2705 <div class="eta-bar-wrap"> 2706 <div class="eta-bar-label">ETA Distribution (P10 – P90)</div> 2707 <div class="eta-bar"> 2708 <div class="eta-bar-range" style="left:15%;right:22%;"></div> 2709 <div class="eta-bar-median" style="left:42%;"></div> 2710 </div> 2711 <div class="eta-bar-labels"> 2712 <span>16.1d (P10)</span> 2713 <span>18.2d (P50)</span> 2714 <span>21.4d (P90)</span> 2715 </div> 2716 </div> 2717 </div> 2718 </div> 2719 </div> 2720 </div> 2721 2722 <!-- FLEET TAB --> 2723 <div class="showcase-panel" data-panel="fleet"> 2724 <div> 2725 <h3 class="section-title" style="font-size:clamp(1.4rem,2.5vw,1.8rem);">Fleet & Dark Activity</h3> 2726 <p class="section-desc">Real-time vessel tracking with automatic dark activity detection. AIS gaps, speed anomalies, and unauthorized zone entries trigger instant alerts.</p> 2727 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;"> 2728 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-crit)"></span>AIS gap detection with configurable thresholds</li> 2729 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>Dead reckoning position estimation during gaps</li> 2730 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:#a78bfa"></span>Geofence zones with entry/exit monitoring</li> 2731 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>Fleet-wide risk dashboard with KPI metrics</li> 2732 </ul> 2733 </div> 2734 <div> 2735 <div class="mockup-card"> 2736 <div class="mockup-titlebar"> 2737 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 2738 <span class="mockup-titlebar-text">arcnautical.com/tracking</span> 2739 </div> 2740 <div class="mockup-body" style="padding:0;"> 2741 <div class="alert-feed"> 2742 <div class="alert-item" data-severity="critical"> 2743 <span class="alert-badge" data-severity="critical">CRIT</span> 2744 <div class="alert-content"> 2745 <div class="alert-type">AIS Gap — Dark Activity</div> 2746 <div class="alert-msg"><span class="alert-vessel">MV DEMO ALPHA</span> — 6.2h signal loss — Bab el-Mandeb</div> 2747 </div> 2748 </div> 2749 <div class="alert-item" data-severity="high">
2750 <span class="alert-badge" data-severity="high">HIGH</span> 2751 <div class="alert-content"> 2752 <div class="alert-type">Zone Entry — JWC Listed Area</div> 2753 <div class="alert-msg"><span class="alert-vessel">MV DEMO BETA</span> — Yemen / Red Sea corridor</div> 2754 </div> 2755 </div> 2756 <div class="alert-item" data-severity="medium"> 2757 <span class="alert-badge" data-severity="medium">MED</span> 2758 <div class="alert-content"> 2759 <div class="alert-type">Speed Anomaly</div> 2760 <div class="alert-msg"><span class="alert-vessel">MV DEMO DELTA</span> — 2.1 kt (avg 14 kt) — Persian Gulf</div> 2761 </div> 2762 </div> 2763 </div> 2764 </div> 2765 </div> 2766 </div> 2767 </div> 2768 2769 <!-- CONTAINER TRACKING TAB --> 2770 <div class="showcase-panel" data-panel="tracking"> 2771 <div> 2772 <h3 class="section-title" style="font-size:clamp(1.4rem,2.5vw,1.8rem);">Container Tracking</h3> 2773 <p class="section-desc">Track containers across 21 major carriers by BOL, container number, or booking reference. Every tracked shipment is cross-referenced against live threat intelligence—so you don't just see where your cargo is, you see what risks surround it.</p> 2774 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;"> 2775 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:#34d399"></span>Track by BOL, container number, or booking reference</li> 2776 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>21 carriers: Maersk, MSC, CMA CGM, Hapag-Lloyd, ONE, and more</li> 2777 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>Full milestone timeline: loaded, departed, transshipped, arrived, delivered</li> 2778 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-crit)"></span>Cross-referenced with voyage risk scores and threat intelligence</li> 2779 </ul> 2780 </div> 2781 <div> 2782 <div class="mockup-card"> 2783 <div class="mockup-titlebar"> 2784 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 2785 <span class="mockup-titlebar-text">arcnautical.com/tracking</span> 2786 </div> 2787 <div class="mockup-body" style="padding:0;"> 2788 <div class="alert-feed"> 2789 <div class="alert-item" data-severity="low"> 2790 <span class="alert-badge" data-severity="low" style="font-size:9px;">SHIP</span> 2791 <div class="alert-content"> 2792 <div class="alert-type" style="font-family:var(--mono);">MSKU7284930</div> 2793 <div class="alert-msg"><span class="alert-vessel">Maersk (MAEU)</span> — Shanghai → Rotterdam — <span style="color:var(--accent);">ON SHIP</span> — ETA Apr 12</div> 2794 </div> 2795 </div> 2796 <div class="alert-item" data-severity="critical"> 2797 <span class="alert-badge" data-severity="critical" style="font-size:9px;">RISK</span> 2798 <div class="alert-content"> 2799 <div class="alert-type" style="font-family:var(--mono);">HLCU2091847</div> 2800 <div class="alert-msg"><span class="alert-vessel">Hapag-Lloyd</span> — Jebel Ali → Felixstowe — <span style="color:var(--risk-crit);">RED SEA TRANSIT</span> — Risk: 74</div> 2801 </div> 2802 </div> 2803 <div class="alert-item" data-severity="low">
2804 <span class="alert-badge" data-severity="low" style="font-size:9px;">ARR</span> 2805 <div class="alert-content"> 2806 <div class="alert-type" style="font-family:var(--mono);">CMDU5038271</div> 2807 <div class="alert-msg"><span class="alert-vessel">CMA CGM</span> — Singapore → Los Angeles — <span style="color:var(--risk-low);">DELIVERED</span></div> 2808 </div> 2809 </div> 2810 </div> 2811 </div> 2812 </div> 2813 </div> 2814 </div> 2815 2816 <!-- UNDERWRITING TAB --> 2817 <div class="showcase-panel" data-panel="underwriting"> 2818 <div> 2819 <h3 class="section-title" style="font-size:clamp(1.4rem,2.5vw,1.8rem);">Underwriting & PML</h3> 2820 <p class="section-desc">Probable Maximum Loss engine with zone-based exposure analysis, EP curves, and stochastic frequency/severity modelling for portfolio-level risk.</p> 2821 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;"> 2822 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-crit)"></span>EP curve generation with zone-specific loss distributions</li> 2823 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>Vessel vetting: detention, flag, age, ownership scoring</li> 2824 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:#f472b6"></span>OFAC/UN/EU sanctions screening on all entity chains</li> 2825 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>Fleet-wide PML exposure snapshots on cron</li> 2826 </ul> 2827 </div> 2828 <div> 2829 <div class="mockup-card"> 2830 <div class="mockup-titlebar"> 2831 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 2832 <span class="mockup-titlebar-text">arcnautical.com/underwriting</span> 2833 </div> 2834 <div class="mockup-body"> 2835 <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:12px;">Exceedance Probability Curve</div> 2836 <div class="ep-curve-wrap" style="margin:0;"> 2837 <svg class="ep-curve-svg" viewBox="0 0 300 120" preserveAspectRatio="none" id="epCurveSvg"> 2838 <line x1="30" y1="10" x2="30" y2="110" stroke="#1c2a3e" stroke-width="0.5"/> 2839 <line x1="30" y1="110" x2="290" y2="110" stroke="#1c2a3e" stroke-width="0.5"/> 2840 <text x="15" y="20" fill="#445068" font-size="7" font-family="'JetBrains Mono',monospace" text-anchor="middle">1.0</text> 2841 <text x="15" y="60" fill="#445068" font-size="7" font-family="'JetBrains Mono',monospace" text-anchor="middle">0.5</text> 2842 <text x="15" y="108" fill="#445068" font-size="7" font-family="'JetBrains Mono',monospace" text-anchor="middle">0.0</text> 2843 <text x="160" y="120" fill="#445068" font-size="7" font-family="'JetBrains Mono',monospace" text-anchor="middle">Loss Amount ($M)</text> 2844 <path class="ep-curve-path" id="epCurvePath" d="M 30,15 C 60,16 80,20 110,35 C 140,50 170,70 200,85 C 230,95 260,103 290,107" fill="none" stroke="#0891b2" stroke-width="2"/> 2845 <path d="M 30,15 C 60,16 80,20 110,35 C 140,50 170,70 200,85 C 230,95 260,103 290,107 L 290,110 L 30,110 Z" fill="rgba(8,145,178,0.08)"/> 2846 </svg> 2847 </div> 2848 </div> 2849 </div> 2850 </div> 2851 </div> 2852 2853 <!-- REPORTS TAB --> 2854 <div class="showcase-panel" data-panel="reports"> 2855 <div> 2856 <h3 class="section-title" style="font-size:clamp(1.4rem,2.5vw,1.8rem);">Reports & Compliance</h3> 2857 <p class="section-desc">Seven automated PDF report types with AI-generated intelligence narratives. Scheduled delivery or on-demand generation via API.</p> 2858 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;"> 2859 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>Voyage Risk Report — Full route intelligence briefing</li> 2860 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-low)"></span>Fleet Summary — Portfolio-wide risk overview</li> 2861 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-crit)"></span>Piracy Briefing — Regional threat assessment</li> 2862 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:#f472b6"></span>Compliance Report — Sanctions + PSC screening</li> 2863 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>PML Report, Route Comparison, Vessel Dossier</li> 2864 </ul> 2865 </div> 2866 <div> 2867 <div class="mockup-card"> 2868 <div class="mockup-titlebar">
2869 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 2870 <span class="mockup-titlebar-text">Generated Reports</span> 2871 </div> 2872 <div class="mockup-body"> 2873 <div class="pdf-stack"> 2874 <div class="pdf-page"> 2875 <div class="pdf-page-title">Piracy Briefing</div> 2876 <div class="pdf-page-line"></div> 2877 <div class="pdf-page-line short"></div> 2878 <div class="pdf-page-line"></div> 2879 <div class="pdf-page-line short"></div> 2880 <div class="pdf-page-line"></div> 2881 <div style="height:20px;margin:8px 0;background:rgba(8,145,178,0.08);border-radius:2px;"></div> 2882 <div class="pdf-page-line"></div> 2883 <div class="pdf-page-line short"></div> 2884 </div> 2885 <div class="pdf-page"> 2886 <div class="pdf-page-title">Fleet Summary</div> 2887 <div class="pdf-page-line"></div> 2888 <div class="pdf-page-line short"></div> 2889 <div class="pdf-page-line"></div> 2890 <div style="height:30px;margin:8px 0;background:rgba(5,150,105,0.08);border-radius:2px;"></div> 2891 <div class="pdf-page-line"></div> 2892 <div class="pdf-page-line short"></div> 2893 </div> 2894 <div class="pdf-page"> 2895 <div class="pdf-page-title">Voyage Risk</div> 2896 <div class="pdf-page-line"></div> 2897 <div class="pdf-page-line"></div> 2898 <div class="pdf-page-line short"></div> 2899 <div style="height:24px;margin:8px 0;background:rgba(217,119,6,0.08);border-radius:2px;"></div> 2900 <div class="pdf-page-line"></div> 2901 <div class="pdf-page-line short"></div> 2902 </div> 2903 </div> 2904 <div style="font-family:var(--mono);font-size:11px;color:var(--text-sec);margin-top:8px;">AI-narrated intelligence · Scheduled delivery · API generation</div> 2905 </div> 2906 </div> 2907 </div> 2908 </div> 2909 </div> 2910 </div> 2911 </section> 2912 2913 <div class="section-divider"></div> 2914 2915 <!-- ============================================================ 2916 OWNERSHIP & SANCTIONS 2917 ============================================================ --> 2918 <section class="section ownership-section" id="ownership"> 2919 <div class="container"> 2920 <div class="ownership-layout"> 2921 <div class="reveal"> 2922 <div class="section-label">Compliance Intelligence</div> 2923 <h3 class="section-title"><a href="/sanctions-screening.html" style="color:inherit;text-decoration:none;">Ownership chain analysis & sanctions screening</a></h3> 2924 <p class="section-desc">Trace beneficial ownership through GLEIF corporate registries. Screen every entity in the chain against OFAC, UN, and OpenSanctions. Detect shell company jurisdictions automatically.</p> 2925 <div style="font-family:var(--mono);font-size:11px;color:var(--text-dim);margin:8px 0 16px;letter-spacing:0.02em;">Sample scan — Pacific Shipping Ltd (Marshall Islands registration, Panama parent, OFAC-listed UBO)</div> 2926 <div class="ownership-metrics"> 2927 <div class="ownership-metric-card"> 2928 <div class="ownership-metric-label">Opacity Score</div> 2929 <div class="ownership-metric-value" style="color:var(--risk-high);">72</div> 2930 </div> 2931 <div class="ownership-metric-card"> 2932 <div class="ownership-metric-label">Sanctions Hits</div> 2933 <div class="ownership-metric-value" style="color:var(--risk-crit);">1</div> 2934 </div> 2935 <div class="ownership-metric-card"> 2936 <div class="ownership-metric-label">Shell Jurisdictions</div> 2937 <div class="ownership-metric-value" style="color:var(--risk-elev);">2</div> 2938 </div> 2939 <div class="ownership-metric-card"> 2940 <div class="ownership-metric-label">Chain Depth</div> 2941 <div class="ownership-metric-value" style="color:var(--accent);">3</div> 2942 </div> 2943 </div> 2944 </div> 2945 <div class="reveal reveal-delay-2"> 2946 <div class="ownership-chain" id="ownershipChain"> 2947 <div class="ownership-entity" data-delay="0"> 2948 <div class="ownership-entity-type">Registered Owner</div> 2949 <div class="ownership-entity-name">Pacific Shipping Ltd</div> 2950 <div class="ownership-entity-detail">Marshall Islands · No GLEIF record</div> 2951 </div> 2952 <div class="ownership-connector" data-delay="300"></div> 2953 <div class="ownership-entity" data-delay="400"> 2954 <div class="ownership-entity-type">Parent Company</div> 2955 <div class="ownership-entity-name">Oceanic Holdings SA</div> 2956 <div class="ownership-entity-detail">Panama · LEI: 5493001KJTIIGC8Y1R12</div> 2957 </div> 2958 <div class="ownership-connector" data-delay="700"></div> 2959 <div class="ownership-entity flagged" data-delay="800"> 2960 <div class="ownership-entity-type">Ultimate Beneficial Owner</div> 2961 <div class="ownership-entity-name">Meridian Group Corp</div> 2962 <div class="ownership-entity-detail">OFAC SDN MATCH — Sanctioned entity</div> 2963 </div> 2964 </div> 2965 </div> 2966 </div> 2967 </div> 2968 </section> 2969 2970 <div class="section-divider"></div> 2971 2972 <!-- ============================================================ 2973 FUEL & CII 2974 ============================================================ --> 2975 <section class="section fuel-section" id="fuel"> 2976 <div class="container"> 2977 <div class="fuel-layout"> 2978 <div class="reveal"> 2979 <div class="section-label">IMO Compliance</div> 2980 <h3 class="section-title">Fuel estimation & CII rating</h3> 2981 <p class="section-desc">IMO MEPC-compliant CII calculator with Admiralty cubic law fuel consumption, CO2 emission factors, and A–E rating bands that tighten annually.</p> 2982 <div class="cii-scale" id="ciiScale"> 2983 <div class="cii-bar-wrap"> 2984 <div class="cii-bar"> 2985 <div class="cii-bar-seg a">A</div> 2986 <div class="cii-bar-seg b">B</div> 2987 <div class="cii-bar-seg c">C</div> 2988 <div class="cii-bar-seg d">D</div> 2989 <div class="cii-bar-seg e">E</div> 2990 </div> 2991 <div class="cii-pointer" id="ciiPointer"></div> 2992 </div> 2993 <div class="cii-labels">
2994 <span>Superior</span><span>Lower</span><span>Moderate</span><span>Upper</span><span>Inferior</span> 2995 </div> 2996 </div> 2997 <div class="fuel-metrics"> 2998 <div class="fuel-metric"> 2999 <div class="fuel-metric-label">Fuel Consumption</div> 3000 <div class="fuel-metric-value">842</div> 3001 <div class="fuel-metric-unit">metric tonnes</div> 3002 </div> 3003 <div class="fuel-metric"> 3004 <div class="fuel-metric-label">CO2 Emissions</div> 3005 <div class="fuel-metric-value">2,655</div> 3006 <div class="fuel-metric-unit">tonnes CO2</div> 3007 </div> 3008 <div class="fuel-metric"> 3009 <div class="fuel-metric-label">Bunker Cost</div> 3010 <div class="fuel-metric-value">$521K</div> 3011 <div class="fuel-metric-unit">at $619/MT VLSFO</div> 3012 </div> 3013 <div class="fuel-metric"> 3014 <div class="fuel-metric-label">CII Attained</div> 3015 <div class="fuel-metric-value" style="color:var(--risk-low);">4.82</div> 3016 <div class="fuel-metric-unit">gCO2/DWT·nm</div> 3017 </div> 3018 </div> 3019 </div> 3020 <div class="reveal reveal-delay-2"> 3021 <div class="mockup-card"> 3022 <div class="mockup-titlebar"> 3023 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 3024 <span class="mockup-titlebar-text">Route Comparison — Fuel & Emissions</span> 3025 </div> 3026 <div class="mockup-body"> 3027 <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:12px;">VLSFO · 65,000 DWT · Bulk Carrier</div> 3028 <table style="width:100%;border-collapse:collapse;font-family:var(--mono);font-size:11px;"> 3029 <thead> 3030 <tr style="border-bottom:1px solid var(--border);"> 3031 <th style="text-align:left;padding:8px 6px;color:var(--text-dim);font-weight:500;">Route</th> 3032 <th style="text-align:right;padding:8px 6px;color:var(--text-dim);font-weight:500;">Risk</th> 3033 <th style="text-align:right;padding:8px 6px;color:var(--text-dim);font-weight:500;">Fuel</th> 3034 <th style="text-align:right;padding:8px 6px;color:var(--text-dim);font-weight:500;">CO2</th> 3035 <th style="text-align:right;padding:8px 6px;color:var(--text-dim);font-weight:500;">CII</th> 3036 </tr> 3037 </thead> 3038 <tbody> 3039 <tr style="border-bottom:1px solid var(--border);"> 3040 <td style="padding:10px 6px;color:var(--text);">Via Suez</td> 3041 <td style="text-align:right;padding:10px 6px;color:var(--risk-elev);font-weight:600;">42</td> 3042 <td style="text-align:right;padding:10px 6px;color:var(--text-sec);">842 MT</td> 3043 <td style="text-align:right;padding:10px 6px;color:var(--text-sec);">2,655 MT</td> 3044 <td style="text-align:right;padding:10px 6px;"><span style="background:rgba(5,150,105,0.15);color:var(--risk-low);padding:2px 6px;border-radius:2px;font-weight:600;">B</span></td> 3045 </tr> 3046 <tr> 3047 <td style="padding:10px 6px;color:var(--text);">Via Cape</td> 3048 <td style="text-align:right;padding:10px 6px;color:var(--risk-low);font-weight:600;">18</td> 3049 <td style="text-align:right;padding:10px 6px;color:var(--text-sec);">1,284 MT</td> 3050 <td style="text-align:right;padding:10px 6px;color:var(--text-sec);">4,049 MT</td> 3051 <td style="text-align:right;padding:10px 6px;"><span style="background:rgba(251,191,36,0.15);color:var(--risk-elev);padding:2px 6px;border-radius:2px;font-weight:600;">C</span></td> 3052 </tr> 3053 </tbody> 3054 </table> 3055 </div> 3056 </div> 3057 </div> 3058 </div> 3059 </div> 3060 </section> 3061 3062 <!-- ============================================================ 3063 OCEAN ROUTING ANIMATION 3064 ============================================================ --> 3065 <section class="section routing-section" id="routing"> 3066 <div class="container"> 3067 <div class="routing-layout"> 3068 <div class="reveal"> 3069 <div class="section-label">Navigation</div> 3070 <h3 class="section-title">Ocean pathfinding engine</h3> 3071 <p class="section-desc">A* graph search through a 7,200×3,600 ocean bitmap at 5.5km resolution. Forced passages through Suez, Panama, Bosphorus, and 12 other strategic straits. 510+ ports with UN/LOCODE.</p> 3072 <ul style="list-style:none;display:flex;flex-direction:column;gap:8px;margin-top:24px;"> 3073 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--accent)"></span>8–50ms typical segment computation</li> 3074 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-low)"></span>EEZ transit analysis with zone identification</li> 3075 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:var(--risk-elev)"></span>Corridor smoothing eliminates jagged grid paths</li> 3076 <li class="signal-item" style="background:transparent;border:none;padding:4px 0;"><span class="signal-dot" style="background:#a78bfa"></span>Recursive subdivision for trans-oceanic routes</li> 3077 </ul> 3078 </div> 3079 <div class="routing-map-wrap reveal reveal-delay-2"> 3080 <div class="routing-map" id="routingShowcaseMap"> 3081 <div class="routing-map-loading">Loading route…</div> 3082 </div> 3083 <div class="routing-map-label">Rotterdam → Singapore · Live pathfinding result</div> 3084 </div> 3085 </div> 3086 </div> 3087 </section> 3088 3089 <div class="section-divider"></div> 3090 3091 <!-- ============================================================ 3092 VESSELWATCH ALERTS â 9 TYPES 3093 ============================================================ --> 3094 <section class="section alerts-section" id="alerts"> 3095 <div class="container"> 3096 <div class="alerts-layout alerts-layout-reverse"> 3097 <div class="reveal reveal-delay-2"> 3098 <div class="mockup-card"> 3099 <div class="mockup-titlebar">
3100 <span class="mockup-dot"></span><span class="mockup-dot"></span><span class="mockup-dot"></span> 3101 <span class="mockup-titlebar-text">arcnautical.com/alerts</span> 3102 </div> 3103 <div class="mockup-body" style="padding:0;"> 3104 <div class="alert-feed"> 3105 <div class="alert-item" data-severity="critical"> 3106 <span class="alert-badge" data-severity="critical">CRIT</span> 3107 <div class="alert-content"> 3108 <div class="alert-type">AIS Gap Detected</div> 3109 <div class="alert-msg"><span class="alert-vessel">MV DEMO ALPHA</span> — 6.2h signal loss — 12°41'N 043°18'E</div> 3110 <div class="alert-time">2 min ago · Webhook delivered</div> 3111 </div> 3112 </div> 3113 <div class="alert-item" data-severity="high"> 3114 <span class="alert-badge" data-severity="high">HIGH</span> 3115 <div class="alert-content"> 3116 <div class="alert-type">Sanctions Hit</div> 3117 <div class="alert-msg"><span class="alert-vessel">MV DEMO BETA</span> — OFAC SDN match on registered owner</div> 3118 <div class="alert-time">18 min ago · Webhook delivered</div> 3119 </div> 3120 </div> 3121 <div class="alert-item" data-severity="medium"> 3122 <span class="alert-badge" data-severity="medium">MED</span> 3123 <div class="alert-content"> 3124 <div class="alert-type">Geofence Exit</div> 3125 <div class="alert-msg"><span class="alert-vessel">MV DEMO GAMMA</span> — Left "Gulf Operations Zone"</div> 3126 <div class="alert-time">43 min ago</div> 3127 </div> 3128 </div> 3129 <div class="alert-item" data-severity="low"> 3130 <span class="alert-badge" data-severity="low">LOW</span> 3131 <div class="alert-content"> 3132 <div class="alert-type">Weather Alert</div> 3133 <div class="alert-msg"><span class="alert-vessel">MV DEMO DELTA</span> — Beaufort 8+ forecast — Bay of Bengal</div> 3134 <div class="alert-time">1h ago</div> 3135 </div> 3136 </div> 3137 </div> 3138 </div> 3139 </div> 3140 </div> 3141 <div class="reveal"> 3142 <div class="section-label">Fleet Monitoring</div> 3143 <h3 class="section-title">9 alert types, webhook delivery</h3> 3144 <p class="section-desc">Continuous 5-minute evaluation cycles across your insured fleet. HMAC-SHA256 signed webhooks with exponential backoff retry and per-type suppression windows.</p> 3145 <div class="alert-types-grid"> 3146 <div class="alert-type-badge"><span class="alert-type-dot" style="background:#a78bfa"></span>AIS Gap</div> 3147 <div class="alert-type-badge"><span class="alert-type-dot" style="background:var(--risk-crit)"></span>Zone Entry</div> 3148 <div class="alert-type-badge"><span class="alert-type-dot" style="background:var(--risk-high)"></span>Piracy</div> 3149 <div class="alert-type-badge"><span class="alert-type-dot" style="background:var(--risk-elev)"></span>NAVWARN</div> 3150 <div class="alert-type-badge"><span class="alert-type-dot" style="background:#38bdf8"></span>Weather</div> 3151 <div class="alert-type-badge"><span class="alert-type-dot" style="background:#f472b6"></span>Sanctions</div> 3152 <div class="alert-type-badge"><span class="alert-type-dot" style="background:var(--risk-low)"></span>Geofence</div> 3153 <div class="alert-type-badge"><span class="alert-type-dot" style="background:var(--accent)"></span>Speed Anomaly</div> 3154 <div class="alert-type-badge"><span class="alert-type-dot" style="background:#fbbf24"></span>Dark Activity</div> 3155 </div> 3156 <div style="font-size:12px;color:var(--text-dim);line-height:1.7;margin-top:16px;font-family:var(--mono);"> 3157 HMAC-SHA256 signed · Exponential backoff · 5-min cycles · Per-type suppression 3158 </div> 3159 </div> 3160 </div> 3161 </div> 3162 </section> 3163 3164 3165 <!-- ============================================================ 3166 PRICING 3167 ============================================================ --> 3168 <section id="pricing" style="padding:96px 0;background:var(--bg);border-top:1px solid var(--border);"> 3169 <div class="container"> 3170 <div style="text-align:center;margin-bottom:40px;"> 3171 <div style="font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;" class="reveal">
3171Pricing</div> 3172 <p style="color:var(--text-sec);font-size:15px;max-width:680px;margin:0 auto;" class="reveal reveal-delay-1">Free to use. Only the API is metered, and every number below is the limit it enforces.</p> 3173 </div> 3174 3175 <!-- Plan grid --> 3176 <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:24px;max-width:1080px;margin:0 auto;"> 3177 3178 <!-- CHECK A VESSEL (no account) --> 3179 <div class="reveal reveal-delay-2" style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:32px 28px;display:flex;flex-direction:column;"> 3180 <div style="font-family:var(--mono);font-size:11px;font-weight:700;color:var(--text-dim);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:14px;">Check a vessel</div> 3181 <div style="display:flex;align-items:baseline;gap:8px;margin-bottom:6px;"><span style="font-family:var(--display);font-size:2.4rem;color:var(--text);font-weight:400;">No account</span></div> 3182 <div style="color:var(--text-dim);font-size:12px;margin-bottom:24px;font-family:var(--mono);">One IMO, one verdict</div> 3183 <ul style="list-style:none;padding:0;margin:0 0 28px;color:var(--text-sec);font-size:14px;line-height:1.9;flex:1;"> 3184 <li>✓ <span data-limit="keylessCheckPerHour">100</span> checks an hour</li> 3185 <li>✓ Same lists, same engine as the API</li> 3186 <li>✓ <span data-limit="playgroundPerHour">5</span> route risk previews an hour</li> 3187 </ul> 3188 <a href="/check" class="btn-ghost" style="text-align:center;">Check a vessel</a> 3189 </div> 3190 3191 <!-- ACCOUNT (highlighted) --> 3192 <div class="reveal reveal-delay-2" style="background:var(--surface);border:2px solid var(--accent);border-radius:8px;padding:32px 28px;display:flex;flex-direction:column;position:relative;"> 3193 <div style="position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:var(--accent);color:#fff;font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:0.12em;padding:4px 14px;border-radius:4px;">SELF-SERVE</div> 3194 <div style="font-family:var(--mono);font-size:11px;font-weight:700;color:var(--accent);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:14px;">Account</div> 3195 <div style="display:flex;align-items:baseline;gap:8px;margin-bottom:6px;"><span style="font-family:var(--display);font-size:2.4rem;color:var(--text);font-weight:400;">$0</span><span style="color:var(--text-dim);font-size:13px;">no card, no expiry</span></div> 3196 <div style="color:var(--text-dim);font-size:12px;margin-bottom:24px;font-family:var(--mono);">Workspace and API, one sign-up</div> 3197 <ul style="list-style:none;padding:0;margin:0 0 28px;color:var(--text-sec);font-size:14px;line-height:1.9;flex:1;"> 3198 <li>✓ Dossiers, voyage risk, fleets, alerts — unmetered</li> 3199 <li>✓ <strong><span data-limit="liveScreeningLimit">5,000</span> sanctions screenings</strong> to start (one-time)</li> 3200 <li>✓ <span data-limit="liveAssessmentLimit">10,000</span> voyage risk assessments a month</li> 3201 <li>✓ <span data-limit="vesselMonitorLimit">50</span> vessels monitored daily</li> 3202 <li>✓ Records kept <span data-limit="screeningRetentionYears">10</span> years, every match cites its source</li> 3203 <li>✓ Full limits on <a href="/developers/" style="color:var(--accent);">/developers</a></li> 3204 </ul> 3205 <a href="/arcnautical.html#/register" class="btn-primary" style="text-align:center;">Open an account</a> 3206 </div> 3207 3208 <!-- PLATFORM --> 3209 <div class="reveal reveal-delay-3" style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:32px 28px;display:flex;flex-direction:column;"> 3210 <div style="font-family:var(--mono);font-size:11px;font-weight:700;color:var(--text-dim);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:14px;">Platform</div> 3211 <div style="display:flex;align-items:baseline;gap:8px;margin-bottom:6px;"><span style="font-family:var(--display);font-size:2.4rem;color:var(--text);font-weight:400;">
3211Per fleet</span></div> 3212 <div style="color:var(--text-dim);font-size:12px;margin-bottom:24px;font-family:var(--mono);">Annual, for screening a whole fleet</div> 3213 <ul style="list-style:none;padding:0;margin:0 0 28px;color:var(--text-sec);font-size:14px;line-height:1.9;flex:1;"> 3214 <li>✓ Allowance sized to your fleet</li> 3215 <li>✓ <span data-limit="slaMonthlyUptimePct">99.5</span>% uptime <a href="/sla" style="color:var(--accent);">SLA</a> with credits</li> 3216 <li>✓ <a href="/dpa" style="color:var(--accent);">DPA</a> and <a href="/security" style="color:var(--accent);">security review</a></li> 3217 <li>✓ Same API, same records</li> 3218 </ul> 3219 <a href="/cdn-cgi/l/email-protection#650401080c0b250417060b0410110c0604094b060a085a1610070f000611582417062b0410110c06040940575535090411030a1708405755402057405d55405c514057550309000011405755160c1f00" class="btn-ghost" style="text-align:center;">Tell us your fleet size</a> 3220 </div> 3221 3222 </div> 3223 </div> 3224 </section> 3225 3226 <!-- ============================================================ 3227 INSTANT VESSEL CHECK 3228 ============================================================ --> 3229 <section id="vessel-check" style="padding:80px 0;background:var(--surface);border-top:1px solid var(--border);border-bottom:1px solid var(--border);"> 3230 <div class="container" style="max-width:720px;text-align:center;"> 3231 <div style="font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;" class="reveal">Instant Vessel Intelligence</div> 3232 <h2 style="font-family:var(--display);font-size:clamp(1.6rem,2.5vw,2.2rem);color:var(--text);margin-bottom:12px;font-weight:400;" class="reveal">Check any vessel in 3 seconds.</h2> 3233 <p style="color:var(--text-sec);margin-bottom:32px;font-size:15px;" class="reveal">Sanctions status, ownership transparency, and vetting grade — powered by OFAC, EU, UN sanctions lists, GLEIF ownership data, and global PSC records. Free, no signup required.</p> 3234 3235 <!-- IMO Input --> 3236 <form id="landingCheckForm" onsubmit="return landingQuickCheck(event)" style="display:flex;gap:12px;max-width:480px;margin:0 auto 24px;" class="reveal"> 3237 <input type="text" id="landing-imo" placeholder="7-digit IMO number" required pattern="[0-9]{7}" title="Enter a 7-digit IMO number" style="flex:1;padding:14px 18px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--text);font-family:var(--mono);font-size:16px;outline:none;letter-spacing:0.05em;"> 3238 <button type="submit" id="landing-check-btn" class="btn-primary" style="padding:14px 28px;font-size:15px;cursor:pointer;border:none;white-space:nowrap;">Check Vessel</button> 3239 </form> 3240 3241 <!-- Results (hidden until check) --> 3242 <div id="landing-results" style="display:none;"></div> 3243 3244 <p style="color:var(--text-dim);font-size:12px;margin-top:16px;font-family:var(--mono);" class="reveal">Try: 9034690 (OFAC-sanctioned) · 9811000 (Ever Given) · 9295345 (Altomare SA case)</p> 3245 </div> 3246 </section> 3247 3248 <!-- ============================================================ 3249 LIVE PORT CONGESTION (free, no signup) 3250 ============================================================ --> 3251 <section id="port-congestion" style="padding:80px 0;background:var(--bg);border-bottom:1px solid var(--border);"> 3252 <div class="container" style="max-width:720px;text-align:center;"> 3253 <div style="font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;" class="reveal">Live Port Operations</div> 3254 <h2 style="font-family:var(--display);font-size:clamp(1.6rem,2.5vw,2.2rem);color:var(--text);margin-bottom:12px;font-weight:400;" class="reveal">Which ports are congested right now?</h2> 3255 <p style="color:var(--text-sec);margin-bottom:32px;font-size:15px;" class="reveal">Live vessel queues, average waiting time, and congestion trend for major global ports — powered by IMF PortWatch. Free, no signup required.</p> 3256 <div class="reveal"> 3257 <a href="/arcnautical.html#/congestion" class="btn-primary" style="display:inline-block;padding:14px 32px;font-size:15px;">Open the congestion map →</a> 3258 </div> 3259 <p style="color:var(--text-dim);font-size:12px;margin-top:16px;font-family:var(--mono);" class="reveal">Singapore · Rotterdam · Shanghai · Los Angeles · +500 ports tracked</p> 3260 </div> 3261 </section> 3262 3263 <!-- ============================================================ 3264 FINAL CTA 3265 ============================================================ --> 3266 <section class="cta-section" id="contact"> 3267 <div class="container"> 3268 <div class="cta-badge reveal">FREE TIER — NO CREDIT CARD REQUIRED</div> 3269 <h2 class="cta-title reveal">Ready for the full intelligence briefing?</h2> 3270 <div class="cta-sub reveal reveal-delay-1">Sign up to unlock fleet monitoring, PDF reports, Monte Carlo ETA, sanctions screening, ownership chain analysis, and 6 automated report types.</div> 3271 <div class="cta-actions reveal reveal-delay-2"> 3272 <a href="/arcnautical.html#/register" class="btn-primary" style="font-size:15px;padding:16px 40px;">Create Free Account</a> 3273 <a href="/sample-report.pdf" class="btn-ghost" style="font-size:15px;padding:16px 40px;" download>See Sample Report</a> 3274 </div> 3275 <div class="cta-email reveal reveal-delay-2">Already have an account? <a href="/arcnautical.html#/login">Sign in</a></div> 3276 </div> 3277 </section> 3278 3279 <!-- ============================================================ 3280 FOOTER 3281 ============================================================ --> 3282 <footer class="footer"> 3283 <div class="container"> 3284 <div class="footer-inner"> 3285 <div class="footer-brand"> 3286 <img class="footer-brand-mark" src="/logo.svg" alt="ArcNautical" width="18" height="18">
3287 <span class="footer-brand-text">ARCNAUTICAL</span> 3288 </div> 3289 <ul class="footer-links"> 3290 <li><a href="/arcnautical.html#/dashboard">Platform</a></li> 3291 <li><a href="/arcnautical.html#/register">Get Started</a></li> 3292 <li><a href="/arcnautical.html#/login">Sign In</a></li> 3293 <li><a href="/developers/">API Docs</a></li> 3294 <li><a href="/sanctions-screening-api/">Sanctions Screening API</a></li> 3295 <li><a href="/ship-suppliers/">For Ship Suppliers</a></li> 3296 <li><a href="/blog">Blog</a></li> 3297 <li><a href="/methodology.html">Methodology</a></li> 3298 <li><a href="/trust.html">Trust & Security</a></li> 3299 <li><a href="https://status.arcnautical.com">Status</a></li> 3300 <li><a href="/privacy.html">Privacy</a></li> 3301 <li><a href="/terms.html">Terms</a></li> 3302 </ul> 3303 <div class="footer-right">© 2026 ArcNautical</div> 3304 </div> 3305 <!-- Intelligence directory: crawlable links from the homepage (highest-authority 3306 page) into the programmatic tree, so internal PageRank reaches the deep 3307 vessel/ports/sanctions pages. Server-rendered <a>s, read in Googlebot crawl phase 1. --> 3308 <nav class="footer-directory" aria-label="Maritime intelligence directory" style="margin-top:24px;padding-top:18px;border-top:1px solid #1c2a3e;font-family:'Inter',system-ui,sans-serif;display:flex;flex-wrap:wrap;gap:32px;justify-content:center;font-size:12.5px;line-height:2;color:#7e8fa6"> 3309 <div> 3310 <div style="font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:#5a6b82;margin-bottom:4px">Vessel & maritime intelligence</div> 3311 <a href="/vessel/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Screened vessels</a> 3312 <a href="/ports/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Port intelligence</a> 3313 <a href="/regions/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Maritime regions</a> 3314 <a href="/routes/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Shipping lane risk</a> 3315 <a href="/mou/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Port State Control MoUs</a> 3316 <a href="/flags/" style="color:#9fb2c9;text-decoration:none">Flag registries</a> 3317 </div> 3318 <div> 3319 <div style="font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:#5a6b82;margin-bottom:4px">Sanctions & research</div> 3320 <a href="/sanctions/countries/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Country sanctions</a> 3321 <a href="/sanctions/entities/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Sanctioned entities</a> 3322 <a href="/data/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Open datasets</a> 3323 <a href="/reports/dark-fleet-flag-brief/01/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Dark-fleet flag brief</a> 3324 <a href="/reports/hk-shadow-fleet-ownership-layer/01/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">HK ownership brief</a> 3325 <a href="/reports/global-sanctioned-fleet-index/" style="color:#9fb2c9;text-decoration:none">Global sanctioned-fleet index</a> 3326 </div> 3327 <div> 3328 <div style="font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:#5a6b82;margin-bottom:4px">Developers & API</div> 3329 <a href="/sanctions-screening-api/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Sanctions screening API</a> 3330 <a href="/voyage-risk-api/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Voyage risk API</a> 3331 <a href="/developers/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">API docs</a> 3332 <a href="/developers/reference/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">API reference</a> 3333 <a href="/developers/stability/" style="color:#9fb2c9;text-decoration:none">Stability & changelog</a> 3334 </div> 3335 <div> 3336 <div style="font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:#5a6b82;margin-bottom:4px">Guides & research</div> 3337 <a href="/blog/posts/2026-09-15-sanctions-screening-api-first-call-needs-no-key/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">A screening API where the first call needs no key</a> 3338 <a href="/blog/posts/2026-09-15-type-1234567-into-a-screening-api/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">What a screening API returns for 1234567</a> 3339 <a href="/blog/posts/2026-09-15-ofac-ten-year-recordkeeping-what-a-screening-record-must-hold/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">OFAC ten-year recordkeeping: what a record must hold</a> 3340 <a href="/blog/posts/2026-03-22-ownership-opacity/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">The ownership-opacity problem</a> 3341 <a href="/blog/posts/2026-06-05-hk-shadow-fleet-ownership-layer/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">
3341102 sanctioned ships, one Hong Kong company</a> 3342 <a href="/blog/posts/2026-05-19-dark-fleet-flag-brief/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">2.3% of the sanctioned fleet flies a transparent flag</a> 3343 <a href="/methodology.html#cite" style="color:#9fb2c9;text-decoration:none">Methodology (DOI)</a> 3344 </div> 3345 <div> 3346 <div style="font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:#5a6b82;margin-bottom:4px">Featured screening</div> 3347 <a href="/sanctions/countries/iran/" style="color:#9fb2c9;text-decoration:none;margin-right:16px">Iran-linked fleet</a> 3348 <a href="/vessel/9385879/" style="color:#9fb2c9;text-decoration:none">Sanctioned tanker (Grumant)</a> 3349 </div> 3350 </nav> 3351 <div style="text-align:center;margin-top:24px;padding-top:18px;border-top:1px solid #1c2a3e;font-family:'Inter',system-ui,sans-serif;font-size:12px;line-height:1.7;color:#7e8fa6"> 3352 Built by <a href="/methodology.html" rel="author" style="color:#0891b2;text-decoration:none">ArcNautical</a> · Maritime sanctions & ownership intelligence 3353 <div style="margin-top:6px;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:10.5px;letter-spacing:0.02em;color:#5a6b82">Screening sources: OFAC SDN · EU · UN · UK HMT/OFSI · OpenSanctions · GLEIF · Paris/Tokyo MoU · Regional PSC MoUs</div> 3354 </div> 3355 </div> 3356 </footer> 3357 3358 <!-- ============================================================ 3359 JAVASCRIPT 3360 ============================================================ -->
vendor: 111 bytes, lines 3360-3361
3360 3361 <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
3361<script src="https://unpkg.com/[email protected]/dist/leaflet.js" crossorigin=""></script>
3361 3362
3362<script> 3363 // ---------------------------------------------------------------- 3364 // RISK GAUGE RENDERER 3365 // ---------------------------------------------------------------- 3366 function renderGauge(svgEl, score, riskLevel, size) { 3367 const cx = size / 2, cy = size / 2; 3368 const r = size * 0.38, strokeW = size * 0.06; 3369 const ARC_START = 135, ARC_SPAN = 270; 3370 const RISK_COLORS = { low:'#059669', moderate:'#818cf8', elevated:'#d97706', high:'#ea580c', critical:'#dc2626' }; 3371 3372 function polar(cx, cy, r, deg) { 3373 const rad = (deg * Math.PI) / 180; 3374 return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }; 3375 } 3376 function arc(cx, cy, r, startDeg, endDeg) { 3377 if (Math.abs(endDeg - startDeg) < 0.01) { const p = polar(cx,cy,r,startDeg); return 'M ' + p.x + ' ' + p.y; } 3378 const s = polar(cx,cy,r,startDeg), e = polar(cx,cy,r,endDeg); 3379 return 'M ' + s.x + ' ' + s.y + ' A ' + r + ' ' + r + ' 0 ' + ((endDeg-startDeg)>180?1:0) + ' 1 ' + e.x + ' ' + e.y; 3380 } 3381 3382 const ns = 'http://www.w3.org/2000/svg'; 3383 function mk(tag, attrs) { 3384 const el = document.createElementNS(ns, tag); 3385 for (const [k,v] of Object.entries(attrs)) el.setAttribute(k,v); 3386 return el; 3387 } 3388 3389 let currentColor = RISK_COLORS[riskLevel] || '#818cf8'; 3390 3391 svgEl.appendChild(mk('path', { d:arc(cx,cy,r,ARC_START,ARC_START+ARC_SPAN), fill:'none', stroke:'#1c2a3e', 'stroke-width':strokeW, 'stroke-linecap':'round' })); 3392 3393 const fillPath = mk('path', { d:arc(cx,cy,r,ARC_START,ARC_START), fill:'none', stroke:currentColor, 'stroke-width':strokeW, 'stroke-linecap':'round' }); 3394 svgEl.appendChild(fillPath); 3395 3396 const tickG = mk('g', {}); 3397 const tickOuterR = r + strokeW/2 + size*0.02; 3398 for (let val=0; val<=100; val+=5) { 3399 const deg = ARC_START + (val/100)*ARC_SPAN; 3400 const isMajor = val%10===0; 3401 const len = isMajor ? size*0.04 : size*0.02; 3402 const o = polar(cx,cy,tickOuterR,deg), i = polar(cx,cy,tickOuterR+len,deg); 3403 tickG.appendChild(mk('line', { x1:o.x, y1:o.y, x2:i.x, y2:i.y, stroke:isMajor?'#445068':'#253550', 'stroke-width':isMajor?1.5:0.75 })); 3404 if (isMajor && size > 120) { 3405 const lp = polar(cx,cy,tickOuterR+len+size*0.04,deg); 3406 const txt = document.createElementNS(ns,'text'); 3407 txt.setAttribute('x',lp.x); txt.setAttribute('y',lp.y); 3408 txt.setAttribute('fill','#445068'); txt.setAttribute('font-size',size*0.05); 3409 txt.setAttribute('font-family',"'JetBrains Mono',monospace"); txt.setAttribute('text-anchor','middle'); 3410 txt.setAttribute('dominant-baseline','central'); txt.textContent=val; 3411 tickG.appendChild(txt); 3412 } 3413 } 3414 svgEl.appendChild(tickG); 3415 3416 const scoreTxt = document.createElementNS(ns,'text'); 3417 scoreTxt.setAttribute('x',cx); scoreTxt.setAttribute('y',cy-size*0.02); 3418 scoreTxt.setAttribute('fill','#d0d8e8'); scoreTxt.setAttribute('font-size',size*0.22); 3419 scoreTxt.setAttribute('font-family',"'JetBrains Mono',monospace"); scoreTxt.setAttribute('font-weight','600'); 3420 scoreTxt.setAttribute('text-anchor','middle'); scoreTxt.setAttribute('dominant-baseline','central'); 3421 scoreTxt.textContent = '0'; 3422 svgEl.appendChild(scoreTxt); 3423 3424 const levelTxt = document.createElementNS(ns,'text'); 3425 levelTxt.setAttribute('x',cx); levelTxt.setAttribute('y',cy+size*0.13); 3426 levelTxt.setAttribute('fill',currentColor); levelTxt.setAttribute('font-size',size*0.065); 3427 levelTxt.setAttribute('font-family',"'JetBrains Mono',monospace"); levelTxt.setAttribute('font-weight','500'); 3428 levelTxt.setAttribute('text-anchor','middle'); levelTxt.setAttribute('dominant-baseline','central'); 3429 levelTxt.setAttribute('letter-spacing','0.1em'); 3430 levelTxt.textContent = riskLevel.toUpperCase(); 3431 svgEl.appendChild(levelTxt); 3432 3433 function animateTo(targetScore, targetLevel) { 3434 const newColor = RISK_COLORS[targetLevel] || '#818cf8'; 3435 fillPath.setAttribute('stroke', newColor); 3436 levelTxt.setAttribute('fill', newColor); 3437 levelTxt.textContent = targetLevel.toUpperCase(); 3438 3439 let fromScore = parseFloat(scoreTxt.textContent) || 0; 3440 const duration = 900; 3441 const start = performance.now(); 3442 function frame(now) { 3443 const t = Math.min((now - start) / duration, 1); 3444 const eased = 1 - Math.pow(1 - t, 3);
3445 const current = fromScore + (targetScore - fromScore) * eased; 3446 fillPath.setAttribute('d', arc(cx,cy,r, ARC_START, ARC_START + (current/100)*ARC_SPAN)); 3447 scoreTxt.textContent = Math.round(current); 3448 if (t < 1) requestAnimationFrame(frame); 3449 } 3450 requestAnimationFrame(frame); 3451 } 3452 3453 // Track the CURRENT value, not just the seeded one. animate() used to 3454 // close over the seed, so a live score arriving before the delayed 3455 // animate() fired got dragged back to the seed -- leaving the gauge and 3456 // the route card beneath it showing two different numbers for the same 3457 // route. Both entry points now read and write the same pair. 3458 var curScore = score, curLevel = riskLevel; 3459 return { 3460 animate: function() { animateTo(curScore, curLevel); }, 3461 updateScore: function(newScore, newLevel) { 3462 curScore = newScore; 3463 curLevel = newLevel; 3464 animateTo(newScore, newLevel); 3465 }, 3466 }; 3467 } 3468 3469 // There is no demo feed. Until 2026-09-15 a hardcoded strip of invented 3470 // events (a made-up NAVWARN number, "OFAC SDN update \u2014 3 new maritime 3471 // entities") rendered here with timestamps stamped at page load so it 3472 // always read "4m ago", badged DEMO DATA. On a compliance product's front 3473 // page that is a placeholder with fake times. If the live feed does not 3474 // load, the strip says so and shows nothing. 3475 3476 // ---------------------------------------------------------------- 3477 // TICKER BUILDER 3478 // ---------------------------------------------------------------- 3479 // Dedupe incoming items by (type + msg prefix + hour-bucket) before 3480 // concatenating for the infinite-scroll loop. Handles the case where 3481 // the live piracy feed returns the same event from IMB + ReCAAP + UKMTO, 3482 // which previously showed "Strait of Malacca - Armed robbery" 3x. 3483 function dedupeTickerItems(items) { 3484 var seen = Object.create(null); 3485 var out = []; 3486 for (var i = 0; i < items.length; i++) { 3487 var it = items[i]; 3488 if (!it || typeof it.msg !== 'string') continue; 3489 var hourBucket = ''; 3490 if (it.time) { 3491 try { hourBucket = new Date(it.time).toISOString().slice(0, 13); } catch (e) { /* ignore */ } 3492 } 3493 var key = (it.type || '') + '|' + it.msg.slice(0, 60) + '|' + hourBucket; 3494 if (seen[key]) continue; 3495 seen[key] = true; 3496 out.push(it); 3497 } 3498 return out; 3499 } 3500 3501 function buildTickerFromItems(items) { 3502 var track = document.getElementById('tickerTrack'); 3503 var deduped = dedupeTickerItems(items); 3504 var html = deduped.concat(deduped).map(function(item) { 3505 return '<div class="ticker-item">' + 3506 '<div class="ticker-accent" data-type="' + item.type + '"></div>' + 3507 '<div class="ticker-body">' + 3508 '<div class="ticker-type" data-type="' + item.type + '">' + item.label + '</div>' + 3509 '<div class="ticker-msg">' + escHtml(item.msg) + '</div>' + 3510 '<div class="ticker-time">' + (item.time ? relativeTime(item.time) : '') + '</div>' + 3511 '</div>' + 3512 '</div>'; 3513 }).join(''); 3514 track.innerHTML = html; 3515 } 3516 3517 function escHtml(str) { 3518 return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); 3519 } 3520 3521 function relativeTime(isoStr) { 3522 var diff = Date.now() - new Date(isoStr).getTime(); 3523 var m = Math.floor(diff / 60000); 3524 if (m < 2) return 'just now'; 3525 if (m < 60) return m + 'm ago'; 3526 var h = Math.floor(m / 60); 3527 if (h < 24) return h + 'h ago'; 3528 return Math.floor(h/24) + 'd ago'; 3529 } 3530 3531 // ---------------------------------------------------------------- 3532 // LIVE DATA INIT 3533 // ---------------------------------------------------------------- 3534 var API = '/api/arcnautical/v1'; 3535 3536 function initLiveData(gaugeCtrl) { 3537 Promise.allSettled([ 3538 fetch(API + '/public/featured-score?origin=SGSIN&dest=NLRTM') 3539 .then(function(r) { return r.ok ? r.json() : null; }) 3540 .catch(function() { return null; }), 3541 fetch(API + '/public/intel-feed') 3542 .then(function(r) { return r.ok ? r.json() : null; }) 3543 .catch(function() { return null; }), 3544 ]).then(function(results) { 3545 var score = results[0].status === 'fulfilled' ? results[0].value : null; 3546 var feed = results[1].status === 'fulfilled' ? results[1].value : null; 3547 3548 if (score && typeof score.riskScore === 'number') { 3549 gaugeCtrl.updateScore(score.riskScore, score.riskLevel); 3550 var RISK_COLORS = { low:'#059669', moderate:'#818cf8', elevated:'#d97706', high:'#ea580c', critical:'#dc2626' }; 3551 var col = RISK_COLORS[score.riskLevel] || '#818cf8'; 3552 document.getElementById('heroRiskLabel').textContent = score.riskScore + ' ' + score.riskLevel.toUpperCase(); 3553 document.getElementById('heroRiskLabel').style.color = col;
3554 var updatedEl = document.getElementById('heroUpdatedAt'); 3555 updatedEl.textContent = 'as of ' + relativeTime(score.computedAt); 3556 updatedEl.style.display = 'block'; 3557 document.getElementById('scoreLiveBadge').style.display = 'inline-flex'; 3558 document.getElementById('scoreDemoBadge').style.display = 'none'; 3559 } 3560 3561 var updatedAt = document.getElementById('feedUpdatedAt'); 3562 if (feed && Array.isArray(feed.items) && feed.items.length > 0) { 3563 var tickerItems = feed.items.map(function(item) { 3564 return { 3565 type: item.type || 'jwc', 3566 label: item.label, 3567 msg: item.message, 3568 time: item.timestamp, 3569 }; 3570 }); 3571 buildTickerFromItems(tickerItems); 3572 document.getElementById('feedLiveBadge').style.display = 'inline-flex'; 3573 // The stamp is the newest EVENT on the strip, never the cache write: 3574 // "updated 2m ago" over a week-old report is the fetch succeeding, not 3575 // the source delivering. newestAt comes from the server; older 3576 // servers are covered by reading the items. 3577 var newest = feed.newestAt || null; 3578 if (!newest) { 3579 for (var i = 0; i < tickerItems.length; i++) { 3580 var ts = tickerItems[i].time; 3581 if (ts && (!newest || ts > newest)) newest = ts; 3582 } 3583 } 3584 if (newest) updatedAt.textContent = 'latest event ' + relativeTime(newest); 3585 } else { 3586 // No fabricated fallback. Say it, and show nothing. 3587 updatedAt.textContent = 'feed unavailable'; 3588 var dot = document.getElementById('tickerDot'); 3589 if (dot) dot.style.display = 'none'; 3590 var track = document.getElementById('tickerTrack'); 3591 if (track) track.style.display = 'none'; 3592 } 3593 }); 3594 } 3595 3596 // ---------------------------------------------------------------- 3597 // COUNT-UP ANIMATION 3598 // ---------------------------------------------------------------- 3599 function animateCountUp(el) { 3600 var target = parseFloat(el.dataset.count); 3601 var suffix = el.dataset.suffix || ''; 3602 var decimals = parseInt(el.dataset.decimal || '0', 10); 3603 var duration = 1500, start = performance.now(); 3604 function frame(now) { 3605 var t = Math.min((now - start) / duration, 1); 3606 var eased = 1 - Math.pow(1 - t, 3); 3607 var current = target * eased; 3608 el.textContent = decimals > 0 ? current.toFixed(decimals) + suffix : Math.round(current) + suffix; 3609 if (t < 1) requestAnimationFrame(frame); 3610 } 3611 requestAnimationFrame(frame); 3612 } 3613 3614 // ---------------------------------------------------------------- 3615 // SCROLL REVEAL 3616 // ---------------------------------------------------------------- 3617 function initReveal() { 3618 var obs = new IntersectionObserver(function(entries) { 3619 entries.forEach(function(entry) { 3620 if (!entry.isIntersecting) return; 3621 entry.target.classList.add('visible'); 3622 var counter = entry.target.querySelector('[data-count]'); 3623 if (counter && !counter.dataset.animated) { 3624 counter.dataset.animated = 'true'; 3625 animateCountUp(counter); 3626 } 3627 obs.unobserve(entry.target); 3628 }); 3629 }, { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }); 3630 document.querySelectorAll('.reveal').forEach(function(el) { obs.observe(el); }); 3631 } 3632 3633 // ---------------------------------------------------------------- 3634 // PORT AUTOCOMPLETE 3635 // ---------------------------------------------------------------- 3636 var SEARCH_API = '/api/arcnautical/v1/public/search-ports'; 3637 var SCORE_API = '/api/arcnautical/v1/public/score-route'; 3638 3639 function countryFlag(code) { 3640 if (!code) return ''; 3641 var up = code.toUpperCase(); 3642 if (up.length !== 2) return ''; 3643 var a = 0x1f1e6; 3644 return String.fromCodePoint(a + up.charCodeAt(0) - 65, a + up.charCodeAt(1) - 65); 3645 } 3646 3647 function PortAutocomplete(inputEl, dropdownEl, onSelect) { 3648 this.input = inputEl; 3649 this.dropdown = dropdownEl; 3650 this.onSelect = onSelect; 3651 this.selected = null; 3652 this.results = []; 3653 this.hlIndex = -1; 3654 this.timer = null; 3655 this.controller = null; 3656 3657 var self = this; 3658 this.input.addEventListener('input', function() { self._onInput(); }); 3659 this.input.addEventListener('keydown', function(e) { self._onKey(e); }); 3660 this.input.addEventListener('focus', function() { 3661 if (self.results.length > 0 && !self.selected) self._open(); 3662 }); 3663 document.addEventListener('click', function(e) { 3664 if (!self.input.parentElement.contains(e.target)) self._close(); 3665 }, true); 3666 } 3667 3668 PortAutocomplete.prototype._onInput = function() { 3669 this.selected = null; 3670 this.input.classList.remove('has-value'); 3671 var q = this.input.value.trim(); 3672 if (q.length < 2) { this._close(); updateScoreBtn(); return; } 3673 if (this.timer) clearTimeout(this.timer); 3674 var self = this; 3675 this.timer = setTimeout(function() { self._search(q); }, 200); 3676 }; 3677 3678 PortAutocomplete.prototype._search = function(q) { 3679 if (this.controller) this.controller.abort(); 3680 this.controller = new AbortController(); 3681 this._showMsg('Searching...'); 3682 var self = this; 3683 fetch(SEARCH_API + '?query=' + encodeURIComponent(q) + '&limit=8', { 3684 signal: this.controller.signal 3685 }).then(function(r) { 3686 if (!r.ok) { self._close(); return; } 3687 return r.json(); 3688 }).then(function(data) { 3689 if (!data) return; 3690 self.results = data.ports || []; 3691 self.hlIndex = -1; 3692 if (self.results.length === 0) self._noMatch(q); 3693 else self._renderResults(); 3694 }).catch(function(e) { 3695 if (e.name === 'AbortError') return; 3696 self._close(); 3697 }); 3698 }; 3699 3700 // A bare "No ports found" was a dead end: a visitor typing a UN/LOCODE we do 3701 // not hold (DKLIN, DETRV on 2026-09-28) got it and left. The code carries its 3702 // country, so offer the ports we cover there; any other miss says how to get 3703 // unstuck. Uppercase only: "Sines" is a port, not Slovenia. 3704 PortAutocomplete.prototype._noMatch = function(q) { 3705 var self = this; 3706 var hint = "No ports found. Try the nearest larger port, or type the country name to list the ports we cover there."; 3707 if (!/^[A-Z]{2}[A-Z2-9]{3}$/.test(q)) { self._showMsg(hint); return; } 3708 var cc = q.slice(0, 2); 3709 fetch(SEARCH_API + '?query=' + cc + '&limit=20', { signal: this.controller.signal }) 3710 .then(function(r) { return r.ok ? r.json() : null; }) 3711 .then(function(data) { 3712 var ports = ((data && data.ports) || []).filter(function(p) { 3713 return String(p.countryCode || p.country_code || '').toUpperCase() === cc; 3714 }); 3715 if (!ports.length) { self._showMsg(hint); return; } 3716 self.results = ports; 3717 self.hlIndex = -1; 3718 self._renderResults('No port matches ' + q + '. Ports we cover in ' + ports[0].c
3718ountry + ':'); 3719 }) 3720 .catch(function(e) { if (e.name !== 'AbortError') self._showMsg(hint); }); 3721 }; 3722 3723 PortAutocomplete.prototype._renderResults = function(heading) { 3724 var self = this; 3725 this.dropdown.innerHTML = (heading ? '<div class="scorer-dd-msg">' + escHtml(heading) + '</div>' : '') + this.results.map(function(p, i) { 3726 return '<div class="scorer-dd-item' + (i === self.hlIndex ? ' hl' : '') + '" data-idx="' + i + '">' + 3727 '<span class="scorer-dd-flag">' + countryFlag(p.countryCode || p.country_code || '') + '</span>' + 3728 '<span class="scorer-dd-name">' + escHtml(p.name) + ', ' + escHtml(p.country) + '</span>' + 3729 '<span class="scorer-dd-code">' + escHtml(p.locode) + '</span>' + 3730 '</div>'; 3731 }).join(''); 3732 3733 this.dropdown.querySelectorAll('.scorer-dd-item').forEach(function(el) { 3734 el.addEventListener('mousedown', function(e) { e.preventDefault(); }); 3735 el.addEventListener('click', function() { self._selectIdx(parseInt(el.dataset.idx)); }); 3736 el.addEventListener('mouseenter', function() { self._highlight(parseInt(el.dataset.idx)); }); 3737 }); 3738 this._open(); 3739 }; 3740 3741 PortAutocomplete.prototype._showMsg = function(text) { 3742 this.dropdown.innerHTML = '<div class="scorer-dd-msg">' + escHtml(text) + '</div>'; 3743 this._open(); 3744 }; 3745 3746 PortAutocomplete.prototype._selectIdx = function(idx) { 3747 var port = this.results[idx]; 3748 if (!port) return; 3749 this.selected = port; 3750 this.input.value = port.name + ' (' + port.locode + ')'; 3751 this.input.classList.add('has-value'); 3752 this._close(); 3753 this.onSelect(port); 3754 updateScoreBtn(); 3755 }; 3756 3757 PortAutocomplete.prototype.setValue = function(port) { 3758 this.selected = port; 3759 this.input.value = port.name + ' (' + port.locode + ')'; 3760 this.input.classList.add('has-value'); 3761 this._close(); 3762 updateScoreBtn(); 3763 }; 3764 3765 PortAutocomplete.prototype.reset = function() { 3766 this.selected = null; 3767 this.input.value = ''; 3768 this.input.classList.remove('has-value'); 3769 this.results = []; 3770 this._close(); 3771 }; 3772 3773 PortAutocomplete.prototype._onKey = function(e) { 3774 if (!this.dropdown.classList.contains('open')) return; 3775 if (e.key === 'ArrowDown') { e.preventDefault(); this._moveHL(1); } 3776 else if (e.key === 'ArrowUp') { e.preventDefault(); this._moveHL(-1); } 3777 else if (e.key === 'Enter') { e.preventDefault(); if (this.hlIndex >= 0) this._selectIdx(this.hlIndex); } 3778 else if (e.key === 'Escape') { this._close(); this.input.blur(); } 3779 }; 3780 3781 PortAutocomplete.prototype._moveHL = function(d) { 3782 var len = this.results.length; 3783 if (!len) return; 3784 var next = this.hlIndex + d; 3785 if (next < 0) next = len - 1; 3786 if (next >= len) next = 0; 3787 this._highlight(next); 3788 }; 3789 3790 PortAutocomplete.prototype._highlight = function(idx) { 3791 this.dropdown.querySelectorAll('.scorer-dd-item').forEach(function(el, i) { 3792 el.classList.toggle('hl', i === idx); 3793 }); 3794 this.hlIndex = idx; 3795 }; 3796 3797 PortAutocomplete.prototype._open = function() { this.dropdown.classList.add('open'); }; 3798 PortAutocomplete.prototype._close = function() { this.dropdown.classList.remove('open'); this.hlIndex = -1; }; 3799 3800 // ---------------------------------------------------------------- 3801 // SCORER STATE 3802 // ---------------------------------------------------------------- 3803 var originAC, destAC, heroGauge, resultsMap, resultsGauge; 3804 3805 function updateScoreBtn() { 3806 var btn = document.getElementById('scoreBtn'); 3807 if (btn) btn.disabled = !(originAC && originAC.selected && destAC && destAC.selected); 3808 } 3809 3810 function scoreRoute() { 3811 if (!originAC.selected || !destAC.selected) return; 3812 if (originAC.selected.locode === destAC.selected.locode) { 3813 showScorerError('Origin and destination must be different ports.'); 3814 return; 3815 } 3816 3817 var btn = document.getElementById('scoreBtn'); 3818 var btnText = document.getElementById('scoreBtnText'); 3819 var spinner = document.getElementById('scoreBtnSpinner'); 3820 var errorEl = document.getElementById('scorerError'); 3821 3822 btn.disabled = true; 3823 btnText.textContent = 'Scoring...'; 3824 spinner.style.display = 'block'; 3825 errorEl.classList.remove('visible'); 3826 3827 fetch(SCORE_API, { 3828 method: 'POST', 3829 headers: { 'Content-Type': 'application/json' }, 3830 body: JSON.stringify({ 3831 origin_locode: originAC.selected.locode, 3832 dest_locode: destAC.selected.locode, 3833 }), 3834 }).then(function(resp) { 3835 if (resp.status === 429) { 3836 return resp.json().catch(function() { return {}; }).then(function(errData) { 3837 showScorerError(errData.error || 'Too many requests in a short period â please try again in a minute.'); 3838 throw new Error('rate_limit'); 3839 }); 3840 } 3841 if (!resp.ok) { 3842 return resp.json().catch(function() { return {}; }).then(function(errData) { 3843 showScorerError(errData.error || 'Could not score this route. Please try again.'); 3844 throw new Error('api_error'); 3845 }); 3846 } 3847 return resp.json(); 3848 }).then(function(data) { 3849 updateHeroCard(data); 3850 renderResults(data); 3851 }).catch(function(err) { 3852 if (err.message !== 'rate_limit' && err.message !== 'api_error') { 3853 console.error('[ArcNautical] Score error:', err); 3854 showScorerError(err && err.message ? err.message : 'Network error. Please check your connection and try again.'); 3855 } 3856 }).finally(function() { 3857 btn.disabled = false;
3858 btnText.textContent = 'Score This Route'; 3859 spinner.style.display = 'none'; 3860 }); 3861 } 3862 3863 function showScorerError(msg) { 3864 var el = document.getElementById('scorerError'); 3865 el.textContent = msg; 3866 el.classList.add('visible'); 3867 var btn = document.getElementById('scoreBtn'); 3868 btn.disabled = false; 3869 document.getElementById('scoreBtnText').textContent = 'Score This Route'; 3870 document.getElementById('scoreBtnSpinner').style.display = 'none'; 3871 } 3872 3873 function updateHeroCard(data) { 3874 var RISK_COLORS = { low:'#059669', moderate:'#818cf8', elevated:'#d97706', high:'#ea580c', critical:'#dc2626' }; 3875 var col = RISK_COLORS[data.riskLevel] || '#818cf8'; 3876 3877 if (heroGauge) heroGauge.updateScore(data.riskScore, data.riskLevel); 3878 3879 var routePorts = document.getElementById('heroRoutePorts'); 3880 if (routePorts) routePorts.innerHTML = data.origin.locode + ' <span style="color:var(--text-dim);">→</span> ' + data.destination.locode; 3881 3882 var routeMeta = document.getElementById('heroRouteMeta'); 3883 if (routeMeta) routeMeta.innerHTML = data.distanceNm.toLocaleString() + ' nm · Risk Score: <span id="heroRiskLabel" style="color:' + col + ';font-weight:600;">' + data.riskScore + ' ' + data.riskLevel.toUpperCase() + '</span>'; 3884 3885 var liveBadge = document.getElementById('scoreLiveBadge'); 3886 var demoBadge = document.getElementById('scoreDemoBadge'); 3887 if (liveBadge) liveBadge.style.display = 'inline-flex'; 3888 if (demoBadge) demoBadge.style.display = 'none'; 3889 3890 var updatedEl = document.getElementById('heroUpdatedAt'); 3891 if (updatedEl) { 3892 updatedEl.textContent = 'scored ' + relativeTime(data.computedAt); 3893 updatedEl.style.display = 'block'; 3894 } 3895 3896 var wpContainer = document.getElementById('heroWaypoints'); 3897 if (wpContainer && data.waypoints && data.waypoints.length > 0) { 3898 wpContainer.innerHTML = data.waypoints.map(function(wp, i) { 3899 var arrow = i < data.waypoints.length - 1 ? '<span class="hero-waypoint-arrow">→</span>' : ''; 3900 return '<span class="hero-waypoint">' + escHtml(wp.name) + '</span>' + arrow; 3901 }).join(''); 3902 } 3903 } 3904 3905 // ---------------------------------------------------------------- 3906 // RESULTS RENDERER 3907 // ---------------------------------------------------------------- 3908 function renderResults(data) { 3909 var panel = document.getElementById('scorerResults'); 3910 3911 if (resultsMap) { 3912 try { resultsMap.off(); resultsMap.remove(); } catch(e) {} 3913 resultsMap = null; 3914 } 3915 3916 var RISK_COLORS = { low:'#059669', moderate:'#818cf8', elevated:'#d97706', high:'#ea580c', critical:'#dc2626' }; 3917 var col = RISK_COLORS[data.riskLevel] || '#818cf8'; 3918 3919 var confPct = Math.round((data.dataConfidence || 0) * 100); 3920 var confClass = confPct >= 50 ? 'good' : 'limited'; 3921 var confLabel = confPct >= 50 ? confPct + '% data coverage' : 'Limited coverage (' + confPct + '%)'; 3922 3923 var driversHtml = (data.drivers || []).map(function(d) { 3924 var dCol = d.score >= 70 ? RISK_COLORS.critical : 3925 d.score >= 50 ? RISK_COLORS.high : 3926 d.score >= 30 ? RISK_COLORS.elevated : 3927 d.score >= 15 ? RISK_COLORS.moderate : RISK_COLORS.low; 3928 return '<div class="driver-card">' + 3929 '<div class="driver-card-name">' + escHtml(d.factor) + '</div>' + 3930 '<div class="driver-card-score" style="color:' + dCol + '">' + d.score + '</div>' + 3931 '<div class="driver-card-weight">Weight: ' + Math.round(d.weight * 100) + '%</div>' + 3932 '<div class="driver-card-bar"><div class="driver-card-bar-fill" style="width:' + d.score + '%;background:' + dCol + '"></div></div>' + 3933 '<div class="driver-card-detail">' + escHtml(d.details) + '</div>' + 3934 '</div>'; 3935 }).join(''); 3936 3937 var wpHtml = (data.waypoints || []).map(function(wp, i) { 3938 var arrow = i < data.waypoints.length - 1 ? '<span class="results-wp-arrow">→</span>' : ''; 3939 return '<span class="results-wp-tag">' + escHtml(wp.name) + '</span>' + arrow; 3940 }).join(''); 3941 3942 var durationHours = data.durationHours || Math.round(data.distanceNm / 14); 3943 var durationDays = (durationHours / 24).toFixed(1); 3944 3945 panel.innerHTML = 3946 '<div class="results-inner">' + 3947 '<div class="results-header">' + 3948 '<div>' + 3949 '<div class="results-route-name">' + escHtml(data.origin.name) + ' <span>→</span> ' + escHtml(data.destination.name) + '</div>' + 3950 '<div class="results-scored-at">' + data.origin.locode + ' → ' + data.destination.locode + ' · Scored ' + relativeTime(data.computedAt) + '</div>' + 3951 '</div>' + 3952 '<div style="display:flex;gap:8px;flex-wrap:wrap">' + 3953 '<button class="results-score-again-btn" id="shareLaneBtn" type="button">ð Copy shareable link</button>' + 3954 '<button class="results-score-again-btn" id="scoreAgainBtn">Score Another Route</button>' + 3955 '</div>' + 3956 '</div>' + 3957 '<div class="results-body">' + 3958 '<div class="results-grid">' + 3959 '<div class="results-score-col">' + 3960 '<svg id="resultsGauge" width="200" height="200" viewB
3960ox="0 0 200 200"></svg>' + 3961 '<div class="results-meta">' + 3962 '<div class="results-meta-item"><strong>' + data.distanceNm.toLocaleString() + '</strong> nm</div>' + 3963 '<div class="results-meta-item"><strong>' + Math.round(durationHours) + '</strong> hours (~' + durationDays + ' days)</div>' + 3964 '<div class="results-confidence ' + confClass + '">' + confLabel + '</div>' + 3965 '</div>' + 3966 '</div>' + 3967 '<div class="results-map-col">' + 3968 '<div class="results-map" id="resultsMap"></div>' + 3969 '</div>' + 3970 '</div>' + 3971 (wpHtml ? '<div class="results-waypoints"><span class="results-wp-label">Via:</span>' + wpHtml + '</div>' : '') + 3972 '<div class="results-drivers-title">Risk Drivers</div>' + 3973 '<div class="results-drivers">' + driversHtml + '</div>' + 3974 // Delivery-framed capture (mounted below). The full result above is shown 3975 // unconditionally â email is how you TAKE the brief with you, never a toll 3976 // to reveal it. Replaces the old blur/lock signup gate, which hid fields the 3977 // public lane path never computes (Monte Carlo / ownership / Cape-vs-Suez / 3978 // AI narrative) â a scarcity + placeholder-integrity defect. 3979 '<div id="lane-capture-slot"></div>' + 3980 '</div>' + 3981 '<div class="results-footer" style="flex-direction:column;gap:12px">' + 3982 // The one thing this result did not offer before: a way to keep it. 3983 '<a id="laneSignupCta" href="' + escHtml(laneSignupUrl(data.origin.locode, data.destination.locode)) + '"' + 3984 ' style="display:block;width:100%;max-width:520px;text-align:center;text-decoration:none;' + 3985 'background:linear-gradient(135deg,#4f46e5,#6366f1);border:1px solid #818cf8;border-radius:8px;' + 3986 'padding:14px 22px;transition:filter .15s">' + 3987 '<span style="display:block;color:#fff;font-weight:600;font-size:14px">Keep this lane â create a free account</span>' + 3988 '<span style="display:block;color:rgba(255,255,255,0.78);font-size:12px;margin-top:3px">' + 3989 'Score it again anytime and get alerted when it changes.</span>' + 3990 '</a>' + 3991 '<button class="scorer-popular-btn" id="scoreAnotherBtn2" style="padding:10px 24px;font-size:13px;">Score Another Route</button>' + 3992 '</div>' + 3993 '</div>'; 3994 3995 document.getElementById('scoreAgainBtn').addEventListener('click', resetScorer); 3996 document.getElementById('scoreAnotherBtn2').addEventListener('click', resetScorer); 3997 3998 var shareLaneBtn = document.getElementById('shareLaneBtn'); 3999 if (shareLaneBtn) shareLaneBtn.addEventListener('click', function () { 4000 var url = laneShareUrl(data.origin.locode, data.destination.locode); 4001 arcCopyToClipboard(url, function (ok) { 4002 shareLaneBtn.textContent = ok ? 'Link copied â' : 'Copy failed'; 4003 setTimeout(function () { shareLaneBtn.textContent = 'ð Copy shareable link'; }, 2400); 4004 }); 4005 try { window.posthog && posthog.capture('lane_result_shared', { origin: data.origin.locode, dest: data.destination.locode, surface: 'arcnautical-landing' }); } catch (e) {} 4006 }); 4007 4008 var laneSignupCta = document.getElementById('laneSignupCta'); 4009 if (laneSignupCta) laneSignupCta.addEventListener('click', function () { 4010 // Fired on the way out, so the funnel can tell "scored and left" from 4011 // "scored and went to sign up" â the step that was previously invisible. 4012 try { 4013 window.posthog && posthog.capture('lane_signup_cta_clicked', { 4014 origin: data.origin.locode, 4015 dest: data.destination.locode, 4016 score: data.riskScore, 4017 surface: 'arcnautical-landing', 4018 }); 4019 } catch (e) {} 4020 }); 4021 4022 var gaugeEl = document.getElementById('resultsGauge'); 4023 resultsGauge = renderGauge(gaugeEl, 0, 'low', 200); 4024 setTimeout(function() { resultsGauge.updateScore(data.riskScore, data.riskLevel); }, 100); 4025 4026 // Delivery-framed lane-brief capture â email = take the brief with you, not a gate. 4027 if (window.ArcLaneCapture) { 4028 ArcLaneCapture.mount('lane-capture-slot', { 4029 originLocode: data.origin.locode, 4030 destLocode: data.destination.locode, 4031 originName: data.origin.name, 4032 destName: data.destination.name, 4033 geojson: data.routeGeojson, 4034 surface: 'arcnautical-landing', 4035 }); 4036 } 4037 4038 requestAnimationFrame(function() { 4039 panel.classList.add('visible'); 4040 setTimeout(function() { 4041 renderResultsMap(data); 4042 panel.scrollIntoView({ behavior: 'smooth', block: 'start' }); 4043 }, 500); 4044 }); 4045 } 4046 4047 function resetScorer() { 4048 var panel = document.getElementById('scorerResults'); 4049 panel.classList.remove('visible'); 4050 setTimeout(function() { panel.innerHTML = ''; }, 700); 4051 4052 if (resultsMap) { resultsMap.remove(); resultsMap = null; } 4053 originAC.reset(); 4054 destAC.reset(); 4055 updateScoreBtn(); 4056 4057 document.getElementById('hero').scrollIntoView({ behavior: 'smooth' }); 4058 setTimeout(function() { document.getElementById('originInput').focus(); }, 500); 4059 } 4060 4061 // ---------------------------------------------------------------- 4062 // MAP RENDERER 4063 // ---------------------------------------------------------------- 4064 function renderResultsMap(data) { 4065 if (resultsMap) { 4066 try { resultsMap.off(); resultsMap.remove(); } catch(e) {} 4067 resultsMap = null; 4068 } 4069 var mapEl = document.getElementById('resultsMap'); 4070 if (!mapEl || !window.L) return; 4071 4072 resultsMap = L.map(mapEl, { 4073 zoomControl: false, 4074 attributionControl: true, 4075 scrollWheelZoom: false, 4076 }); 4077 4078 L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { 4079 attribution: '© <a href="https://carto.com/">CARTO</a>', 4080 subdomains: 'abcd', 4081 maxZoom: 19, 4082 }).addTo(resultsMap); 4083 4084 L.control.zoom({ position: 'topright' }).addTo(resultsMap); 4085 4086 var initBounds = null; 4087 if (data.routeGeojson) { 4088 initBounds = L.geoJSON(data.routeGeojson).getBounds(); 4089 } 4090 if (initBounds && initBounds.isValid()) { 4091 resultsMap.fitBounds(initBounds, { padding: [40, 40] }); 4092 } else if (data.origin && data.destination) { 4093 resultsMap.fitBounds([ 4094 [data.origin.lat, data.origin.lng], 4095 [data.destination.lat, data.destination.lng] 4096 ], { padding: [40, 40] }); 4097 } else {
4098 resultsMap.setView([20, 40], 3); 4099 } 4100 4101 resultsMap.invalidateSize(); 4102 4103 if (data.routeGeojson) { 4104 L.geoJSON(data.routeGeojson, { 4105 style: { color: '#0891b2', weight: 2.5, opacity: 0.8 }, 4106 }).addTo(resultsMap); 4107 } 4108 4109 if (data.origin && data.origin.lat) { 4110 L.circleMarker([data.origin.lat, data.origin.lng], { 4111 radius: 7, fillColor: '#059669', fillOpacity: 0.9, color: '#fff', weight: 2, 4112 }).bindTooltip(data.origin.name, { permanent: false }).addTo(resultsMap); 4113 } 4114 4115 if (data.destination && data.destination.lat) { 4116 L.circleMarker([data.destination.lat, data.destination.lng], { 4117 radius: 7, fillColor: '#dc2626', fillOpacity: 0.9, color: '#fff', weight: 2, 4118 }).bindTooltip(data.destination.name, { permanent: false }).addTo(resultsMap); 4119 } 4120 4121 if (data.waypoints) { 4122 data.waypoints.forEach(function(wp) { 4123 if (!wp.lat || !wp.lng) return; 4124 L.circleMarker([wp.lat, wp.lng], { 4125 radius: 4, fillColor: '#f59e0b', fillOpacity: 0.8, color: '#fff', weight: 1, 4126 }).bindTooltip(wp.name, { permanent: false }).addTo(resultsMap); 4127 }); 4128 } 4129 } 4130 4131 // ---------------------------------------------------------------- 4132 // SHAREABLE LANE PERMALINK â a zero-cost rung between "anonymous" and "gave 4133 // email": copy a link to THIS lane result; /?origin=â¦&dest=⦠re-scores it live 4134 // for whoever opens it (see the score-from-URL block in INIT). Every forward is 4135 // a fresh top-of-funnel touch, no email required. Mirrors /check?imo= on the 4136 // vessel side. 4137 // ---------------------------------------------------------------- 4138 function laneShareUrl(oL, dL) { 4139 return 'https://arcnautical.com/?origin=' + encodeURIComponent(oL) + '&dest=' + encodeURIComponent(dL); 4140 } 4141 4142 // SIGNUP BRIDGE THAT CARRIES THE LANE. On 2026-08-02 a visitor arrived from 4143 // Brave Search, scored a lane here, registered 52 seconds later â and got a 4144 // seeded "Demo Fleet" and a global dashboard, because the scorer persists 4145 // nothing (Redis cache only) and every signup link was a bare #/register. 4146 // The lane they came for was simply gone. 4147 // 4148 // No new persistence is needed to fix it: #/voyage?origin=&dest= already 4149 // auto-scores, and an AUTHED score already upserts arcnautical_saved_lanes. 4150 // Handing the account the lane in the URL is enough â the row writes itself. 4151 // 4152 // UTM in the search string, route in the hash: RegisterPage reads 4153 // window.location.search for attribution, and the SPA router owns the hash. 4154 // Same shape as check.html's vessel-side signup bridge. 4155 function laneSignupUrl(oL, dL) { 4156 return '/arcnautical.html?utm_source=landing&utm_medium=lane_scorer&utm_campaign=lane_bridge' 4157 + '&lane=' + encodeURIComponent(oL) + '-' + encodeURIComponent(dL) + '#/register'; 4158 } 4159 4160 function arcCopyToClipboard(text, cb) { 4161 cb = cb || function () {}; 4162 try { 4163 if (navigator.clipboard && navigator.clipboard.writeText) { 4164 navigator.clipboard.writeText(text).then(function () { cb(true); }, function () { arcCopyFallback(text, cb); }); 4165 return; 4166 } 4167 } catch (e) {} 4168 arcCopyFallback(text, cb); 4169 } 4170 4171 function arcCopyFallback(text, cb) { 4172 try { 4173 var ta = document.createElement('textarea'); 4174 ta.value = text; ta.setAttribute('readonly', ''); 4175 ta.style.position = 'fixed'; ta.style.top = '-1000px'; ta.style.opacity = '0'; 4176 document.body.appendChild(ta); ta.focus(); ta.select(); 4177 var ok = document.execCommand('copy'); 4178 document.body.removeChild(ta); cb(!!ok); 4179 } catch (e) { cb(false); } 4180 } 4181 4182 // Score a lane from bare LOCODEs (shared-link entry). A shared URL carries only 4183 // codes, so bypass the autocomplete and POST the two codes straight to the scorer 4184 // API â the result is deterministic, so the colleague who opens the link sees 4185 // exactly what was shared, freshly recomputed. 4186 var LANE_LOCODE_RE = /^[A-Z]{2}[A-Z0-9]{3}$/; 4187 function scoreLaneByLocodes(oL, dL) { 4188 oL = String(oL || '').toUpperCase(); dL = String(dL || '').toUpperCase(); 4189 if (!LANE_LOCODE_RE.test(oL) || !LANE_LOCODE_RE.test(dL) || oL === dL) return; 4190 fetch(SCORE_API, { 4191 method: 'POST', 4192 headers: { 'Content-Type': 'application/json' }, 4193 body: JSON.stringify({ origin_locode: oL, dest_locode: dL }), 4194 }).then(function (resp) { 4195 if (!resp.ok) throw new Error('score_failed'); 4196 return resp.json(); 4197 }).then(function (data) { 4198 updateHeroCard(data); 4199 renderResults(data); 4200 try { window.posthog && posthog.capture('lane_deep_link_landed', { origin: oL, dest: dL, source: 'url_param' }); } catch (e) {} 4201 }).catch(function () { /* silent: visitor can still score manually */ }); 4202 } 4203 4204 // ---------------------------------------------------------------- 4205 // POPULAR ROUTES 4206 // ---------------------------------------------------------------- 4207 var POPULAR_PORTS = { 4208 SGSIN: { locode:'SGSIN', name:'Singapore', country:'Singapore', countryCode:'SG', lat:1.29, lng:103.85 }, 4209 NLRTM: { locode:'NLRTM', name:'Rotterdam', country:'Netherlands', countryCode:'NL', lat:51.92, lng:4.48 }, 4210 EGPSD: { locode:'EGPSD', name:'Port Said', country:'Egypt', countryCode:'EG', lat:31.26, lng:32.30 }, 4211 CNSHA: { locode:'CNSHA', name:'Shanghai', country:'China', countryCode:'CN', lat:31.23, lng:121.47 }, 4212 DEHAM: { locode:'DEHAM', name:'Hamburg', country:'Germany', countryCode:'DE', lat:53.55, lng:9.97 }, 4213 USHOU: { locode:'USHOU', name:'Houston', country:'United States', countryCode:'US', lat:29.76, lng:-95.36 }, 4214 AEJEA: { locode:'AEJEA', name:'Jebel Ali', country:'United Arab Emirates', countryCode:'AE', lat:25.01, lng:55.06 }, 4215 }; 4216 4217 function initPopularRoutes() { 4218 document.querySelectorAll('.scorer-popular-btn[data-origin]').forEach(function(btn
4218) { 4219 btn.addEventListener('click', function() { 4220 var o = POPULAR_PORTS[btn.dataset.origin]; 4221 var d = POPULAR_PORTS[btn.dataset.dest]; 4222 if (o && d) { 4223 originAC.setValue(o); 4224 destAC.setValue(d); 4225 updateScoreBtn(); 4226 scoreRoute(); 4227 } 4228 }); 4229 }); 4230 } 4231 4232 // ---------------------------------------------------------------- 4233 // SCROLL PROGRESS BAR 4234 // ---------------------------------------------------------------- 4235 function initScrollProgress() { 4236 var bar = document.getElementById('scrollProgress'); 4237 window.addEventListener('scroll', function() { 4238 var scrollTop = window.pageYOffset || document.documentElement.scrollTop; 4239 var docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; 4240 var pct = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; 4241 bar.style.width = pct + '%'; 4242 }, { passive: true }); 4243 } 4244 4245 // ---------------------------------------------------------------- 4246 // HAMBURGER MENU 4247 // ---------------------------------------------------------------- 4248 function initHamburger() { 4249 var btn = document.getElementById('navHamburger'); 4250 var links = document.getElementById('navLinks'); 4251 btn.addEventListener('click', function() { 4252 btn.classList.toggle('open'); 4253 links.classList.toggle('open'); 4254 }); 4255 // Close on link click 4256 links.querySelectorAll('a').forEach(function(a) { 4257 a.addEventListener('click', function() { 4258 btn.classList.remove('open'); 4259 links.classList.remove('open'); 4260 }); 4261 }); 4262 } 4263 4264 // ---------------------------------------------------------------- 4265 // PRODUCT SHOWCASE TABS 4266 // ---------------------------------------------------------------- 4267 function initProductTabs() { 4268 var tabs = document.querySelectorAll('#showcaseTabs .showcase-tab'); 4269 var panels = document.querySelectorAll('.showcase-panel');
4270 tabs.forEach(function(tab) { 4271 tab.addEventListener('click', function() { 4272 var target = tab.dataset.tab; 4273 tabs.forEach(function(t) { t.classList.remove('active'); }); 4274 tab.classList.add('active'); 4275 panels.forEach(function(p) { 4276 if (p.dataset.panel === target) { 4277 p.classList.add('active'); 4278 // Re-trigger EP curve animation if underwriting tab 4279 if (target === 'underwriting') { 4280 var epPath = document.getElementById('epCurvePath'); 4281 if (epPath) { epPath.classList.remove('drawn'); void epPath.offsetWidth; epPath.classList.add('drawn'); } 4282 } 4283 } else { 4284 p.classList.remove('active'); 4285 } 4286 }); 4287 }); 4288 }); 4289 } 4290 4291 // ---------------------------------------------------------------- 4292 // API PREVIEW TABS 4293 // ---------------------------------------------------------------- 4294 function initApiTabs() { 4295 var tabs = document.querySelectorAll('#apiTabs .api-tab'); 4296 var panels = document.querySelectorAll('.api-panel');
4297 tabs.forEach(function(tab) { 4298 tab.addEventListener('click', function() { 4299 var target = tab.dataset.api; 4300 tabs.forEach(function(t) { t.classList.remove('active'); }); 4301 tab.classList.add('active'); 4302 panels.forEach(function(p) { 4303 p.classList.toggle('active', p.dataset.apiPanel === target); 4304 }); 4305 }); 4306 }); 4307 } 4308 4309 // ---------------------------------------------------------------- 4310 // SIGNAL CONVERGENCE ANIMATION 4311 // ---------------------------------------------------------------- 4312 function initSignalConvergence() { 4313 var svg = document.getElementById('signalsSvg'); 4314 if (!svg) return; 4315 4316 var linesG = document.getElementById('signalLines'); 4317 var nodesG = document.getElementById('signalNodes'); 4318 var centerScore = document.getElementById('signalsCenterScore'); 4319 var ns = 'http://www.w3.org/2000/svg'; 4320 4321 var colors = ['#dc2626','#ea580c','#d97706','#0891b2','#a78bfa','#6366f1','#38bdf8','#f472b6','#fb923c','#fbbf24']; 4322 var labels = ['CON','PIR','NAV','JWC','AIS','CII','WX','SAN','PSC','DIS']; 4323 var cx = 200, cy = 350; 4324 4325 // Place 10 nodes in a semicircle above center 4326 for (var i = 0; i < 10; i++) { 4327 var angle = Math.PI + (Math.PI * (i + 0.5) / 10); 4328 var nx = cx + 160 * Math.cos(angle); 4329 var ny = cy + 160 * Math.sin(angle); 4330 4331 // Line 4332 var line = document.createElementNS(ns, 'line'); 4333 line.setAttribute('x1', nx); 4334 line.setAttribute('y1', ny); 4335 line.setAttribute('x2', cx); 4336 line.setAttribute('y2', cy - 44); 4337 line.setAttribute('stroke', colors[i]); 4338 line.setAttribute('stroke-width', '1'); 4339 line.setAttribute('opacity', '0.4'); 4340 line.setAttribute('class', 'signal-line'); 4341 line.style.transitionDelay = (i * 0.12) + 's'; 4342 linesG.appendChild(line); 4343 4344 // Node circle 4345 var circle = document.createElementNS(ns, 'circle'); 4346 circle.setAttribute('cx', nx); 4347 circle.setAttribute('cy', ny); 4348 circle.setAttribute('r', '18'); 4349 circle.setAttribute('fill', '#0d1420'); 4350 circle.setAttribute('stroke', colors[i]); 4351 circle.setAttribute('stroke-width', '1.5'); 4352 circle.setAttribute('class', 'signal-node'); 4353 circle.style.transitionDelay = (i * 0.12) + 's'; 4354 nodesG.appendChild(circle); 4355 4356 // Node label 4357 var text = document.createElementNS(ns, 'text'); 4358 text.setAttribute('x', nx); 4359 text.setAttribute('y', ny); 4360 text.setAttribute('text-anchor', 'middle'); 4361 text.setAttribute('dominant-baseline', 'central'); 4362 text.setAttribute('fill', colors[i]); 4363 text.setAttribute('font-size', '8'); 4364 text.setAttribute('font-family', "'JetBrains Mono',monospace"); 4365 text.setAttribute('font-weight', '600'); 4366 text.setAttribute('class', 'signal-node'); 4367 text.style.transitionDelay = (i * 0.12) + 's'; 4368 text.textContent = labels[i]; 4369 nodesG.appendChild(text); 4370 } 4371 4372 // Trigger on scroll 4373 var triggered = false; 4374 var obs = new IntersectionObserver(function(entries) { 4375 if (triggered) return; 4376 entries.forEach(function(entry) { 4377 if (!entry.isIntersecting) return; 4378 triggered = true; 4379 svg.querySelectorAll('.signal-node').forEach(function(n) { n.classList.add('visible'); }); 4380 svg.querySelectorAll('.signal-line').forEach(function(l) { l.classList.add('drawn'); }); 4381 // Animate center score 4382 var start = performance.now(); 4383 function frame(now) { 4384 var t = Math.min((now - start) / 1500, 1); 4385 var eased = 1 - Math.pow(1 - t, 3); 4386 centerScore.textContent = Math.round(34 * eased); 4387 if (t < 1) requestAnimationFrame(frame); 4388 } 4389 requestAnimationFrame(frame); 4390 }); 4391 }, { threshold: 0.3 }); 4392 obs.observe(svg); 4393 } 4394 4395 // ---------------------------------------------------------------- 4396 // OWNERSHIP CHAIN ANIMATION 4397 // ---------------------------------------------------------------- 4398 function initOwnershipChain() { 4399 var chain = document.getElementById('ownershipChain'); 4400 if (!chain) return; 4401 4402 var obs = new IntersectionObserver(function(entries) {
4403 entries.forEach(function(entry) { 4404 if (!entry.isIntersecting) return; 4405 chain.querySelectorAll('.ownership-entity, .ownership-connector').forEach(function(el) { 4406 var delay = parseInt(el.dataset.delay || '0'); 4407 setTimeout(function() { el.classList.add('visible'); }, delay); 4408 }); 4409 obs.unobserve(chain); 4410 }); 4411 }, { threshold: 0.3 }); 4412 obs.observe(chain); 4413 } 4414 4415 // ---------------------------------------------------------------- 4416 // CII POINTER ANIMATION 4417 // ---------------------------------------------------------------- 4418 function initCiiPointer() { 4419 var pointer = document.getElementById('ciiPointer'); 4420 if (!pointer) return; 4421 4422 var obs = new IntersectionObserver(function(entries) { 4423 entries.forEach(function(entry) { 4424 if (!entry.isIntersecting) return; 4425 // Slide to B rating (30% position) 4426 pointer.style.left = '30%'; 4427 obs.unobserve(entry.target); 4428 }); 4429 }, { threshold: 0.3 }); 4430 obs.observe(document.getElementById('ciiScale')); 4431 } 4432 4433 // ---------------------------------------------------------------- 4434 // ROUTE DRAWING ANIMATION 4435 // ---------------------------------------------------------------- 4436 var routingShowcaseMap = null; 4437 function initRouteShowcase() { 4438 var mapEl = document.getElementById('routingShowcaseMap'); 4439 if (!mapEl || !window.L) return; 4440 4441 var loaded = false; 4442 var obs = new IntersectionObserver(function(entries) { 4443 entries.forEach(function(entry) { 4444 if (!entry.isIntersecting || loaded) return; 4445 loaded = true; 4446 obs.unobserve(entry.target); 4447 4448 // Init map 4449 routingShowcaseMap = L.map(mapEl, { 4450 zoomControl: false, 4451 attributionControl: true, 4452 scrollWheelZoom: false, 4453 dragging: true, 4454 }); 4455 L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { 4456 attribution: '© <a href="https://carto.com/">CARTO</a>', 4457 subdomains: 'abcd', maxZoom: 19, 4458 }).addTo(routingShowcaseMap); 4459 L.control.zoom({ position: 'topright' }).addTo(routingShowcaseMap); 4460 4461 // Default view while loading 4462 routingShowcaseMap.setView([20, 60], 3); 4463 4464 // Fetch real route from pathfinding engine 4465 fetch('/api/arcnautical/v1/public/score-route', { 4466 method: 'POST', 4467 headers: { 'Content-Type': 'application/json' }, 4468 body: JSON.stringify({ origin_locode: 'NLRTM', dest_locode: 'SGSIN' }), 4469 }) 4470 .then(function(r) { return r.ok ? r.json() : null; }) 4471 .then(function(data) { 4472 if (!data || !routingShowcaseMap) return; 4473 4474 // Draw route 4475 if (data.routeGeojson || data.route_geojson) { 4476 var geojson = data.routeGeojson || data.route_geojson; 4477 var routeLayer = L.geoJSON(geojson, { 4478 style: { color: '#0891b2', weight: 3, opacity: 0.9 }, 4479 }).addTo(routingShowcaseMap); 4480 routingShowcaseMap.fitBounds(routeLayer.getBounds(), { padding: [30, 30] }); 4481 } 4482 4483 // Origin marker 4484 if (data.origin && data.origin.lat) { 4485 L.circleMarker([data.origin.lat, data.origin.lng], { 4486 radius: 7, fillColor: '#059669', fillOpacity: 0.9, color: '#fff', weight: 2, 4487 }).bindTooltip(data.origin.name || 'Rotterdam', { permanent: true, direction: 'top', className: 'route-showcase-label' }).addTo(routingShowcaseMap); 4488 } 4489 4490 // Destination marker 4491 if (data.destination && data.destination.lat) { 4492 L.circleMarker([data.destination.lat, data.destination.lng], { 4493 radius: 7, fillColor: '#dc2626', fillOpacity: 0.9, color: '#fff', weight: 2, 4494 }).bindTooltip(data.destination.name || 'Singapore', { permanent: true, direction: 'top', className: 'route-showcase-label' }).addTo(routingShowcaseMap); 4495 } 4496 4497 // Waypoints 4498 if (data.waypoints) {
4499 data.waypoints.forEach(function(wp) { 4500 if (!wp.lat || !wp.lng) return; 4501 L.circleMarker([wp.lat, wp.lng], { 4502 radius: 4, fillColor: '#f59e0b', fillOpacity: 0.8, color: '#fff', weight: 1, 4503 }).bindTooltip(wp.name, { permanent: false, direction: 'top', className: 'route-showcase-label' }).addTo(routingShowcaseMap); 4504 }); 4505 } 4506 4507 // Update label with distance 4508 var label = document.querySelector('.routing-map-label'); 4509 if (label && data.distanceNm) { 4510 label.textContent = 'Rotterdam \u2192 Singapore \u00B7 ' + Math.round(data.distanceNm).toLocaleString() + ' nm \u00B7 Live pathfinding result'; 4511 } 4512 }) 4513 .catch(function() { 4514 // API unavailable â show static fallback view 4515 var label = document.querySelector('.routing-map-label'); 4516 if (label) label.textContent = 'Rotterdam \u2192 Singapore \u00B7 Connect API for live route'; 4517 }); 4518 }); 4519 }, { threshold: 0.2 }); 4520 obs.observe(mapEl); 4521 } 4522 4523 // ---------------------------------------------------------------- 4524 // EP CURVE ANIMATION 4525 // ---------------------------------------------------------------- 4526 function initEpCurve() { 4527 var obs = new IntersectionObserver(function(entries) { 4528 entries.forEach(function(entry) { 4529 if (!entry.isIntersecting) return; 4530 var path = document.getElementById('epCurvePath'); 4531 if (path) path.classList.add('drawn'); 4532 obs.unobserve(entry.target); 4533 }); 4534 }, { threshold: 0.3 }); 4535 var epSvg = document.getElementById('epCurveSvg'); 4536 if (epSvg) obs.observe(epSvg); 4537 } 4538 4539 // ---------------------------------------------------------------- 4540 // HERO CYCLING PREVIEW 4541 // ---------------------------------------------------------------- 4542 function initHeroPreview() { 4543 var items = document.querySelectorAll('#heroPreview .hero-preview-item'); 4544 if (items.length < 2) return; 4545 var idx = 0; 4546 setInterval(function() { 4547 items[idx].classList.remove('active'); 4548 idx = (idx + 1) % items.length; 4549 items[idx].classList.add('active'); 4550 }, 3500); 4551 } 4552 4553 // ---------------------------------------------------------------- 4554 // INIT 4555 // ---------------------------------------------------------------- 4556 document.addEventListener('DOMContentLoaded', function() { 4557 // Hero gauge 4558 var heroGaugeEl = document.getElementById('heroGauge'); 4559 heroGauge = renderGauge(heroGaugeEl, 34, 'low', 240); 4560 4561 var mockGaugeEl = document.getElementById('mockGauge'); 4562 if (mockGaugeEl) renderGauge(mockGaugeEl, 34, 'low', 100).animate(); 4563 4564 setTimeout(function() { heroGauge.animate(); }, 400); 4565 4566 // Live data 4567 initLiveData(heroGauge); 4568 4569 // Port autocomplete 4570 originAC = new PortAutocomplete( 4571 document.getElementById('originInput'), 4572 document.getElementById('originDropdown'), 4573 function() {} 4574 ); 4575 destAC = new PortAutocomplete( 4576 document.getElementById('destInput'), 4577 document.getElementById('destDropdown'), 4578 function() {} 4579 ); 4580 4581 // Score button 4582 document.getElementById('scoreBtn').addEventListener('click', scoreRoute); 4583 document.getElementById('destInput').addEventListener('keydown', function(e) { 4584 if (e.key === 'Enter' && !document.getElementById('destDropdown').classList.contains('open')) { 4585 if (originAC.selected && destAC.selected) scoreRoute(); 4586 } 4587 }); 4588 4589 // Popular routes 4590 initPopularRoutes(); 4591 4592 // New features 4593 initScrollProgress(); 4594 initHamburger(); 4595 initProductTabs(); 4596 initApiTabs(); 4597 initSignalConvergence(); 4598 initOwnershipChain(); 4599 initCiiPointer(); 4600 initRouteShowcase(); 4601 initEpCurve(); 4602 initHeroPreview(); 4603 4604 // The landing #api capture form is gone: it asked people to wait for 4605 // access that has been self-serve since 2026-07-30. The section now sends 4606 // them to mint a key instead. The form still exists on /developers/ and 4607 // /voyage-risk-api/ for the things that ARE granted by hand â quota above 4608 // 10,000/month and corridor monitors. 4609 4610 // Scroll reveal (last, so all elements are ready) 4611 initReveal(); 4612 4613 // Score-from-URL: a shared lane permalink (/?origin=â¦&dest=â¦) auto-runs, landing 4614 // the recipient straight on the result (mirrors /check?imo= on the vessel side).
4615 try { 4616 var _laneQs = new URLSearchParams(window.location.search); 4617 var _lo = (_laneQs.get('origin') || '').trim(); 4618 var _ld = (_laneQs.get('dest') || '').trim(); 4619 if (_lo && _ld) scoreLaneByLocodes(_lo, _ld); 4620 } catch (e) {} 4621 }); 4622 4623 // Landing page instant vessel check 4624 async function landingQuickCheck(e) { 4625 e.preventDefault(); 4626 var imo = document.getElementById('landing-imo').value.trim(); 4627 if (!/^\d{7}$/.test(imo)) return false; 4628 4629 try { window.posthog && posthog.capture('check_vessel_submitted', { imo: imo, surface: 'arcnautical-landing' }); } catch(_){} 4630 4631 var btn = document.getElementById('landing-check-btn'); 4632 var resultsEl = document.getElementById('landing-results'); 4633 btn.textContent = 'Checking...'; btn.disabled = true; 4634 resultsEl.style.display = 'block'; 4635 resultsEl.innerHTML = '<div style="padding:32px;color:var(--text-sec);font-size:0.85rem">Screening vessel across OFAC, EU, UN sanctions databases, GLEIF ownership registry, and PSC detention records...</div>'; 4636 4637 try { 4638 // Fetch gets its own catch â see the same split in check.html. Sharing 4639 // one catch with the render block below reported every rendering 4640 // TypeError as `reason: 'network', status: 0`. 4641 var r; 4642 try { 4643 r = await fetch('/api/arcnautical/v1/public/quick-check', { 4644 method: 'POST', 4645 headers: { 'Content-Type': 'application/json', 'X-Arc-Browser-Id': (window.arcBrowserId || '') }, 4646 body: JSON.stringify({ imo: imo }) 4647 }); 4648 } catch (netErr) { 4649 try { window.posthog && posthog.capture('check_vessel_failed', { imo: imo, reason: 'network', status: 0, message: String((netErr && netErr.message) || netErr).slice(0, 120), surface: 'arcnautical-landing' }); } catch(_){} 4650 resultsEl.innerHTML = '<div style="padding:24px;color:var(--risk-high)">Check failed. Please try again.</div>'; 4651 return false; 4652 } 4653 if (r.status === 429) { 4654 try { window.posthog && posthog.capture('check_vessel_failed', { imo: imo, reason: 'rate_limited', status: r.status, surface: 'arcnautical-landing' }); } catch(_){} 4655 resultsEl.innerHTML = '<div style="padding:24px;color:var(--risk-elev)">Too many requests in a short period — please try again in a minute.</div>'; 4656 return false; 4657 } 4658 var data = {}; 4659 try { data = await r.json(); } catch (_) { 4660 try { window.posthog && posthog.capture('check_vessel_failed', { imo: imo, reason: 'invalid_json', status: r.status, surface: 'arcnautical-landing' }); } catch(_){} 4661 resultsEl.innerHTML = '<div style="padding:24px;color:var(--risk-high)">Check failed</div>'; 4662 return false; 4663 } 4664 if (!r.ok || !data || data.error) { 4665 try { window.posthog && posthog.capture('check_vessel_failed', { imo: imo, reason: data && data.error ? 'not_found_or_error' : 'http_error', status: r.status, surface: 'arcnautical-landing' }); } catch(_){} 4666 resultsEl.innerHTML = '<div style="padding:24px;color:var(--risk-high)">' + escapeLandingHtml(data && data.error || 'Check failed') + '</div>'; 4667 return false; 4668 } 4669 try { window.posthog && posthog.capture('check_vessel_succeeded', { imo: imo, sanctions_status: data.sanctions && data.sanctions.status, vetting_grade: data.vetting && data.vetting.grade, ownership_opacity: data.ownership && data.ownership.opacity, surface: 'arcnautical-landing' }); } catch(_){} 4670 4671 var sColor = data.sanctions.status === 'RED' ? 'var(--risk-crit)' : data.sanctions.status === 'AMBER' ? 'var(--risk-elev)' : data.sanctions.status === 'INCOMPLETE' ? '#6b7280' : 'var(--risk-low)'; 4672 var oColor = data.ownership.opacity === 'HIGH' ? 'var(--risk-crit)' : data.ownership.opacity === 'MEDIUM' ? 'var(--risk-elev)' : 'var(--risk-low)'; 4673 var gColor = 'AB'.indexOf(data.vetting.grade) >= 0 ? 'var(--risk-low)' : data.vetting.grade === 'C' ? 'var(--risk-elev)' : 'var(--risk-crit)'; 4674 4675 resultsEl.innerHTML = 4676 '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:28px;text-align:center">' + 4677 '<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:24px">' + 4678 '<div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:12px">Sanctions</div>' + 4679 '<div style="width:56px;height:56px;border-radius:50%;background:color-mix(in srgb, ' + sColor + ' 15%, transparent);border:2px solid ' + sColor + ';margin:0 auto 10px;display:flex;align-items:center;justify-content:center"><div style="width:20px;height:20px;border-radius:50%;background:' + sColor + '"></div></div>' + 4680 '<div style="font-size:1rem;font-weight:700;color:' + sColor + '">' + escapeLandingHtml(data.sanctions.status) + '</div>' + 4681 '<div style="font-size:0.7rem;color:var(--text-dim);margin-top:4px">' + escapeLandingHtml(data.sanctions.detail) + '</div>' + 4682 '</div>' + 4683 '<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:24px">' + 4684 '<div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:12px">Ownership Opacity</div>' + 4685 '<div style="font-size:2.2rem;font-weight:700;font-family:var(--mono);color:' + oColor + '">' + data.ownership.score + '</div>' + 4686 '<div style="font-size:0.8rem;color:' + oColor + ';margin-top:4px">' + escapeLandingHtml(data.ownership.opacity) + '</div>' + 4687 '</div>' + 4688 '<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:24px">' + 4689 '<div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:12px">Vetting Grade</div>' + 4690 '<div style="font-size:2.2rem;font-weight:700;font-family:var(--mono);color:' + gColor + '">' + escapeLandingHtml(data.vetting.grade) + '</div>' + 4691 '<div style="font-size:0.8rem;color:var(--text-dim);margin-top:4px">' + data.vetting.score + '/100 · ' + escapeLandingHtml(data.vetting.status) + '</div>' + 4692 '</div>' + 4693 '</div>' + 4694 landingRenderCtas(data, imo); 4695 } catch (err) { 4696 // Reachable only after `r` is set (fetch failures return above), so this 4697 // is a parse/render bug in our own code, not the network. 4698 try { 4699 window.posthog && posthog.capture('check_vessel_failed', { 4700 imo: imo, 4701 reason: r ? 'render' : 'client', 4702 status: r ? r.status : 0, 4703 message: String((err && err.message) || err).slice(0, 200), 4704 surface: 'arcnautical-landing', 4705 }); 4706 } catch(_){} 4707 resultsEl.innerHTML = '<div style="padding:24px;color:var(--risk-high)">Check failed. Please try again.</div>'; 4708 } finally { 4709 btn.textContent = 'Check Vessel'; btn.disabled = false;
4710 } 4711 return false; 4712 } 4713 4714 function landingCtaProfile(data) { 4715 var sanctions = data && data.sanctions && data.sanctions.status; 4716 var grade = data && data.vetting && data.vetting.grade; 4717 var opacity = data && data.ownership && data.ownership.opacity; 4718 if (sanctions === 'RED' || sanctions === 'AMBER' || grade === 'D' || grade === 'E' || opacity === 'HIGH') return 'evidence'; 4719 if (sanctions === 'GREEN' && (grade === 'A' || grade === 'B') && (opacity === 'LOW' || opacity === 'MEDIUM')) return 'watch'; 4720 return 'mixed'; 4721 } 4722 4723 function landingDossierCta(imo, profile, isSecondary) { 4724 var title = profile === 'evidence' ? 'Request reviewed source evidence' : 'Request the reviewed intelligence dossier'; 4725 var body = profile === 'evidence' 4726 ? 'Sanctions match details, ownership chain, PSC records, and source evidence after manual review.' 4727 : 'Ownership chain, sanctions match details, PSC detention records, and PDF after manual review.'; 4728 var button = profile === 'evidence' ? 'Request Reviewed Evidence' : 'Request Reviewed Dossier'; 4729 return '<div id="landing-email-gate" style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:28px;text-align:center' + (isSecondary ? ';margin-top:16px' : '') + '">' + 4730 '<div style="font-family:var(--display);font-size:1.2rem;color:var(--text);margin-bottom:6px">' + title + '</div>' + 4731 '<div style="color:var(--text-sec);font-size:0.8rem;margin-bottom:16px">' + body + '</div>' + 4732 '<form onsubmit="return landingRequestDossier(event,\'' + imo + '\')" style="display:flex;gap:8px;max-width:420px;margin:0 auto;flex-wrap:wrap">' + 4733 '<input type="email" id="landing-lead-email" placeholder="Work email" required style="flex:1;min-width:190px;padding:12px 16px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:14px;outline:none">' + 4734 '<button type="submit" id="landing-dossier-btn" data-default-label="' + button + '" class="btn-primary" style="padding:12px 20px;font-size:14px;cursor:pointer;border:none;white-space:nowrap">' + button + '</button>' + 4735 '</form>' + 4736 '</div>'; 4737 } 4738 4739 function landingWatchCta(imo, isSecondary) { 4740 return '<div id="landing-watch-gate" style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:28px;text-align:center' + (isSecondary ? ';margin-top:16px' : '') + '">' + 4741 '<div style="font-family:var(--display);font-size:1.2rem;color:var(--text);margin-bottom:6px">Watch this vessel for changes</div>' + 4742 '<div style="color:var(--text-sec);font-size:0.8rem;margin-bottom:16px">Get an email if sanctions status, ownership opacity, or vetting grade changes.</div>' + 4743 '<form onsubmit="return landingWatchVessel(event,\'' + imo + '\')" style="display:flex;gap:8px;max-width:420px;margin:0 auto;flex-wrap:wrap">' + 4744 '<input type="email" id="landing-watch-email" placeholder="Work email" required style="flex:1;min-width:190px;padding:12px 16px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:14px;outline:none">' + 4745 '<button type="submit" id="landing-watch-btn" class="btn-primary" style="padding:12px 20px;font-size:14px;cursor:pointer;border:none;white-space:nowrap">Watch Vessel</button>' + 4746 '</form>' + 4747 '</div>'; 4748 } 4749 4750 function landingRenderCtas(data, imo) { 4751 var profile = landingCtaProfile(data); 4752 if (profile === 'watch') return landingWatchCta(imo, false) + landingDossierCta(imo, 'mixed', true); 4753 if (profile === 'mixed') return landingDossierCta(imo, 'mixed', false) + landingWatchCta(imo, true); 4754 return landingDossierCta(imo, 'evidence', false); 4755 } 4756 4757 async function landingRequestDossier(e, imo) { 4758 e.preventDefault(); 4759 var email = document.getElementById('landing-lead-email').value.trim(); 4760 var btn = document.getElementById('landing-dossier-btn'); 4761 var defaultLabel = btn.getAttribute('data-default-label') || 'Request Reviewed Dossier'; 4762 btn.textContent = 'Requesting...'; btn.disabled = true;
4763 try { 4764 if (window.posthog) { 4765 posthog.identify(window.arcBrowserId, { email: email, lead_source: 'landing_dossier_request' }); 4766 posthog.capture('dossier_review_requested', { imo: imo, email: email }); 4767 } 4768 } catch(_){} 4769 try { 4770 var resp = await fetch('/api/arcnautical/v1/public/request-dossier-link', { 4771 method: 'POST', 4772 headers: { 'Content-Type': 'application/json', 'X-Arc-Browser-Id': (window.arcBrowserId || '') }, 4773 body: JSON.stringify({ email: email, imo: imo }) 4774 }); 4775 var data = {}; try { data = await resp.json(); } catch(_){} 4776 if (!resp.ok || !data.success) throw { status: resp.status, reason: (data && data.error) || 'request_failed' }; 4777 btn.parentElement.parentElement.innerHTML = 4778 '<div style="padding:16px;background:rgba(22,163,74,0.1);border:1px solid rgba(22,163,74,0.3);border-radius:6px">' + 4779 '<div style="color:var(--risk-low);font-weight:600;margin-bottom:4px">Dossier review requested.</div>' + 4780 '<div style="color:var(--text-sec);font-size:0.85rem">I will review this IMO and email ' + escapeLandingHtml(email) + ' once the dossier is cleared for release.</div>' + 4781 '</div>'; 4782 } catch (err) { 4783 try { window.posthog && posthog.capture('dossier_review_failed', { imo: imo, reason: err && err.reason || 'network', status: err && err.status || 0, surface: 'arcnautical-landing' }); } catch(_){} 4784 btn.textContent = defaultLabel; btn.disabled = false; 4785 } 4786 return false; 4787 } 4788 4789 async function landingWatchVessel(e, imo) { 4790 e.preventDefault(); 4791 var email = document.getElementById('landing-watch-email').value.trim(); 4792 var btn = document.getElementById('landing-watch-btn'); 4793 btn.textContent = 'Sending...'; btn.disabled = true; 4794 try { 4795 if (window.posthog) { 4796 posthog.identify(window.arcBrowserId, { email: email, lead_source: 'landing_watch_request' }); 4797 posthog.capture('watch_requested_client', { imo: imo, email: email }); 4798 } 4799 } catch(_){} 4800 try { 4801 var resp = await fetch('/api/arcnautical/v1/public/watch-vessel', { 4802 method: 'POST', 4803 headers: { 'Content-Type': 'application/json', 'X-Arc-Browser-Id': (window.arcBrowserId || '') }, 4804 body: JSON.stringify({ email: email, imo: imo }) 4805 }); 4806 var data = {}; try { data = await resp.json(); } catch(_){} 4807 if (!resp.ok || !data.success) throw { status: resp.status, reason: (data && data.error) || 'request_failed' }; 4808 var already = data && data.status === 'already_watching'; 4809 document.getElementById('landing-watch-gate').innerHTML = 4810 '<div style="padding:16px;background:rgba(22,163,74,0.1);border:1px solid rgba(22,163,74,0.3);border-radius:6px">' + 4811 '<div style="color:var(--risk-low);font-weight:600;margin-bottom:4px">' + (already ? 'Already watching this vessel.' : 'Almost there - confirm your watch.') + '</div>' + 4812 '<div style="color:var(--text-sec);font-size:0.85rem">' + (already ? 'You will get an email if its status changes.' : 'Check your inbox at ' + escapeLandingHtml(email) + ' and click the confirm link to activate it.') + '</div>' + 4813 '</div>'; 4814 } catch (err) { 4815 try { window.posthog && posthog.capture('watch_request_failed', { imo: imo, reason: err && err.reason || 'network', status: err && err.status || 0, surface: 'arcnautical-landing' }); } catch(_){} 4816 btn.textContent = 'Watch Vessel'; btn.disabled = false; 4817 } 4818 return false; 4819 } 4820 4821 function escapeLandingHtml(str) { 4822 var div = document.createElement('div'); 4823 div.appendChild(document.createTextNode(String(str || ''))); 4824 return div.innerHTML; 4825 } 4826 </script>
vendor: 1 bytes, line 4826
4826
4827<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"1af6d678532a473c837804ef17df055d","r":1,"spa":2}' crossorigin="anonymous"></script>
4827 4828</body> 4829</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.