Skip to content

Design

Files

  • site/static/: index.html (the map), about.html, support.html (behind "apoie"/Donate), 404.html (forwards old marola.dev/docs/* links to docs.marola.dev), favicon.svg.
  • site/static/app.js: fetches data/areas.json and each area's board, builds the map (Mapbox GL JS), the beach markers, the list, the card, the hour slider and the satellite/NASA GIBS raster layers. ui.js: language resolution and t() (MIP-0054). flow.js: the WebGL wind/wave particle layer, a Mapbox GL custom layer interpolated from the board's own readings (no weather grid fetched). i18n.js: generated by scripts/i18n_bundle.py from site/i18n/*.json — edit the catalogs, never this file.
  • site/static/mapbox-config.js: empty in the repo (window.MAROLA_MAPBOX = { token: "", style: "" }); site.yml rewrites it at deploy from MAPBOX_PUBLIC_TOKEN/MAPBOX_STYLE (scripts/mapbox_config.sh). site/static/chatbot-config.js and chat.js: the chat widget (the chat widget).
  • site/static/vendor/: vendored third-party assets, no build step (libraries).
  • site/areas.json: the areas the boards are built for (reference). site/fixtures/: the board the test harness (scripts/site_check.js) renders, plus a schema fixture. site/board.schema.json: the board contract, vendored from the pinned image (reference).
  • site/i18n/: pt-BR.json (source locale), en.json (translated), context.json (a note per key); scripts/i18n_bundle.py checks and bundles them (development).

The board, beach by beach, and markers

app.js's colour()/band() turn each beach's score into one of five bands: no data (score null/undefined), unfit (unfit or score <= 0), 1–39, 40–69, or ≥70 — the same bands the hour bar's legend shows. A map marker is a 16 px SVG dot (waveIcon) with a white ring, the band's colour (DESIGN.md's --c70…--cna tokens) written straight into its fill attribute; the selected beach grows to 32 px and shows its score. Everywhere else the band is a CSS class, never inline style= (which the CSP below blocks): list/card score chips use .c70/.c40/.c1/.c0/.cna; the water layer's sampling-point markers use wpoint c0/wpoint cna/wpoint c70; a beach's tooltip and its card's inline water-status dot use wdot c0/wdot cna/wdot c70 (waterDotClass). The water layer draws every sampling point in the area, in the same three bands (proper, improper, unclassified).

CSP and the no-third-party rule

index.html's page keeps default-src 'self'; img-src 'self' data: blob: https:; connect-src 'self' https:; worker-src 'self'; object-src 'none' (about.html/support.html, which carry no map, drop connect-src/worker-src: default-src 'self'; img-src 'self' https:; object-src 'none'). script-src has no override, so it inherits default-src 'self': every script is same-origin, including Mapbox GL JS's own worker (vendor/mapbox-gl-csp.js is the CSP build, vendor/mapbox-gl-csp-worker.js loads from vendor/, not a blob:, which the non-CSP build needs).

The page's only two third-party origins (AGENTS.md's Code style section) are:

  • Mapbox: the base map (style, tiles, fonts), billed per map load above its free tier once a token is set (mapbox-config.js, the tile policy).
  • NASA GIBS: the four satellite raster layers (clouds, sea-surface temperature, anomaly, El Niño), free and keyless, fetched only once a visitor picks one of those layers (app.js's renderRaster).

img-src … https: and blob: are how Mapbox GL decodes map imagery; connect-src … https: lets the page reach Mapbox's tile/style/event endpoints and, when set, the chat widget's own endpoint (chatbot-config.js). No other third-party script, font or analytics origin is loaded — Inter is self-hosted (vendor/fonts/), not pulled from a font CDN.