# LighthouseLoader

> A striped lighthouse on a rock whose beam sweeps round: the beam is squashed through zero width like a turning light seen side-on, and the lamp flares each time it swings past the viewer. A water line laps at the rock. 2.4s loop.

- Page: https://www.boatui.dev/loaders/lighthouse
- Source: https://github.com/wi11s/boatui/blob/main/components/loaders/lighthouse-loader.tsx
- Animated. Generate: ~911 tokens (source size, single pass). Import: ~19 tokens.

## Install

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

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

## Usage

```tsx
import { LighthouseLoader } from '@/components/loaders/lighthouse-loader';

export function Saving() {
  return <LighthouseLoader size={48} label="Saving your changes…" />;
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `theme` | `Partial<LighthouseLoaderTheme>` | — | Colour overrides. Keys listed under theme tokens. |
| `size` | `number` | 64 | Width and height in pixels. |
| `label` | `string` | "Loading…" | Announced to screen readers through role="status". |
| `paused` | `boolean` | false | Freezes the animation. |
| `className` | `string` | — | Applied to the root element (an inline-flex span). |
| `style` | `CSSProperties` | — | Merged into the root element style, after theme variables. |

## Theme tokens

| Key | CSS variable | Default |
| --- | --- | --- |
| `beam` | `--beam` | `#f6c75a` |
| `lamp` | `--lamp` | `#fff2c4` |
| `tower` | `--tower` | `#f4f1ea` |
| `stripes` | `--stripes` | `#d1493c` |
| `roof` | `--roof` | `#3d4a57` |
| `rock` | `--rock` | `#6b7a86` |
| `water` | `--water` | `#7fb6d4` |

## Source

`components/loaders/lighthouse-loader.tsx`

```tsx
// <LighthouseLoader>: a lighthouse on a rock whose beam sweeps round and round. The beam is faked
// in 2D by squashing it through zero width, and the lamp flares each time it swings past you.

import { useId } from 'react';
import { LoaderFrame, type LoaderProps } from './loader';
import styles from './lighthouse-loader.module.css';

export const lighthouseLoaderTheme = {
  beam: '#f6c75a',
  lamp: '#fff2c4',
  tower: '#f4f1ea',
  stripes: '#d1493c',
  roof: '#3d4a57',
  rock: '#6b7a86',
  water: '#7fb6d4',
};
export type LighthouseLoaderTheme = typeof lighthouseLoaderTheme;

/** A lighthouse whose beam sweeps round, flaring as it faces you. */
export function LighthouseLoader(props: LoaderProps<LighthouseLoaderTheme>) {
  const id = `qs${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
  return (
    <LoaderFrame
      {...props}
      defaultTheme={lighthouseLoaderTheme}
      art={
        <>
          <defs>
            {/* The water line fades out at both ends instead of spilling past the box. */}
            <linearGradient id={`${id}fade`} gradientUnits="userSpaceOnUse" x1="10" y1="0" x2="90" y2="0">
              <stop offset="0" stopColor="#fff" stopOpacity="0" />
              <stop offset="0.25" stopColor="#fff" />
              <stop offset="0.75" stopColor="#fff" />
              <stop offset="1" stopColor="#fff" stopOpacity="0" />
            </linearGradient>
            <mask id={`${id}mask`} maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
              <rect width="100" height="100" fill={`url(#${id}fade)`} />
            </mask>
          </defs>

          {/* Beam: hinged at the lamp; its width runs 1 → 0 → -1 like the cosine of a turning light. */}
          <g transform="translate(50 30)">
            <g className={styles.beam}>
              <path d="M0 -3 L46 -12 L46 12 L0 3 Z" fill="var(--beam)" opacity="0.55" />
            </g>
          </g>

          <path d="M41 36 L39 78 H61 L59 36 Z" fill="var(--tower)" />
          <path d="M40.6 46 L40 56 H60 L59.4 46 Z M39.6 66 L39.2 74 H60.8 L60.4 66 Z" fill="var(--stripes)" />
          <rect x="42" y="24" width="16" height="12" rx="1.5" fill="var(--roof)" />
          <rect x="44.5" y="26" width="11" height="9" rx="1" fill="var(--lamp)" />
          <path d="M40 24 L50 15 L60 24 Z" fill="var(--stripes)" />
          <rect x="38" y="35" width="24" height="2.5" rx="1" fill="var(--roof)" />
          <circle className={styles.flare} cx="50" cy="30" r="9" fill="var(--lamp)" />

          <path d="M26 86 Q32 74 44 76 H58 Q70 74 76 86 Z" fill="var(--rock)" />
          <g mask={`url(#${id}mask)`}>
            <g className={styles.water} fill="none" stroke="var(--water)" strokeWidth="3" strokeLinecap="round">
              <path d="M-4 88 q6 -4 12 0 t12 0 t12 0 t12 0 t12 0 t12 0 t12 0 t12 0 t12 0 t12 0" />
            </g>
          </g>
        </>
      }
    />
  );
}
```

`components/loaders/lighthouse-loader.module.css`

```css
/* The beam swings 1 → -1 → 1 in 2.4s (ease-in-out alternate approximates a cosine), so it
   passes through zero width — facing you — every 1.2s. The lamp flares at those moments. */
.beam { animation: sweep 1.2s ease-in-out infinite alternate; }

.flare {
  transform-box: fill-box;
  transform-origin: center;
  animation: flare 1.2s ease-in-out infinite;
}

/* The water line laps sideways by one 24-unit wavelength. */
.water { animation: lap 2.4s linear infinite; }

@keyframes sweep {
  from { transform: scaleX(1); }
  to   { transform: scaleX(-1); }
}
@keyframes flare {
  0%, 100% { transform: scale(0.4); opacity: 0.15; }
  50%      { transform: scale(1.3); opacity: 0.9; }
}
@keyframes lap { to { transform: translateX(-24px); } }
```
