# SurveyStream — background pattern

Seamless, infinitely repeating SVG tile: a telemetry node matrix with continuous stream
waveforms running through it. **Drawn twice — once for dark grounds, once for light.**

## Files

| File | Size | Use |
| --- | --- | --- |
| `tile-brand-400.svg` | 12.6 KB | **Canonical dark tile.** Brand palette. |
| `tile-brand-400-light.svg` | 12.6 KB | **Canonical light tile.** |
| `tile-brand-400-subtle.svg` | 12.6 KB | Dark, half-strength accents — behind text. |
| `tile-brand-400-light-subtle.svg` | 12.6 KB | Light, half strength. |
| `tile-slate-400.svg` | 12.6 KB | Slate palette, dark. The hexes from the brief. |
| `tile-slate-400-light.svg` | 12.6 KB | Slate palette, light. |
| `tile-slate-400-subtle.svg` | 12.6 KB | Slate, dark, half strength. |
| `tile-slate-200.svg` / `-light` | 9.7 KB | 200×200 tile, denser grid. |
| `pattern-*.svg` | ~12.9 KB | 1200×800 demos: `<pattern>` in `<defs>` + the demonstration rect. |
| `pattern.css` | 3.5 KB | Theme-swapping custom properties + ready classes. 3.1 KB gzipped. |
| `pattern.html` | 162.8 KB | Live specimen page; specimens follow the page theme. |

## The light tile is drawn, not inverted

This is the part worth understanding before reaching for `filter: invert()`.

On a dark ground the accents are **additive** — a bright cyan stroke over near-black reads as
glow, and the three stacked strokes build a halo. Invert that and you get a dark stroke with a
pale halo on white: the halo reads as a dirty smudge rather than light, and the whisper-thin grid
becomes a heavy dark cage.

What changes in the light tile:

| | Dark | Light |
| --- | --- | --- |
| Accent A | Stream `#10BBD1` | Current `#0A6E86` |
| Accent B | Pulse `#2FE3A2` | Pulse-deep `#12987A` |
| Grid | `rgba(126,150,156,0.09)` | `rgba(86,106,112,0.11)` |
| Nodes | `rgba(126,150,156,0.18)` | `rgba(86,106,112,0.22)` |
| Glow layers | 100% | **62%** — dark ink on light shows far more |
| Line layers | 100% | 90% |
| Packets | 0.85 | 0.72 |
| Geometry | identical | identical |

Both tiles pass the same seam test.

> An earlier version of this folder documented the pattern as **dark-mode only by design**. That
> was true of the artwork as drawn, but it made the deliverable half a system — a background that
> only works in one theme is not a background. Fixed rather than justified.

## Why it is seamless

Five rules, all enforced by the generator:

1. **Every wave has an integer number of periods across the tile width.** That is the whole seam
   condition — it makes value *and* slope agree at `x=0` and `x=W`. The four waves run
   2, 3, 1 and 4 periods across 400px.
2. **Wave paths are drawn from −1 period to W+1 period.** The overhang is clipped by
   `<pattern>`, and because the path is W-periodic the clipped part is exactly what the
   neighbouring tile draws. No phase break at the join.
3. **No filters.** This is the trap. A `feGaussianBlur` renders its glow *outside* the path,
   that output gets clipped at the tile boundary, and the clipped halo does not wrap — a
   visible bright seam. The glow here is three layered strokes on the same path, which is pure
   geometry and joins correctly.
4. **Grid lines are drawn at `0 … W−step`, never at `W`.** Drawing both edges doubles the
   line on every boundary and halves the apparent spacing there.
5. **Nodes on an edge are duplicated on the opposite edge.** A dot centred at `x=0` is
   clipped to its right half; its mirror at `x=W` supplies the left half from the neighbour.
   Corner dots get all four positions.

Two composition rules that are not about seams but about the repeat being *invisible*:

- **Waves are spaced uniformly at 1/8, 3/8, 5/8, 7/8 of the height** so the gap that wraps
  across the tile edge is the same size as the internal gaps. Uneven spacing leaves an empty
  band, and that band is what makes a viewer see the grid of tiles.
- **Packet segments stay clear of the edges.** A `stroke-dasharray` along the wave would have
  been easier, but dash phase is measured from the path start, so it restarts at every tile
  and breaks at the boundary.

## Verification

Rendered 3×3 at 1:1 and measured. For each pixel column the mean `|I(x+1,y) − I(x,y)|` was
computed; a seam shows up as an outlier column.

```
brand 400 dark    vertical    interior mean Δ 0.910  sd 1.946   boundary Δ 0.403  z −0.26  PASS
                  horizontal  interior mean Δ 1.592  sd 2.518   boundary Δ 0.000  z −0.63  PASS
brand 400 light   vertical    interior mean Δ 0.993  sd 2.892   boundary Δ 0.345  z −0.22  PASS
                  horizontal  interior mean Δ 1.524  sd 3.073   boundary Δ 0.003  z −0.50  PASS
slate 400 dark    vertical    boundary z −0.25    slate 200 dark   boundary z −0.49
slate 200 light   vertical    boundary z −0.36    horizontal       boundary z −0.40
periodicity       max |I(x,y) − I(x+400,y)| over 960,000 samples = 0   (every tile)
```

The boundary columns are **quieter than the average interior column** — negative z-scores — so
there is not merely no detectable seam, the joins are smoother than typical content. Periodicity
error is exactly 0, meaning the tiling is pixel-identical, not approximately so.

## Usage

```css
/* theme-aware, both grounds handled */
@import "pattern/pattern.css";
.hero { /* use .ss-bg or .ss-bg-subtle */ }

/* or pick one explicitly */
.hero { background: #04171F url("pattern/tile-brand-400.svg") repeat; background-size: 400px 400px; }
```

```html
<!-- inline, when you want to recolour via CSS or animate a layer -->
<svg><defs>…<pattern id="surveystream-pattern" …>…</pattern></defs>
  <rect width="100%" height="100%" fill="url(#surveystream-pattern)"/>
</svg>
```

**Figma:** drag in `tile-brand-400.svg` (or the `-light` counterpart), select the frame, and set
the fill to *Image → Tile* at 400px. Because the tile is a real 400×400 frame with a clip, Figma's
tiling lines up with the CSS tiling exactly.

**Animating it:** shift `background-position` by exactly 400px per cycle (see `.ss-bg-drift`).
Any other distance produces a visible jump when the animation loops.

**Pinning a ground:** `.ss-bg--force-dark` and `.ss-bg--force-light` ignore the theme, for a
section that must always be one or the other.

## Notes

- **Behind text, use the subtle variant** and keep body copy off the wave bands. The full-strength
  version is for hero bands and empty states. This holds in both themes — the light tile is
  quieter than the dark one, but a wave crest still runs through a line of 14px text.
- **Two palettes.** The brief specified slate `#0B0F19`/`#0F172A` with `#00E5A3`/`#06B6D4`;
  both are built in both grounds. The brand-palette version is canonical per the colour system,
  and it is what `--ss-pattern` points at.
