# ![ASCII-Globe](https://github.com/jcubic/ascii-globe/blob/master/.github/logo.svg?raw=true)

[![npm](https://img.shields.io/badge/npm-0.5.0-yellow.svg)](https://www.npmjs.com/package/ascii-globe)
[![github repo](https://img.shields.io/badge/github-repo-orange?logo=github)](https://github.com/jcubic/ascii-globe)
![NPM Downloads](https://img.shields.io/npm/dm/ascii-globe)
[![jsDelivr hits (npm)](https://img.shields.io/jsdelivr/npm/hm/ascii-globe)](https://www.jsdelivr.com/package/npm/ascii-globe)
[![LICENSE MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jcubic/ascii-globe/blob/master/LICENSE)

Zero dependencies isomorphic ASCII-Art globe renderer in JavaScript.

See [Live Demo](https://codepen.io/jcubic/full/EaNaRVp)

## Installation

### npm

```bash
npm install ascii-globe
```

### CDN

```html
<!-- IIFE (global variable) -->
<script src="https://cdn.jsdelivr.net/npm/ascii-globe"></script>

<!-- ES Module -->
<script type="module">
import Globe from 'https://esm.run/ascii-globe';
</script>
```

## Usage

### ES Module (Node.js / Vite / bundlers)

```javascript
import Globe from 'ascii-globe';

const globe = new Globe({ size: 1.4 });
console.log(globe.render(90));
```

### CommonJS (Node.js)

```javascript
const Globe = require('ascii-globe');

const globe = new Globe({ size: 1.4 });
console.log(globe.render(90));
```

### Browser (script tag)

```html
<pre id="output"></pre>
<script src="https://cdn.jsdelivr.net/npm/ascii-globe"></script>
<script>
var globe = new Globe({ size: 1 });
document.getElementById('output').textContent = globe.render(0);
</script>
```

### Browser (ES Module)

```html
<pre id="output"></pre>
<script type="module">
import Globe from 'https://esm.run/ascii-globe';

const globe = new Globe({ size: 1 });
document.getElementById('output').textContent = globe.render(0);
</script>
```

## API

### `new Globe(options?)`

Creates a new globe instance.

| Option         | Type     | Default | Description                                         |
|----------------|----------|---------|-----------------------------------------------------|
| `size`         | `number` | `1.4`   | Scale factor. `1` produces a 120x60 character grid. |
| `map`          | `string` | —       | Base64-encoded map data. Defaults to the built-in Earth map. |
| `land`         | `string` | `'#'`   | Character used to render land masses.               |
| `water`        | `string` | `'-'`   | Character used to render water/ocean.               |
| `background`   | `string` | `' '`   | Character used for the area outside the globe disk. |
| `margin`       | `number` | `0`     | Number of characters around the globe disk.         |
| `marginBlock`  | `number` | `0`     | Vertical margin (overrides `margin`).               |
| `marginInline` | `number` | `0`     | Horizontal margin (overrides `margin`).             |
| `padding`      | `number` | `0`     | Thickness (in characters) of a circular gap between the globe and the border. |
| `border`       | `string` | `'#'`   | Character used to draw the circular border/glow ring in the default (non-`format`) renderer. Only has an effect when `borderWidth` is set. |
| `borderWidth`  | `number` | `0`     | Thickness (in characters) of the border ring. This is what turns the ring on — `border` alone does nothing. |
| `pin`          | `string` | `'@'`   | Default character for location pins. Can include ANSI escape codes. |
| `pinSize`      | `number` | `1`     | Default size multiplier for pin markers.            |
| `pins`         | `Pin[]`  | `[]`    | Array of pin locations.                             |
| `tilt`         | `number` | `0`     | Axial tilt in degrees (Earth's tilt is 23.5°).      |
| `speed`        | `number` | `0.7`   | Rotation speed in degrees per frame.                |
| `format`       | `function` | —     | Callback `(type, length) => string` for custom output (see below). |

#### Pin object

| Property | Type     | Required | Description                                                        |
|----------|----------|----------|--------------------------------------------------------------------|
| `lat`    | `number` | yes      | Latitude in degrees (-90 to 90).                                   |
| `long`   | `number` | yes      | Longitude in degrees (-180 to 180).                                |
| `char`   | `string` | no       | Override character for this pin. Can include ANSI escape codes.    |
| `size`   | `number` | no       | Size multiplier for this pin (overrides `pinSize`).                |

#### `format(type, length)`

When provided, `render()` calls this function for each run of consecutive cells of the same type instead of using the `land`/`water`/`background`/`border`/`pin` characters. This lets you wrap output in HTML tags, ANSI codes, or any other markup.

Cells are numbered outward-in: background is always `0`, the border ring and padding ring (when enabled) take the next slots in that order, then water, land, and pins. The border ring is enabled by `borderWidth > 0` — the `border` character is only used by the default (non-`format`) renderer, so with `format` you don't need to set it at all. Padding is enabled by `padding > 0`.

| `borderWidth > 0` | `padding > 0` | Type values                                              |
|--------------------|----------------|-----------------------------------------------------------|
| no                  | no             | `0` Background, `1` Water, `2` Land, `3+` Pin              |
| yes                 | no             | `0` Background, `1` Border, `2` Water, `3` Land, `4+` Pin   |
| no                  | yes            | `0` Background, `1` Padding, `2` Water, `3` Land, `4+` Pin  |
| yes                 | yes            | `0` Background, `1` Border, `2` Padding, `3` Water, `4` Land, `5+` Pin |

For the pin case, the index into the `pins` array is `type - pinsStartType`, where `pinsStartType` is the last number in the row above (e.g. `3` in the first row, `5` in the last).

Example — a colored glow using `format`, without setting a `border` character:

```javascript
const globe = new Globe({
  size: 1,
  padding: 1,
  borderWidth: 1,
  format(type, length) {
    const chars = [' ', '*', ' ', '-', '#'];
    const colors = ['', 'cyan', '', '', ''];
    const text = chars[type].repeat(length);
    if (!colors[type]) return text;
    return `<span style="color:${colors[type]}">${text}</span>`;
  }
});
```

Example — HTML colored output:

```javascript
const globe = new Globe({
  size: 1,
  pins: [{ lat: 52.23, long: 21.01 }],
  format(type, length) {
    const chars = [' ', ' ', '#', '@'];
    const colors = ['', '', 'green', 'red'];
    const text = chars[type].repeat(length);
    if (!colors[type]) return text;
    return `<span style="color:${colors[type]}">${text}</span>`;
  }
});

pre.innerHTML = globe.render(250);
```

#### Custom maps

The library ships with an Earth map by default, but you can use a different map by passing
the `map` option. The package includes a built-in Death Star map:

```javascript
import Globe from 'ascii-globe';
import deathStar from 'ascii-globe/maps/death-star';

const globe = new Globe({ map: deathStar, land: '#', water: ' ' });
console.log(globe.render(0));
```

With a script tag (no modules), load the map as a separate script. The load order doesn't matter:

```html
<pre id="output"></pre>
<script src="https://cdn.jsdelivr.net/npm/ascii-globe"></script>
<script src="https://cdn.jsdelivr.net/npm/ascii-globe/dist/maps/death-star.global.js"></script>
<script>
var globe = new Globe({ map: Globe.maps['death-star'], land: '#', water: ' ' });
document.getElementById('output').textContent = globe.render(0);
</script>
```

You can also generate your own map data from any equirectangular projection image
(see [Generating custom map data](#generating-custom-map-data) below).

### `globe.render(rotation)`

Returns a string with the ASCII globe rendered at the given rotation.

- `rotation` — a single number (horizontal angle in degrees) or a `[horizontal, vertical]` pair. Values wrap around automatically.

```javascript
globe.render(90);       // horizontal rotation only
globe.render([90, 30]); // horizontal + vertical tilt
```

## CLI

```bash
npx ascii-globe --rotation 200
npx ascii-globe --animate
```

Or install globally:

```bash
npm install -g ascii-globe
globe --rotation 200
globe --animate
```

```
ASCII Globe v0.4.2 - Isomorphic ASCII globe renderer

Usage: globe <--rotation <degrees> | --animate> [options]

Options:
  --rotation <degrees>  Rotation angle (single number or h,v pair)
  --animate             Animate the globe in the terminal
  --size <number>       Globe size multiplier (default: 1.4)
  --map <file>          Path to a map data file (generated by extract-texture)
  --land <char>         Character for land (default: #)
  --water <char>        Character for water (default: -)
  --background <char>   Character for background (default: " ")
  --margin <number>     Characters around the globe (default: 0)
  --margin-block <n>    Vertical margin (overrides --margin)
  --margin-inline <n>   Horizontal margin (overrides --margin)
  --padding <number>    Circular gap between the globe and the border (default: 0)
  --border <char>       Character for a circular border/glow around the globe (default: #)
  --border-width <n>    Thickness of the border ring; turns it on (default: 0, or 1 if --border is set)
  --pin <char>          Character for location pins (default: @)
  --pin-size <number>   Size of pin markers (default: 1)
  --pins <coords>       Pin locations as lat,long pairs separated by ;
  --tilt <degrees>      Axial tilt in degrees (default: 0)
  --speed <number>      Rotation speed in degrees per frame (default: 0.7)
  --help                Show this help message
  -v, --version         Show version number

Either --rotation or --animate is required.
```

Example with pins (Warsaw and New York):

```bash
globe --rotation 250 --pins '52.23,21.01;40.71,-74.01'
globe --rotation 250 --pin '\x1b[31m@\x1b[m' --pins '52.23,21.01'
```

Example with a circular glow around the globe (1 character of padding, `#` border):

```bash
globe --rotation 0 --padding 1 --border '#'
```

Example with a custom map:

```bash
globe --rotation 40,20 --map ./my-map.js --land '#' --water ' '
```

## Examples

### Node.js terminal animation

```javascript
import Globe from 'ascii-globe';

const globe = new Globe({ size: 1, land: '#', water: ' ', tilt: 23.5 });

let rotation = 0;

process.stdout.write('\x1B[?25l'); // hide cursor

setInterval(() => {
  process.stdout.write('\x1B[2J\x1B[H'); // clear + home
  process.stdout.write(globe.render(rotation));
  rotation = (rotation + globe.speed) % 360;
}, 1000 / 30);
```

Run the included example:

```bash
npm run example:node
```

### Browser animation

```html
<pre id="globe"></pre>
<button id="toggle">Pause</button>
<input id="rotation" type="number" min="0" max="360" step="0.1" value="0" disabled>

<script src="https://cdn.jsdelivr.net/npm/ascii-globe"></script>
<script>
var globe = new Globe({ size: 1, land: '#', water: ' ' });
var pre = document.getElementById('globe');
var playing = true;
var rotation = 0;

function loop() {
  pre.textContent = globe.render(rotation);
  if (playing) {
    rotation = (rotation + 0.7) % 360;
    requestAnimationFrame(loop);
  }
}

document.getElementById('toggle').addEventListener('click', function() {
  playing = !playing;
  if (playing) loop();
});

loop();
</script>
```

Open `examples/browser/index.html` via a local server to run the included browser demo.

## Generating custom map data

You can generate map data from any equirectangular projection image (2:1 aspect ratio PNG).
The extract script is included in the repository:

```bash
npx tsx scripts/extract-texture.ts <image.png> --output <output.ts>
```

Options:

| Flag           | Description                                              |
|----------------|----------------------------------------------------------|
| `--output <path>` | Output file path (default: `src/maps/<name>.ts`)      |
| `--grayscale`  | Use brightness instead of Earth-specific color detection |
| `--alpha`      | Use the alpha channel as the mask (opaque = land)        |
| `--invert`     | Invert the mask (swap land and water)                    |

The default mode classifies pixels using Earth-specific color heuristics (blue → water,
dark → land). Use `--grayscale` or `--alpha` for non-Earth images.

The output file can be imported and passed to the `map` option:

```javascript
import Globe from 'ascii-globe';
import myMap from './my-map.ts';

const globe = new Globe({ map: myMap });
```

Or used with the CLI:

```bash
globe --rotation 90 --map ./my-map.ts
```

## Building from source

```bash
npm install
npm run extract   # generate src/maps/ from globe.png and death-star.png
npm run build     # compile TypeScript to dist/
```

## License

Copyright (c) 2026 [Jakub T. Jankiewicz](https://jakub.jankiewicz.org/)

Released under the MIT License. See [LICENSE](https://github.com/jcubic/ascii-globe/blob/master/LICENSE) for details.
