Skip to content

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 · subdomain cosmos-atlas.pages.dev Account: 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 builddist/
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.