npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@verifyhash/sky-planner

v0.1.0

Published

Offline astronomy/photography night planner engine: solar events (sunrise/sunset/solar noon, civil+nautical+astronomical twilight, golden & blue hour), moon events (rise/set/phase/illumination), and a moonless-dark stargazing score for any lat/lon/date. Z

Readme

sky-planner

Offline night-planning engine for photographers and stargazers. Give it a place and a date; it returns the Sun's schedule (rise, set, solar noon, all three twilights, and the golden- and blue-hour windows), the Moon's schedule (rise, set, phase name, illumination), and a single 0–100 stargazing quality score for that night based on how much of the night is both astronomically dark and free of the Moon.

Everything runs locally with no dependencies and no network. index.js + vendor/ + lib/ are the pure computation engine (T-APP.1); index.html is a self-contained single-page UI (T-APP.2) that drives that same engine.

The UI (index.html)

Open index.html in any browser (double-click it, or serve the folder) — it is one self-contained file with no CDN, no external fonts/scripts/styles, and no network at all. Enter a latitude, longitude and date (or press Use my location, which degrades gracefully to manual entry) and the page updates live: a noon-to-noon sky-gradient timeline with sun/moon markers, readouts for every twilight and the golden/blue-hour windows, moon phase and rise/set, the moonless astronomical-dark window, and a stargazing-quality meter.

The engine is inlined verbatim into index.html by build.js, wrapped in a tiny in-page CommonJS shim — no source is duplicated by hand. If you change the engine, re-sync the page with:

node build.js

build.js reads index.js and its vendor//lib/ modules and splices their exact bytes between the ENGINE_START/ENGINE_END markers in index.html.

Who it's for

  • Landscape / portrait photographers chasing the golden hour (warm, low Sun) and the blue hour (the deep-blue twilight just before/after it) — the engine gives you the exact start/end of each, morning and evening.
  • Astrophotographers and stargazers who need true darkness: it reports the astronomical-twilight boundaries (Sun 18° below the horizon) and computes how many minutes of the night are actually Moon-free.
  • Anyone searching golden-hour / moon-phase / "is tonight dark?" for a trip, an event, or a shoot — one function call answers all of it for any latitude/longitude/date, past or future.

What "golden hour" and "blue hour" mean here

Photography terms don't have one universal definition, so here are the exact Sun-elevation thresholds this engine uses (documented so you can trust the numbers):

| Window | Sun elevation | Notes | |--------------|--------------------|----------------------------------------------| | Golden hour | −4° to +6° | Soft, warm light; the "hour" is much shorter near the equator and much longer near the poles. | | Blue hour | −6° to −4° | The saturated-blue twilight; sits just inside civil twilight. | | Civil dusk/dawn | Sun at −6° | Enough light to see; horizon still visible. | | Nautical dusk/dawn | Sun at −12° | Horizon fades; brighter stars out. | | Astronomical dusk/dawn | Sun at −18° | Sky is fully dark — the start/end of true night. |

Quick start

const { planNight } = require('sky-planner'); // or require('./index.js')

const plan = planNight({
  lat: 40.7128,           // + North
  lon: -74.006,           // + East
  date: { year: 2024, month: 3, day: 20 }, // or a JS Date
  tz: -4,                 // hours from UTC — only affects local-day selection
});

console.log(plan.sun.sunrise);                 // → UTC Date instant
console.log(plan.sun.goldenHour.evening);      // → { start: Date, end: Date }
console.log(plan.moon.phaseName, plan.moon.illuminationPercent + '%');
console.log('stargazing score:', plan.stargazing.score); // 0–100

All time fields are UTC Date instants (or null when the event doesn't occur — polar day/night, or a Moon that never rises/sets that day). To print a local clock time, format with the same tz you passed in, or use the re-exported helper:

const { localClockMinutes } = require('sky-planner');
localClockMinutes(plan.sun.sunset, -4); // → minutes-after-midnight, local

Result shape

{
  input: { lat, lon, tz, date: { year, month, day } },
  sun: {
    solarNoon, sunrise, sunset, dayLengthMinutes,
    twilight: { civilDawn, civilDusk, nauticalDawn, nauticalDusk,
                astronomicalDawn, astronomicalDusk },
    goldenHour: { morning: { start, end }, evening: { start, end } },
    blueHour:   { morning: { start, end }, evening: { start, end } }
  },
  moon: {
    moonrise, moonset, alwaysUp, alwaysDown,
    phaseName,            // 'New' | 'Waxing Crescent' | ... | 'Waning Crescent'
    illumination,         // 0..1, fraction of the disk lit
    illuminationPercent   // 0..100, rounded
  },
  stargazing: {
    astronomicalNightMinutes, // length of Sun-below-−18° night
    moonlessDarkMinutes,      // of that, how much has the Moon down
    score                     // 0..100
  }
}

The stargazing score

The score answers one question: how much genuinely dark, Moon-free sky does this night offer? It is computed as

  1. the astronomical night = this evening's astronomical dusk → the next morning's astronomical dawn (Sun below −18° the whole time);
  2. minus the minutes within that window when the Moon is above the horizon (sampled every 5 minutes from the lunar altitude);
  3. the resulting moonless-dark minutes mapped onto 0–100, rising linearly and plateauing at 100 once you have 6 hours of moonless darkness — beyond that, more time doesn't make the night meaningfully better.

By construction the score is bounded to [0, 100] and monotonically non-decreasing in the length of the moonless-dark window (stargazingScore() is exported separately so you can verify this). A new-moon night in winter scores near 100; a full-moon night, where the Moon is up all night, scores near 0. Nights with no astronomical darkness at all (high-latitude summer) score 0.

stargazingScore(moonlessDarkMinutes) is also exported on its own if you want to score a custom dark window.

Honest limits

  • Sun times use the NOAA/Meeus solar geometry (see vendor/solar-calc) and land within ~1–2 minutes of published almanac times at mid-latitudes. Near the poles, and for the exact moment of a horizon grazing, expect more spread.
  • Moon phase / illumination use a simplified Meeus series (see vendor/moon-phase): phase angle good to ~0.5°, illuminated fraction to ~1%. Illumination is evaluated at the middle of the night, not at a fixed instant.
  • Moonrise / moonset are found by sampling the lunar altitude hourly and interpolating the horizon crossing — typically within ~5 minutes at mid-latitudes, worse where the Moon skims the horizon. This is a planning tool, not an occultation or eclipse predictor.
  • The score ignores clouds, light pollution, air quality, and the Moon's phase during its time up (a thin crescent above the horizon still counts as "Moon up"). It measures dark-window geometry, not sky transparency.
  • No atmospheric-refraction term is added to the reported instantaneous Sun altitude; the twilight/rise thresholds already fold standard refraction into their zenith angles.

How it's built (offline, self-contained)

sky-planner/
├── index.js                 engine — the planNight() + stargazingScore() API
├── lib/
│   └── moon-position.js      Moon altitude + moonrise/moonset (original code)
├── vendor/                   pinned, verbatim copies — see PROVENANCE.md
│   ├── solar-calc/           NOAA solar geometry (MIT)
│   └── moon-phase/           simplified Meeus lunar phase (MIT)
└── test/
    └── engine.test.js

The solar and lunar-phase math is vendored (copied + committed + pinned by SHA-256) from the sibling solar-calc and moon-phase projects rather than required across directories, so this folder is fully standalone. See vendor/PROVENANCE.md for the exact source commits and hashes. There is no network access, no server, and no runtime dependency anywhere in the project.

Running the tests

One command, Node's built-in runtime only (no test framework, nothing to install):

node test/engine.test.js

It checks published sunrise/sunset for New York, London, and Sydney (±3 min), the full ordering of the twilight and golden/blue-hour events, lunar illumination against documented new/full moons, moonrise/moonset horizon geometry, and that the stargazing score is bounded, monotonic, and correctly lower on a full-moon night than a new-moon night. The process exits non-zero if any assertion fails.

License

MIT. Vendored components are MIT (see vendor/PROVENANCE.md).