JK
>CARGANDO // CARGANDO
INICIALIZANDO VISTAVisualización 3D WebGL del planeta Tierra de alto rendimiento con shaders atmosféricos y marcadores geográficos interactivos.
Renderizar cuerpos planetarios realistas en el navegador sin pérdida de fotogramas en dispositivos de gama media y baja.
| Tecnología | Versión | Propósito | Por qué se eligió |
|---|---|---|---|
| React | ^19.0.1 | UI runtime for both entries | Component model; canvas work stays outside React and is driven through a ref |
| TypeScript | ^7.0.2 | Types for the whole project | Only static gate in CI (lint = tsc --noEmit) with strict mode on |
| Vite | ^8.3.0 | Build tool and dev server | Fast builds with dual entry via rollupOptions.input |
| Tailwind CSS | ^4.3.3 | Utility styling | No config file; @theme tokens in index.css |
| d3-geo | ^3.1.1 | Projection maths only | geoOrthographic/geoPath/graticule without committing to a renderer |
| topojson-client + world-atlas | ^3.1.0 / ^2.0.2 | Country polygons | Free, offline, no API key; India's entry is replaced by official vectors |
| lucide-react | ^0.546.0 | Icons | Tree-shakeable icon set |
| @vercel/analytics | ^2.0.1 | Pageview analytics | Zero-config on Vercel |
| Vitest | ^5.0.4 | Unit tests | Fast, node environment - everything under test is pure logic |
| pnpm | 10.33.2 (pinned) | Package manager | Deterministic lockfile in CI |
| GitHub Actions | ubuntu-latest, Node 22 | CI | Native to the repo host; lint -> test -> build gates |
| Vercel | — | Static hosting and CDN | Static assets + headers + rewrites with no server |
| OSM Nominatim | public API | Geocoding search | Free and keyless |
| Método | Ruta | Propósito |
|---|---|---|
| GET | / | Main app SPA - customizer, search, embed generator (rewritten to /index.html) |
| GET | /embed | Chrome-free iframe target; rewritten to /embed.html and served with CSP frame-ancestors * |
| GET | /embed.html | Same embed entry, served directly; configured by URL query params |
| Método | Ruta | Propósito |
|---|---|---|
| GET | /search | Resolve a place name to lat/lng for the search pin; debounced, abortable, ~1 req/s budget with a 30 s cooldown on 429 |
Decisión: index.html and embed.html are built from one config and both render the same EarthGlobe component
Motivo: The embed must never diverge visually or behaviourally from the app; a second renderer would double the surface area of every fix
Beneficios: One renderer, every fix reaches both surfaces
Compensaciones: The embed downloads saved-settings branches it never runs (~3.5 kB, ~0.3 kB gzipped)
Decisión: Hand-written Canvas 2D painter with d3-geo used only for projection maths
Motivo: Zero backend, zero keys, static-only hosting - a hand-painted orthographic globe at 110m resolution does not need a GPU stack
Beneficios: Negligible library weight, no shader or context-loss failure modes, full control of the drawing passes
Compensaciones: Per-frame CPU scales with geometry (177 country + 37 state polygons every frame); no LOD, culling or instancing
Decisión: Filter ISO 356 out of world-atlas and draw India from india-official.json (37 features incl. J&K and Ladakh)
Motivo: Natural Earth's India does not match official boundaries, and cartographic accuracy should not require a tile server
Beneficios: Authoritative borders, fully offline, no map service at runtime
Compensaciones: ~+210 KB payload; TopoJSON quantisation was rejected because it changes cartographic fidelity
Decisión: Fixed parameter order in buildEmbedUrl, plus a round-trip test that parses generated URLs back through parseGlobeConfig
Motivo: A pasted embed snippet is a public API surface that cannot change
Beneficios: A silent behavioural break becomes a red build; the CI workflow flags the round-trip test as load-bearing
Compensaciones: Genuine awkwardness is preserved to keep the byte format stable (e.g. DEFAULT_APP_ZOOM exists only to make a non-behaviour visible)
Decisión: Coerce-then-clamp instead of strict typing; id optional, numeric strings accepted, longitude wraps, zoom clamps
Motivo: Hosts send string coordinates, out-of-range zoom or a marker without id - a typeof check would reject those and silently stop the globe moving
Beneficios: Existing embeds keep working; only genuinely unusable payloads are rejected, with a commandRejected event
Compensaciones: Hand-written and only partly uniform; 3 of 20 commands still fail silently
Decisión: Require event.source === window.parent, with EMBED_ALLOWED_PARENT_ORIGINS shipping empty and outbound events targeting the referrer origin
Motivo: Embedding is a public, unauthenticated capability - the realistic threat is a third party messaging an embed already on screen
Beneficios: Blocks pop-up/opener/sibling-frame attacks at zero cost to embedders
Compensaciones: Any site that iframes the globe has full script control; acceptable because nothing secret is transmitted
Decisión: Diff writes field-by-field against a last-known ref, merge them over a fresh disk read, skip unchanged writes, and reconcile on the storage event
Motivo: Whole-blob last-write-wins destroyed a sibling tab's unrelated fields and fired spurious events
Beneficios: Correct multi-tab behaviour; a slider drag collapses into a single serialisation
Compensaciones: A ~45-value hook return, three parallel write effects, no schema version for forward migration
Decisión: background=false substitutes pure white/black instead of clearing to transparent; every frame is fully painted
Motivo: Removes clearRect, avoids compositing cost and letterboxing, and makes the theme halo legible
Beneficios: Simpler and cheaper frames, predictable look across hosts
Compensaciones: The globe cannot be composited over a host page background - a real limitation for embeds
Decisión: Inertia 0.93^(dt*60), auto-rotation speed*dt*10, wheel zoom 1.08 in / 1/1.08 out
Motivo: Flat velocity decay behaves differently at 60 vs 120 Hz and asymmetric zoom steps are not reversible
Beneficios: Identical feel on 60 Hz, 120 Hz and ProMotion displays
Compensaciones: Constants are scattered across three sites rather than shared