SurveyStream

Documentdocs/pattern/README.md

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

FileSizeUse
tile-brand-400.svg12.6 KBCanonical dark tile. Brand palette.
tile-brand-400-light.svg12.6 KBCanonical light tile.
tile-brand-400-subtle.svg12.6 KBDark, half-strength accents — behind text.
tile-brand-400-light-subtle.svg12.6 KBLight, half strength.
tile-slate-400.svg12.6 KBSlate palette, dark. The hexes from the brief.
tile-slate-400-light.svg12.6 KBSlate palette, light.
tile-slate-400-subtle.svg12.6 KBSlate, dark, half strength.
tile-slate-200.svg / -light9.7 KB200×200 tile, denser grid.
pattern-*.svg~12.9 KB1200×800 demos: <pattern> in <defs> + the demonstration rect.
pattern.css3.5 KBTheme-swapping custom properties + ready classes. 3.1 KB gzipped.
pattern.html162.8 KBLive 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:

DarkLight
Accent AStream #10BBD1Current #0A6E86
Accent BPulse #2FE3A2Pulse-deep #12987A
Gridrgba(126,150,156,0.09)rgba(86,106,112,0.11)
Nodesrgba(126,150,156,0.18)rgba(86,106,112,0.22)
Glow layers100%62% — dark ink on light shows far more
Line layers100%90%
Packets0.850.72
Geometryidenticalidentical

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

/* 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; }
<!-- 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.