ATMOSPHERE8K
Live Wind Visualization

Complete Build Prompt

Copy this document into Rocket.new. Your live wind globe builds in one pass. Every file included. Zero guesswork.

COPY → PASTE → BUILD
How to Use
1Copy everything between the START PROMPT and END PROMPT markers below
2Paste into Rocket.new and send as a single message
3Complete the 3 setup steps at the end of this document
4Click Launch in the Rocket builder top-right corner → Netlify → done
Critical — Data Source
NOAA terminated OpenDAP on February 23 2026. Never use nomads.ncep.noaa.gov/dods/ URLs. This prompt uses Open-Meteo — real GFS model data, JSON format, no parsing required.

Build a project called Atmosphere8K. A full-screen live global wind visualization globe. Follow every specification exactly. Do not add features not listed. Do not use libraries not listed.

USE: Next.js 15 App Router TypeScript · HTML5 Canvas (two stacked canvases) · D3.js geoOrthographic · TopoJSON world-atlas · Open-Meteo API · Netlify Blobs · Inline styles only
DO NOT USE: Three.js · WebGL · react-globe.gl · any globe library · Tailwind · GRIB2 parsers

package.jsoncomplete
{
  "name": "atmosphere8k",
  "version": "1.0.0",
  "private": true,
  "scripts": { "dev": "next dev", "build": "next build", "start": "next start" },
  "dependencies": {
    "next": "^15.0.0",
    "react": "^18.3.0",
    "react-dom": "^18.3.0",
    "d3": "^7.9.0",
    "d3-geo": "^3.1.1",
    "topojson-client": "^3.1.0",
    "@netlify/blobs": "^8.1.0"
  },
  "devDependencies": {
    "@types/d3": "^7.4.3",
    "@types/topojson-client": "^3.1.4",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "typescript": "^5"
  }
}
app/globals.csscomplete
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000a1a; }
canvas { display: block; }
app/layout.tsxcomplete
import type { Metadata, Viewport } from 'next';
import './globals.css';
export const metadata: Metadata = {
  title: 'Atmosphere8K',
  description: 'Live Wind Visualization',
};
export const viewport: Viewport = {
  width: 'device-width', initialScale: 1, maximumScale: 1, userScalable: false,
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return <html lang="en"><body>{children}</body></html>;
}
app/page.tsxcomplete
import Globe from '@/components/Globe';
export default function Page() { return <Globe />; }
app/api/wind/route.tscritical — do not modify
import { NextResponse } from 'next/server';
import { getStore } from '@netlify/blobs';
export const runtime = 'nodejs';
const NX = 72, NY = 37, DX = 5, DY = 5, LAT1 = 90, LON1 = 0;
const TTL = 6 * 60 * 60 * 1000;
let memCache: { ts: number; data: string } | null = null;

function buildGrid() {
  const lats: number[] = [], lons: number[] = [];
  for (let j = 0; j < NY; j++) {
    for (let i = 0; i < NX; i++) {
      lats.push(+(LAT1 - j * DY).toFixed(1));
      const raw = LON1 + i * DX;
      // CRITICAL: Open-Meteo rejects longitude above 180
      lons.push(+(raw > 180 ? raw - 360 : raw).toFixed(1));
    }
  }
  return { lats, lons };
}

function dirToUV(speed: number, dir: number): [number, number] {
  const r = dir * (Math.PI / 180);
  return [-speed * Math.sin(r), -speed * Math.cos(r)];
}

async function fetchBatch(lats: number[], lons: number[]) {
  const url = 'https://api.open-meteo.com/v1/forecast' +
    '?latitude=' + lats.join(',') +
    '&longitude=' + lons.join(',') +
    '¤t=wind_speed_10m,wind_direction_10m&wind_speed_unit=ms';
  const res = await fetch(url, { signal: AbortSignal.timeout(30000) });
  if (!res.ok) throw new Error('Open-Meteo ' + res.status);
  const json = await res.json();
  const locs = Array.isArray(json) ? json : [json];
  return locs.map((l: any) => ({
    speed: l?.current?.wind_speed_10m ?? 0,
    dir:   l?.current?.wind_direction_10m ?? 0,
  }));
}

export async function GET() {
  try {
    if (memCache && Date.now() - memCache.ts < TTL)
      return NextResponse.json(JSON.parse(memCache.data));
    try {
      const store = getStore('wind-data');
      const cached = await store.getWithMetadata('wind-current');
      if (cached?.metadata?.ts && Date.now() - Number(cached.metadata.ts) < TTL)
        return NextResponse.json(JSON.parse(cached.data as string));
    } catch {}
    const { lats, lons } = buildGrid();
    const mid = Math.ceil((NX * NY) / 2);
    const r1 = await fetchBatch(lats.slice(0, mid), lons.slice(0, mid));
    await new Promise(r => setTimeout(r, 5000));
    const r2 = await fetchBatch(lats.slice(mid), lons.slice(mid));
    const all = [...r1, ...r2];
    const data = {
      header: { la1: LAT1, lo1: LON1, la2: -LAT1, lo2: 355,
                dx: DX, dy: DY, nx: NX, ny: NY },
      uData: all.map(({ speed, dir }) => dirToUV(speed, dir)[0]),
      vData: all.map(({ speed, dir }) => dirToUV(speed, dir)[1]),
    };
    const json = JSON.stringify(data);
    memCache = { ts: Date.now(), data: json };
    try {
      const store = getStore('wind-data');
      await store.set('wind-current', json, { metadata: { ts: Date.now() } });
    } catch {}
    return NextResponse.json(data);
  } catch {
    return NextResponse.json({ error: 'Wind data unavailable' }, { status: 503 });
  }
}
components/Globe.tsx — color themes + key functionsverbatim
// Color themes — exact RGB stop values required
const COLOR_THEMES = {
  classic: [[0,[17,110,187]],[3,[44,183,168]],[7,[76,196,123]],
            [14,[170,213,57]],[21,[253,214,43]],[28,[240,124,26]],
            [40,[212,55,21]],[63,[130,0,40]]],
  ocean:   [[0,[0,20,60]],[5,[0,60,120]],[12,[0,110,180]],
            [20,[0,170,220]],[30,[50,210,240]],[63,[220,250,255]]],
  fire:    [[0,[10,0,30]],[2,[60,0,10]],[5,[150,20,0]],
            [10,[210,70,0]],[18,[250,140,0]],[30,[255,210,0]],[63,[255,255,255]]],
  neon:    [[0,[0,0,50]],[4,[30,0,120]],[8,[0,50,220]],
            [14,[0,200,255]],[20,[0,255,100]],[30,[200,255,0]],[63,[255,0,150]]],
  aurora:  [[0,[5,10,30]],[4,[0,60,80]],[8,[0,120,100]],
            [14,[0,200,150]],[22,[100,230,200]],[35,[180,160,255]],[63,[255,200,255]]],
  mono:    [[0,[15,15,20]],[5,[55,58,68]],[12,[110,115,128]],
            [20,[170,175,188]],[30,[210,215,228]],[63,[255,255,255]]],
};

function windColor(speed: number, theme: string): string {
  const stops = COLOR_THEMES[theme] || COLOR_THEMES.classic;
  if (speed <= 0) return `rgb(${stops[0][1].join(',')})`;
  for (let i = 1; i < stops.length; i++) {
    const [s0, c0] = stops[i-1], [s1, c1] = stops[i];
    if (speed <= s1) {
      const t = (speed - s0) / (s1 - s0);
      return `rgb(${~~(c0[0]+t*(c1[0]-c0[0]))},${~~(c0[1]+t*(c1[1]-c0[1]))},${~~(c0[2]+t*(c1[2]-c0[2]))})`;
    }
  }
  const last = stops[stops.length-1][1];
  return `rgb(${last.join(',')})`;
}

function interpolateWind(wind: WindData, lon: number, lat: number) {
  const { lo1, la1, dx, dy, nx, ny } = wind.header;
  const i  = ((lon - lo1 + 360) % 360) / dx;
  const j  = (la1 - lat) / dy;
  if (j < 0 || j > ny - 1) return null;
  const fi = Math.floor(i) % nx, ci = (fi + 1) % nx;
  const fj = Math.min(Math.floor(j), ny - 2), cj = fj + 1;
  const tx = i - Math.floor(i), ty = j - fj;
  const idx = (r: number, c: number) => r * nx + c;
  const lerp = (a: number[]) =>
    (1-tx)*(1-ty)*a[idx(fj,fi)] + tx*(1-ty)*a[idx(fj,ci)] +
    (1-tx)*ty*a[idx(cj,fi)] + tx*ty*a[idx(cj,ci)];
  const u = lerp(wind.uData), v = lerp(wind.vData);
  return isNaN(u)||isNaN(v) ? null : [u, v] as [number,number];
}
components/Globe.tsx — build specification
Setup Steps
1
Download world map
TopoJSON world atlas for country borders and coastlines.
curl -o public/world-110m.json https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json
2
Netlify Blobs wind cache
Deploy to Netlify. Blobs are automatically available — no configuration needed. The /api/wind route caches fetched wind data in a wind-data blob store for 6 hours.
3
Deploy
Click Launch in the Rocket builder top-right corner, connect Netlify, and deploy. The app is fully static-compatible with one serverless API route.

Built with Rocket.new · NOAA GFS wind data via Open-Meteo · D3.js geoOrthographic projection

ATMOSPHERE8K · LIVE WIND VISUALIZATION