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
| 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:
- 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=0andx=W. The four waves run 2, 3, 1 and 4 periods across 400px. - 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. - No filters. This is the trap. A
feGaussianBlurrenders 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. - Grid lines are drawn at
0 … W−step, never atW. Drawing both edges doubles the line on every boundary and halves the apparent spacing there. - Nodes on an edge are duplicated on the opposite edge. A dot centred at
x=0is clipped to its right half; its mirror atx=Wsupplies 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-dasharrayalong 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/#0F172Awith#00E5A3/#06B6D4; both are built in both grounds. The brand-palette version is canonical per the colour system, and it is what--ss-patternpoints at.