← DEVLOG
Interactive2025.09.045 min read

Cosmos — Coding the Feeling of Space

A healing page that lets you experience the stillness of space in a browser — CSS star fields, Canvas nebulae, and Web Audio synthesized BGM

canvasambientcss-animationbgmhealing

The Goal — A Space Where Nothing Is Required

Most interactive content asks something of you. Click here, score points, clear a level.

Cosmos aims for the opposite: a space where you can just open the page and feel like you're inside the universe. No interaction required.


SpaceBackground — CSS-Based Star Field

The background stars are implemented with CSS animation rather than Canvas. The CPU overhead is lower, and the browser automatically throttles animations when the tab is inactive.

The StarField component generates hundreds of <span> elements, each with a random position and blink timing injected as CSS variables.

// Per-star inline style
style={{
  '--star-x':       `${Math.random() * 100}vw`,
  '--star-y':       `${Math.random() * 100}vh`,
  '--star-delay':   `${Math.random() * 8}s`,
  '--star-opacity': `${0.3 + Math.random() * 0.7}`,
} as CSSProperties}

MeteorEffect is a separate layer that occasionally sends a diagonal streak across the screen. Opacity and transform are both driven by @keyframes for smooth trails.


Canvas Nebula Rendering

Nebula light is built by layering multiple semi-transparent radialGradient passes. A single gradient cannot reproduce the uneven, diffuse glow of a real nebula.

const drawNebula = (ctx: CanvasRenderingContext2D, cx: number, cy: number) => {
  // 3–5 layers, each with different offsets, radii, colors, and alpha
  NEBULA_LAYERS.forEach(({ dx, dy, rx, ry, color, alpha }) => {
    const grad = ctx.createRadialGradient(cx + dx, cy + dy, 0, cx + dx, cy + dy, rx);
    grad.addColorStop(0, `${color}${Math.round(alpha * 255).toString(16)}`);
    grad.addColorStop(1, 'transparent');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.ellipse(cx + dx, cy + dy, rx, ry, 0, 0, Math.PI * 2);
    ctx.fill();
  });
};

Nebula colors reference real nebula photography: hydrogen red, oxygen teal, and sulfur gold.


Global BGM — Uninterrupted Across Page Navigation

The BGM is managed at layout.tsx level through GlobalBGMContext. Music continues seamlessly when navigating between pages.

// layout.tsx
<GlobalBGMProvider>
  <SearchProvider>
    {children}
  </SearchProvider>
</GlobalBGMProvider>

Game pages call usePageBGMMode('page') to suppress the main BGM during gameplay. Cosmos makes no such call, so the BGM plays on undisturbed.

The BGM itself is synthesized entirely with Web Audio API. Two low-frequency drone oscillators with a slow LFO modulation create the endless resonance of open space.

// Drone layer: subtle interference between two close frequencies
const osc1 = ctx.createOscillator();
osc1.frequency.value = 55;
const osc2 = ctx.createOscillator();
osc2.frequency.value = 55.7; // 0.7Hz apart

// LFO: volume oscillates between 0.6 and 1.0 on an 8-second cycle
const lfo = ctx.createOscillator();
lfo.frequency.value = 0.125; // 8-second period

The 0.7Hz frequency difference produces a natural beating effect — a slow pulse that feels like the universe breathing.


OrbitCaption — Typewriter Quotes About Space

At the bottom of the screen, sentences appear one character at a time. The OrbitCaption component handles this as a sequential chain.

Each caption follows the cycle: typing → visible → fade out → gap → next caption. It plays once and stops; re-entering the page restarts the sequence from the beginning.

// Timing constants
const REVEAL_SPEED_MS = 55; // milliseconds per character
const DISPLAY_MS = 6000; // hold time after full reveal
const FADE_MS = 1500; // fade-out duration
const BETWEEN_MS = 1000; // gap before next caption

Long sentences use \n for line break control. white-space: pre-wrap on the caption element renders them exactly as written.


Closing Thoughts

Space is silent — there is no medium to carry sound. And yet, when building a browser representation of space, I added sound anyway. A drone BGM, twinkling stars, meteor trails.

Knowing it isn't real doesn't change the feeling. Watching stars while listening to the ambient hum — something about it feels genuine. Atmosphere comes from density, not accuracy.

A space where nothing is required. That was the whole point.

Content related to this post

Try it yourself