# NightSkyBackground

> A deep night sky: 110 stars twinkling at three sizes (some warm-tinted, the brightest with a soft glow), a faint diagonal band of the Milky Way, and three shooting stars that each flash once in a long cycle. CSS keyframes only.

- Page: https://www.boatui.dev/backgrounds/night-sky
- Source: https://github.com/wi11s/boatui/blob/main/components/backgrounds/night-sky-background.tsx
- Animated. Generate: ~946 tokens (source size, single pass). Import: ~21 tokens.

## Install

All backgrounds: `npx degit wi11s/boatui/components/backgrounds components/backgrounds`

Only this one: copy the shared files (`background.tsx`, `background.module.css`) and `night-sky-background.tsx` and `night-sky-background.module.css` into `components/backgrounds/`. Shared files are in https://www.boatui.dev/llms-full.txt.

## Usage

```tsx
import { NightSkyBackground } from '@/components/backgrounds/night-sky-background';

export default function Layout({ children }: { children: React.ReactNode }) {
  return <NightSkyBackground style={{ minHeight: '100vh' }}>{children}</NightSkyBackground>;
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `theme` | `Partial<NightSkyTheme>` | — | Colour overrides. Keys listed under theme tokens. |
| `paused` | `boolean` | false | Freezes the animation. |
| `className` | `string` | — | Applied to the root element. Size it like any block element. |
| `style` | `CSSProperties` | — | Merged into the root element style, after theme variables. |
| `children` | `ReactNode` | — | Your content. The background paints behind it. |

## Theme tokens

| Key | CSS variable | Default |
| --- | --- | --- |
| `skyTop` | `--sky-top` | `#070b1f` |
| `skyBottom` | `--sky-bottom` | `#1d2550` |
| `milkyWay` | `--milky-way` | `#8f9fe0` |
| `star` | `--star` | `#ffffff` |
| `warmStar` | `--warm-star` | `#ffe2b0` |

## Source

`components/backgrounds/night-sky-background.tsx`

```tsx
// <NightSkyBackground>: a deep night sky with stars twinkling at three sizes, a faint band of the
// Milky Way, and a shooting star every few seconds. Animated.

import type { CSSProperties } from 'react';
import { BackgroundFrame, r2, seeded, type BackgroundProps } from './background';
import styles from './night-sky-background.module.css';

export const nightSkyTheme = {
  skyTop: '#070b1f',
  skyBottom: '#1d2550',
  milkyWay: '#8f9fe0',
  star: '#ffffff',
  warmStar: '#ffe2b0',
};
export type NightSkyTheme = typeof nightSkyTheme;

const rand = seeded(83);
const STARS = Array.from({ length: 110 }, () => {
  const big = rand() > 0.9;
  return {
    x: r2(rand() * 100),
    y: r2(rand() * 100),
    size: big ? r2(2.5 + rand() * 1.5) : r2(0.8 + rand() * 1.4),
    warm: rand() > 0.8,
    twinkle: r2(2 + rand() * 4),
    delay: r2(rand() * 6),
    big,
  };
});

// Shooting stars: where each starts, and when in its long, mostly empty cycle it flashes.
const METEORS = [
  { x: 72, y: 12, cycle: 9, delay: 2 },
  { x: 40, y: 6, cycle: 13, delay: 8 },
  { x: 90, y: 30, cycle: 17, delay: 12 },
];

/** A deep night sky with twinkling stars, the Milky Way and shooting stars. */
export function NightSkyBackground(props: BackgroundProps<NightSkyTheme>) {
  return (
    <BackgroundFrame
      {...props}
      defaultTheme={nightSkyTheme}
      layer={
        <>
          <div className={styles.sky} />
          <div className={styles.milkyWay} />
          {STARS.map((s, i) => (
            <span
              key={i}
              className={`${styles.star} ${s.big ? styles.big : ''}`}
              style={{
                left: `${s.x}%`,
                top: `${s.y}%`,
                width: s.size,
                height: s.size,
                background: s.warm ? 'var(--warm-star)' : 'var(--star)',
                animationDuration: `${s.twinkle}s`,
                animationDelay: `${-s.delay}s`,
              }}
            />
          ))}
          {METEORS.map((m, i) => (
            <span
              key={i}
              className={styles.meteor}
              style={{ left: `${m.x}%`, top: `${m.y}%`, animationDuration: `${m.cycle}s`, animationDelay: `${-m.delay}s` } as CSSProperties}
            />
          ))}
        </>
      }
    />
  );
}
```

`components/backgrounds/night-sky-background.module.css`

```css
.sky {
  position: absolute;
  inset: 0;
  background: linear-gradient(var(--sky-top), var(--sky-bottom));
}

/* A soft diagonal band, made of two blurred ellipses of light. */
.milkyWay {
  position: absolute;
  inset: -20%;
  background:
    radial-gradient(ellipse 60% 14% at 50% 50%, color-mix(in srgb, var(--milky-way) 30%, transparent), transparent 70%),
    radial-gradient(ellipse 30% 8% at 60% 46%, color-mix(in srgb, var(--milky-way) 22%, transparent), transparent 70%);
  transform: rotate(-28deg);
  opacity: 0.8;
}

.star {
  position: absolute;
  border-radius: 50%;
  animation: twinkle ease-in-out infinite alternate;
}

/* The brightest stars get a soft glow. */
.big { box-shadow: 0 0 6px 1px color-mix(in srgb, var(--star) 55%, transparent); }

/* A streak that is invisible for most of its cycle, then races down-left and fades. */
.meteor {
  position: absolute;
  width: 90px;
  height: 1.5px;
  border-radius: 1px;
  background: linear-gradient(to left, var(--star), transparent);
  transform-origin: 0 50%;
  opacity: 0;
  animation: meteor linear infinite;
}

@keyframes twinkle {
  from { opacity: 0.25; transform: scale(0.8); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes meteor {
  0%, 92% { transform: rotate(150deg) translateX(0) scaleX(0.3); opacity: 0; }
  93%     { opacity: 1; }
  98%     { transform: rotate(150deg) translateX(-160px) scaleX(1); opacity: 0; }
  100%    { transform: rotate(150deg) translateX(-160px) scaleX(1); opacity: 0; }
}
```
