SurveyStream · Background pattern
A seamless 400×400 tile: a node matrix with continuous stream waveforms running through it. Verified pixel-periodic with zero boundary discontinuity — the joins measure quieter than the average interior column. Two inks: one for dark grounds, one for light.
01 — The pattern
Every specimen below follows the page theme. Switch it with the control in the corner — these are two separately drawn tiles, not one inverted.
02 — Why it is seamless
x=0 and
x=W. The four waves run 2, 3, 1 and 4 periods across 400 px.<pattern>, and because the path is W-periodic the clipped
part is exactly what the neighbour draws. No phase break at the join.feGaussianBlur renders its glow outside
the path, that output is clipped at the tile boundary, and the clipped halo does not
wrap — a bright seam on every edge. The glow here is three layered strokes on the same
path: pure geometry, joins correctly.0 … W−step, never at W.
Drawing both edges doubles the line on every boundary and visibly halves the spacing
there.x=0 is clipped to
its right half; its mirror at x=W supplies the left half from the
neighbouring tile. Corner dots get all four positions.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. My first version spaced them unevenly and left an empty band — the band was seamless, but it was also the thing that made the grid of tiles visible.
Packets 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 every tile and breaks at the boundary.
Rendered 3×3 at 1:1, then for every pixel column the mean
|I(x+1,y) − I(x,y)|. A seam is an outlier column — a bright or dark line
where two tiles meet. Interior columns average 0.909 with sd
1.972; the boundary columns measure 0.425, a z-score
of −0.25. The joins are quieter than typical content, which is the
strongest form of "no seam" you can ask for.
| Tile | Axis | Interior mean Δ | sd | Boundary Δ | z | Periodicity error | Result |
|---|---|---|---|---|---|---|---|
| brand 400 dark | vertical | 0.910 | 1.946 | 0.403 | −0.26 | 0 | PASS |
| brand 400 dark | horizontal | 1.592 | 2.518 | 0.000 | −0.63 | 0 | PASS |
| brand 400 light | vertical | 0.993 | 2.892 | 0.345 | −0.22 | 0 | PASS |
| brand 400 light | horizontal | 1.524 | 3.073 | 0.003 | −0.50 | 0 | PASS |
| slate 400 dark | vertical | 0.909 | 1.972 | 0.425 | −0.25 | 0 | PASS |
| slate 200 dark | vertical | 2.736 | 2.191 | 1.655 | −0.49 | 0 | PASS |
| slate 200 light | vertical | 2.677 | 3.391 | 1.460 | −0.36 | 0 | PASS |
Periodicity error is the maximum |I(x,y) − I(x+400,y)| across
960,000 samples. Zero means the tiling is pixel-identical, not approximately so.
03 — Two inks, not an inversion
This is the part worth understanding before you reach for
filter: invert().
On dark 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 grid becomes a heavy dark cage instead of a whisper.
An earlier version of this page said the pattern was dark-mode only by design. That was true of the artwork, but it made the deliverable half a system. It is fixed rather than justified.
#0A6E86, Pulse → Pulse-deep #12987A.
Bright mint on white is invisible; deep teal on white is a line.pattern.css ships both and swaps them across the
three theme states, so .ss-bg is now correct on either ground
instead of switching itself off under prefers-color-scheme: light.
04 — The code
Wave path data abbreviated — the full tile is 12.6 KB, and 1.8 KB gzipped.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800" width="1200" height="800">
<title>SurveyStream — stream & telemetry pattern</title>
<defs>
<pattern id="surveystream-pattern" patternUnits="userSpaceOnUse" width="400" height="400">
<!-- 1. matrix: lines at 0…350, never at 400, so spacing stays even across tiles -->
<path d="M0 0V400M50 0V400M100 0V400…M0 0H400M0 50H400…"
stroke="rgba(126,150,156,0.09)" stroke-width="1" fill="none" shape-rendering="crispEdges"/>
<!-- 2. telemetry nodes; any node on an edge is mirrored on the opposite edge -->
<path d="M0 0m-1.15 0a1.15 1.15 0 1 0 2.3 0a1.15 1.15 0 1 0 -2.3 0…" fill="rgba(126,150,156,0.18)"/>
<!-- 3. streams: 2 periods across 400. Drawn from −200 to 600 so the overhang
is clipped into exactly what the neighbouring tile draws.
Three layered strokes fake the glow — a real filter would be clipped
at the tile edge and leave a bright seam. -->
<g>
<path d="M-200 50C-157.55 20.67 -142.45 20.67 -100 50C…"
stroke="#10BBD1" stroke-width="13" opacity="0.07" fill="none" stroke-linecap="round"/>
<path d="M-200 50C…" stroke="#10BBD1" stroke-width="5.85" opacity="0.12" fill="none" stroke-linecap="round"/>
<path d="M-200 50C…" stroke="#10BBD1" stroke-width="1.5" opacity="0.3" fill="none" stroke-linecap="round"/>
</g>
<!-- …three more streams at 3, 1 and 4 periods, y = 3/8, 5/8, 7/8 -->
<!-- 4. packets: short bright segments on the wave, kept clear of the edges -->
<g><path d="M112.4 33.1L117.3 31.8…" stroke="#10BBD1" stroke-width="3.15"
opacity="0.85" fill="none" stroke-linecap="round"/></g>
</pattern>
</defs>
<rect width="100%" height="100%" fill="#04171F"/>
<rect width="100%" height="100%" fill="url(#surveystream-pattern)"/>
</svg>
05 — Usage
CSS
/* both themes, via the shipped custom properties */
@import "pattern/pattern.css";
.hero { /* .ss-bg swaps ground + tile with the theme */ }
/* or pick one explicitly */
.hero-dark {
background: #04171F url("pattern/tile-brand-400.svg") repeat;
background-size: 400px 400px;
}
Reference the file in production. The SVG gzips to 1.8 KB as a file; the same tile percent-encoded inside a stylesheet is 12.3 KB raw and compresses far worse, because URI escaping destroys the repetition gzip feeds on.
Figma
tile-brand-400.svg or the
-light counterpart.Animating it
Shift background-position by exactly 400 px
per cycle — .ss-bg-drift does this over 60 s. Any other
distance produces a visible jump when the loop restarts.
Keep body copy off the wave bands. Use the subtle preset behind text in either theme — the full-strength tile is for hero bands, empty states and login screens. The light tile is quieter than the dark one, but a wave crest still runs through a line of 14px text.