# CloudsBackground

> Twelve soft clouds in three parallax layers drift across a sky gradient: far clouds are small, faint and slow, near ones larger and quicker, each shaded toward its base. A pale sun glows gently in the corner. CSS keyframes and container units.

- Page: https://www.boatui.dev/backgrounds/clouds
- Source: https://github.com/wi11s/boatui/blob/main/components/backgrounds/clouds-background.tsx
- Animated. Generate: ~1,051 tokens (source size, single pass). Import: ~20 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 `clouds-background.tsx` and `clouds-background.module.css` into `components/backgrounds/`. Shared files are in https://www.boatui.dev/llms-full.txt.

## Usage

```tsx
import { CloudsBackground } from '@/components/backgrounds/clouds-background';

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

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `theme` | `Partial<CloudsTheme>` | — | 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` | `#9ccbee` |
| `skyBottom` | `--sky-bottom` | `#eef6fc` |
| `cloud` | `--cloud` | `#ffffff` |
| `cloudShade` | `--cloud-shade` | `#e2eef8` |
| `sun` | `--sun` | `#fff4d1` |

## Source

`components/backgrounds/clouds-background.tsx`

```tsx
// <CloudsBackground>: soft clouds drifting across the sky in three parallax layers,
// under a gently glowing sun. Animated.

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

export const cloudsTheme = {
  skyTop: '#9ccbee',
  skyBottom: '#eef6fc',
  cloud: '#ffffff',
  cloudShade: '#e2eef8',
  sun: '#fff4d1',
};
export type CloudsTheme = typeof cloudsTheme;

// Far layers are smaller, fainter and slower; the near layer is larger and quicker.
const LAYERS = [
  { count: 5, width: [80, 110], duration: [110, 140], opacity: 0.55, top: [8, 38] },
  { count: 4, width: [130, 170], duration: [75, 95], opacity: 0.8, top: [20, 60] },
  { count: 3, width: [190, 240], duration: [50, 62], opacity: 0.95, top: [45, 82] },
];

const rand = seeded(88);
const between = ([a, b]: number[]) => a + rand() * (b - a);
const CLOUDS = LAYERS.flatMap(layer =>
  Array.from({ length: layer.count }, (_, i) => {
    const duration = r2(between(layer.duration));
    return {
      width: r2(between(layer.width)),
      top: r2(between(layer.top)),
      duration,
      // Spread evenly along the loop so the sky is never empty.
      delay: r2(duration * ((i + 0.2 + rand() * 0.6) / layer.count)),
      opacity: layer.opacity,
      flip: rand() > 0.5,
    };
  }),
);

/** One cloud: a flat-bottomed pill with three rounded puffs, shaded toward the base. */
function Cloud({ width, flip, gradient }: { width: number; flip: boolean; gradient: string }) {
  return (
    <svg
      width={width}
      height={width * 0.42}
      viewBox="0 0 100 42"
      style={{ display: 'block', transform: flip ? 'scaleX(-1)' : undefined }}
    >
      <g fill={`url(#${gradient})`}>
        <rect x="4" y="22" width="92" height="18" rx="9" />
        <circle cx="30" cy="24" r="14" />
        <circle cx="52" cy="17" r="17" />
        <circle cx="74" cy="25" r="12" />
      </g>
    </svg>
  );
}

/** Soft clouds drifting across the sky in three parallax layers. */
export function CloudsBackground(props: BackgroundProps<CloudsTheme>) {
  const gradient = 'qs-clouds-shade';
  return (
    <BackgroundFrame
      {...props}
      defaultTheme={cloudsTheme}
      layer={
        <>
          <div className={styles.sky} />
          <div className={styles.sun} />
          <svg width="0" height="0" style={{ position: 'absolute' }} aria-hidden="true">
            <defs>
              {/* In each cloud's own 100×42 space, so the whole cloud shades as one shape. */}
              <linearGradient id={gradient} gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="42">
                <stop offset="0.5" stopColor="var(--cloud)" />
                <stop offset="1" stopColor="var(--cloud-shade)" />
              </linearGradient>
            </defs>
          </svg>
          {CLOUDS.map((c, i) => (
            <div
              key={i}
              className={styles.drift}
              style={{ top: `${c.top}%`, opacity: c.opacity, animationDuration: `${c.duration}s`, animationDelay: `${-c.delay}s` } as CSSProperties}
            >
              <Cloud width={c.width} flip={c.flip} gradient={gradient} />
            </div>
          ))}
        </>
      }
    />
  );
}
```

`components/backgrounds/clouds-background.module.css`

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

/* A soft sun: a pale disc inside a wide glow that breathes slowly. */
.sun {
  position: absolute;
  top: -60px;
  right: -40px;
  width: 280px;
  height: 280px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--sun) 0 18%, color-mix(in srgb, var(--sun) 45%, transparent) 30%, transparent 68%);
  animation: glow 6s ease-in-out infinite alternate;
}

/* Clouds travel the full width, from just off the left edge to just off the right. */
.drift {
  position: absolute;
  left: 0;
  animation: drift linear infinite;
  will-change: transform;
}

@keyframes drift {
  from { transform: translateX(-280px); }
  to   { transform: translateX(calc(100cqw + 40px)); }
}
@keyframes glow {
  from { opacity: 0.85; transform: scale(1); }
  to   { opacity: 1; transform: scale(1.06); }
}
```
