Cosmos Atlas — v1 shipped¶
Status: 🟢 LIVE at cosmos.aguidetocloud.com · 8 May 2026 NZST Repo: github.com/susanthgit/cosmos-atlas Cloudflare Pages: project
cosmos-atlas· subdomaincosmos-atlas.pages.devAccount:d42846fe2c29daf890ec57877fda5e04
The atlas is its own planet, with its own atmosphere, its own physics, its own logo set, and its own deploy pipeline. v1 ships a complete, accessible, performant, voice-passed living solar system. This doc is the operational reference for everything in v1.
Atmosphere (locked 8 May 2026)¶
| Token | Value | Why |
|---|---|---|
| Background | #02030E |
Almost-black with cool undertone. Lets per-planet glows pop. |
| HUD chrome | #F2EDE3 |
Apollo-Eno warm white. Distinct from any planet glow. |
| Accent | #FFB347 |
Scientific-instrument amber. Used for focus, active, CTA. |
| Display | Space Grotesk (400/500/600/700) | Geometric, cosmic, distinct from Earth's Inter and Plain AI's Inter. Self-hosted Latin via @fontsource. |
| Mono | JetBrains Mono (400/500) | Already in cosmos via CMD — reuse signals "same universe". |
| Tilt | 38° around screen-x-axis | Gentle but distinct 3D perspective. Confirmed correct after v1.1 eyes-on; the "felt crowded" complaint turned out to be halo bleed + opaque lotus, not tilt. |
| Focal | 900 px | Perspective focal length. Bodies further "back" appear smaller. |
Sun¶
Per cosmos-philosophy, the Sun is the AI co-founder. Invisible pulsing glow only — no glyph, no label. A radial-gradient halo at canvas centre with a pulse cycle of 7 seconds. The Sun does not need a brand mark — it doesn't show up on any planet's surface; every planet exists because it's feeding them.
v1.1 tuning: sun.size dropped 22 → 12 and halo radius multiplier 7× → 5× after live eyes-on showed the warm glow flooding the inner system and bleeding into Earth+Guided.
This was a binary call locked with Sush 8 May. The other defensible option (lotus sigil at centre) was rejected because it would conflate Earth (a planet) with the cosmic source.
Architecture¶
cosmos-atlas/
├── public/
│ ├── favicon.svg ← atlas-internal concentric-rings glyph (placeholder; Phase 4b should replace)
│ ├── planets/
│ │ └── earth-lotus.webp ← vendored from aguidetocloud-revamp/static/images/
│ ├── robots.txt
│ └── sitemap.xml
├── src/
│ ├── components/
│ │ └── PlanetIcon.astro ← 9 icons: 7 cosmos rail + curriculum + mcp
│ ├── data/
│ │ ├── planets.json ← vendored read-only mirror from Earth
│ │ └── atlas.json ← atlas-private: atmosphere + sun + orbits + cards + mcp
│ ├── layouts/
│ │ └── Base.astro ← html shell, meta, fonts, skip-link
│ ├── pages/
│ │ └── index.astro ← SSR semantic HTML + canvas + body overlays + card panel
│ ├── scripts/
│ │ └── cosmos.ts ← canvas controller, 3D math, hover, click, card render
│ └── styles/
│ ├── atmosphere.css ← Deep Cosmos tokens, type, motion, reduced-motion
│ └── cosmos.css ← canvas, HUD, bodies, card, static fallback, list view
├── deploy.mjs ← direct-upload to CF Pages (BLAKE3, no wrangler)
├── astro.config.mjs ← static output, inline stylesheets auto, compress HTML
├── package.json ← dev/build/deploy scripts; @fontsource + @noble/hashes
├── tsconfig.json ← strict
└── README.md ← repo-facing readme
Data shape¶
planets.json (vendored, read-only):
{
"cosmos": { "newsletter_url": "...", ... },
"planets": [
{ "slug": "earth", "name": "A Guide to Cloud", "url": "...", "tagline": "..." },
...
]
}
atlas.json (atlas-private):
{
"atmosphere": { "background": "#02030E", "tilt": 38, "focal": 900, ... },
"sun": { "label": "the source", "size": 22, "pulseSec": 7, ... },
"planets": [
{
"slug": "earth",
"type": "🌍 Home planet",
"atmosphere": "Zen",
"status": "live",
"tagline": "...",
"audience": "...",
"content": "...",
"founder": "...",
"stats": ["56 tools", "66 mind maps", "9 cert paths"],
"orbit": { "radius": 200, "ecc": 0.04, "tilt": 6, "speedSec": 88, "phase": 30 },
"body": { "size": 36, "glowCore": "#A5B4FC", "glowOuter": "#6366F1" },
"moons": [ { "slug": "guided", "name": "Guided", "url": "...", ...orbit + body... } ]
},
...
],
"mcp": {
"slug": "mcp",
"name": "MCP Move",
"url": "https://mcp.aguidetocloud.com/",
"tagline": "...",
"stats": ["agent-readable", "per-planet emerging", "CMD live first"],
"anchor": { "x": 0.86, "y": 0.18, "size": 26, "glowCore": "#FFD89A", "glowOuter": "#FFB347" },
"endpoints": [ { "slug": "...", "name": "...", "url": "...", "status": "live|planned" }, ... ],
"starfield": [ { "x": 0.12, "y": 0.18, "size": 1.5, "twinkleSec": 4.2 }, ... ]
},
"decorativeStars": [ { "x": 0.08, "y": 0.12, "size": 0.6, "alpha": 0.55 }, ... ]
}
The merge of planets.json and atlas.json happens at SSR build time in src/pages/index.astro:
- Planet manifest gives canonical name + url
- Atlas gives atmosphere + orbit + cards
- Merge by slug → composed planets passed to client as JSON in a <script type="application/json"> tag
3D projection — the math¶
Each body's position is computed in two stages:
Stage 1 — orbital plane (xy):
angle = (t / speedSec) × 2π + phase
xp = radius × cos(angle)
yp = radius × sin(angle) × (1 - eccentricity)
// Apply per-orbit tilt rotation in 2D plane
xp_rotated = xp × cos(orbitTilt) - yp × sin(orbitTilt)
yp_rotated = xp × sin(orbitTilt) + yp × cos(orbitTilt)
Stage 2 — 3D tilt + perspective:
const TILT = 38° (in radians)
const FOCAL = 900
yRot = yp × cos(TILT) // y compressed by tilt
zRot = yp × sin(TILT) // y partially becomes z (depth)
scale = FOCAL / (FOCAL + zRot) // perspective scale
screenX = xp × scale
screenY = yRot × scale
depth = zRot // for sort + dim
Each frame, cosmos.ts:
1. Computes orbital positions for all planets + their moons
2. Projects each through the 3D tilt
3. Updates each .planet-body HTML element's transform: translate3d(...) translate(-50%, -50%) scale(...)
4. Sorts by depth → assigns z-index so back planets render behind closer ones
5. Dims back planets via opacity: 0.42 if a planet is focused
6. Canvas redraws orbit ellipses (96-sample projected paths) + sun glow + body halos + starfield
Body overlays vs canvas glow¶
- HTML overlays (
<button class="planet-body">): position + click + keyboard. Contains an inline SVG (or transparent webp for Earth) for the planet's own logo. Native focus + ARIA + tab order. v1.1: every body has an invisible 12px hit-zone via.planet-body::before { inset: -12px }so size-22 moons aren't fiddly to click. - Canvas halos: drawn under each body's screen position. Radial gradient from the planet's own glow colour. v1.1: halo multiplier tightened to 2.2× idle / 3.2× hover (was 3.0×/4.4×) to stop bleed between neighbours; star kind also draws a halo now (was skipped in v1.0); during hover or focus, all other bodies + orbit rings dim to 28-40%.
This split gives: - Crisp logos at any scale (SVG is resolution-independent) - Soft atmospheric glow under each logo (canvas radial gradient) - Native focus rings + keyboard nav (HTML semantics) - Pointer events live on the buttons, not the canvas (no pointer hit-testing in JS)
Camera glide¶
When a planet is clicked:
- cameraTargetX/Y is set to -orbitalPosition of the focused body (so it lands on screen centre)
- cameraTargetZoom is 1.85 desktop / 1.5 mobile
- updateCamera() lerps the camera each frame: current += (target - current) × deltaSec × 4.5
- The orbit ring of the focused body highlights amber; others dim
- Camera glide takes ~250-400ms feel (depends on lerp factor)
When the card closes (Esc / X / click empty area), camera returns to origin (lerp factor reused).
Card panel¶
Slides in from the right (or up from bottom on mobile <720px) when a body is opened. Contents: 1. Plain AI 🌱 free forever firewall badge (only for Plain AI + Curriculum) 2. Hero block: 80px planet logo with halo on the left (halo tinted to the planet's own glow), name + tagline + type/atmosphere/status badges on the right 3. Stats chip row: 3 factual content counts per planet 4. Audience block: ≤18 words, "Who it's for" 5. Content block: ≤22 words, "What you'll find" 6. Founder note: ≤22 words, italic, with left rule (Sush's voice fingerprints) 7. Visit CTA: amber button → planet's URL 8. Moons (if any): inline mini-cards with their own logo + tagline + content + founder + visit 9. For MCP: extra Endpoints list at the bottom (live + planned)
Card focus management: opening a card moves screen-reader focus to #card-name. Esc closes + restores focus to the body element that was clicked.
Voice — every word voice-passed¶
| Layer | Constraint |
|---|---|
| Card name | 1-3 words |
| Tagline | 5-9 words |
| Audience | ≤18 words |
| Content | ≤22 words |
| Founder | ≤22 words, in Sush's voice fingerprints |
| Stats chips | 1-3 words each, factual content counts only (no boasting) |
| Moon notes | 1 sentence each |
| Total card body | ≤100 words |
Forbidden words enforced cosmos-wide: ecosystem · frontier · vendor · agentic-as-adjective · robust · scalable · AI-powered · world-class · best-in-class · game-changer · synergies · holistic · mission-critical · empowering · enabling · in layman's terms.
Sush voice fingerprints used: - Earth founder: "In my head, I'm still the curious intern. What overflows lands here." - Guided founder: "...Honest take? It still costs me to run." - Plain AI founder: "If I can't explain it to my mum at the dinner table, I haven't understood it yet."
A11y¶
| Surface | What's there |
|---|---|
| Skip-link | "Skip to planet list" — first focusable element, jumps to #static-shell |
| Canvas | aria-label on <canvas>, role="presentation" (decorative) |
| Body buttons | Native <button>, aria-label="${name} · ${tagline}", focus rings, Tab/Shift-Tab navigate, Enter opens |
| Hover label | role="status" aria-live="polite" so screen readers announce the hovered/focused body |
| Card | Focus moves to card name on open; Esc closes + restores focus |
| Reduced motion | Orbits stop; transitions instant; bodies hold at phase 0 |
| Static fallback | Full semantic HTML, every planet, every moon, MCP — role="list" on planet list |
| List view toggle | HUD button toggles between cosmos and static list |
SEO¶
| Surface | What's there |
|---|---|
<title> |
"Cosmos Atlas — A Guide to Cloud" |
<meta description> |
One-sentence cosmos summary |
| Canonical | https://cosmos.aguidetocloud.com/ |
| Open Graph | type=website, title, description, og:image (placeholder, see v2 roadmap), site_name |
| Twitter Card | summary_large_image |
| JSON-LD | WebSite with hasPart listing all 7 planets + 2 moons + MCP |
| Sitemap | /sitemap.xml (just / for now — atlas is one page) |
| Robots | Allow all + sitemap reference |
Performance budget¶
| Asset | Size | Note |
|---|---|---|
| HTML | 36 KB | inline SVG icons render twice (canvas overlay + static fallback) |
| CSS bundle | 21 KB | atmosphere + cosmos + 6 @font-face declarations |
| JS bundle | 13 KB (gz 4.7 KB) | cosmos controller + entrypoint |
| Earth lotus webp | 12 KB | v1.1: chroma-keyed transparent version (was 11 KB opaque navy-bg sticker that read as a square on the cosmos). Sharp + pngjs pipeline; 92.4% of pixels alpha-keyed against #242830 ± 22-60. |
| Fonts (latin-only woff2) | ~94 KB | Space Grotesk × 4 weights + JetBrains Mono × 2 weights |
| Favicon SVG | 1.2 KB | placeholder concentric rings |
| Total transferred | ~175 KB | under 250 KB budget ✅ |
Deploy¶
| Step | Command |
|---|---|
| Local dev | npm run dev → http://localhost:4287 |
| Build | npm run build → dist/ |
| Deploy | npm run deploy → builds + uploads to CF Pages |
The deploy script (deploy.mjs) uses Cloudflare's direct-upload Pages API:
1. BLAKE3-hash every file in dist/
2. Get upload JWT from /pages/projects/cosmos-atlas/upload-token
3. Check-missing → upload only new assets in batches of 20
4. POST /deployments with manifest
This bypasses wrangler pages deploy because wrangler's workerd dependency has no Windows ARM64 binary.
Token resolution (in order): CLOUDFLARE_API_TOKEN env var → ~/.copilot/secrets/cloudflare-api-token file.
Cosmos contracts¶
| Contract | Status |
|---|---|
Reads planets.json |
✅ Vendored read-only copy from Earth |
Owns atlas.json |
✅ Atlas-private |
| Reads each planet's MCP endpoint manifest | ⏳ Hardcoded in atlas.json — should pull dynamically in v2 |
| Receives back-link from each planet's footer | ⏳ Phase 12 — deferred until parallel voice/logo/nav/parity sessions land |
Atlas joins planets.toml (cosmos-audit guard #4) |
⏳ Follow-up cross-planet session |
Provenance¶
| Date | Event | Commit |
|---|---|---|
| 8 May 2026 (am) | Vision drafted, three text-heavy mockups rejected | session 2d6287c4 |
| 8 May 2026 (afternoon) | Atmosphere call with Sush; Phase 0 + 1 (scaffold + data + voice-passed cards) | 658ab59 |
| 8 May 2026 (afternoon) | Phase 2 (real planet logos + 3D tilt + richer cards) | e9e8583 |
| 8 May 2026 (evening) | Self-hosted fonts via @fontsource | f0042bc |
| 8 May 2026 (evening) | Direct-upload deploy script | 1be8c63 |
| 8 May 2026 (evening) | First production deploy + DNS + custom domain | LIVE on cosmos.aguidetocloud.com |
| 8 May 2026 (evening) | v1.1 polish — eyes-on tuning, transparent lotus, mobile auto-list, hover-pause + hover-dim + hit-zone | 98a5f27 |
v1.1 polish (8 May 2026, evening)¶
After v1.0 went live, Sush's first browser-pass surfaced "because of the tilt it felt crowded and overlapped." Playwright screenshots (desktop 1440 + 1920, tablet 820, mobile 390, reduced-motion) revealed three real issues — none of them actually the tilt:
| # | What | Root cause |
|---|---|---|
| 1 | Earth lotus rendered as a navy square against the cosmos | earth-lotus.webp was VP8 lossy with no alpha channel. Bg #242830 was baked in. |
| 2 | Earth + Guided moon halos merged into one blob | Moon orbit r=56 + Earth body 36 + Guided body 22 + halos 3.0× → halos collided ~8px |
| 3 | Mobile portrait clumped all 6 planets + 2 moons into one rainbow blob | Canvas mode rendered at all viewport widths; no list-view auto-fallback |
Plus secondary crowding: sun glow flooding the centre, Plain AI cyan halo eating the upper-right quadrant, MCP star looking tiny next to Plain AI, planets' click-targets fiddly on small bodies, and orbits continuing during interaction making clicks hard.
Fixes shipped in commit 98a5f27:
| Layer | Change |
|---|---|
atlas.json sun |
size 22 → 12 |
atlas.json orbits |
Earth 200 (kept), CMD 290→320, Shift 370→410, Plain AI 450→480, Agentic 540→620, Claw 620→720; eccentricities tightened |
atlas.json moons |
Guided radius 56→75, Curriculum 60→70 |
atlas.json MCP anchor |
(0.86, 0.18) size 26 → (0.92, 0.10) size 32 |
cosmos.ts halo |
Body multiplier 4.4×/3.0× → 3.2×/2.2×; sun halo radius 7× → 5×; star kind now renders halo too |
cosmos.ts hover |
Other bodies + orbit rings dim to 28-40% on hover (was focus-only); orbits pause on hover/focus via simT/realT split (twinkles + sun pulse stay live on realT) |
cosmos.ts mobile |
Auto-engage list view if window.innerWidth < 600 on mount; "Cosmos View" HUD button still works as override |
cosmos.css hit-zone |
.planet-body::before { inset: -12px; pointer-events: auto } — invisible 12px halo around each body |
public/planets/earth-lotus.webp |
Transparent version (chroma-keyed via sharp + pngjs, 256×256, 12 KB) |
Reusable pattern: the chroma-key script is in ~/.copilot/session-state/061c101c-.../files/chroma-key.mjs. Useful any time a brand asset has a baked-in background. Distance metric is RGB Euclidean against the bg colour with a soft band (hard-zero at distance ≤22, full-opaque at ≥60, linear ramp in between). Preserves anti-aliased stroke edges.
What this taught us: the perceived "crowding" was mostly halo bleed + opacity issues + tight inner-orbit spacing — NOT the tilt. v2-roadmap eyes-on item #1 (try 30° / 45°) is now de-prioritised. 38° is the right tilt for this cosmos.