SurveyStream

SurveyStream · Production SVG

Horizontal Lockup

Every curve is an exact arc-to-cubic conversion, every glyph is a real Archivo Bold outline, and the whole thing is fitted to viewBox="0 0 600 140" with the bounds computed rather than eyeballed — verified zero clipping on all four edges.

viewBox0 0 600 140
Size7.95 KB, no dependencies
Mark anchors8 on the loop, 7 on the flow
Precision2 decimal places
TextOutlined — no font needed

01 — The lockup

Rendered from the file itself.

SurveyStream Horizontal logo lockup: an interlocking S-curve mark with an active flow accent, followed by the SurveyStream wordmark.
lockup-600x140.svgTwo-tone wordmark
SurveyStream Horizontal logo lockup: an interlocking S-curve mark with an active flow accent, followed by the SurveyStream wordmark.
Same file, slate groundNote the contrast issue in §04

02 — The code

Complete and standalone.

The mark's path data is short enough to read — that's the point of exact bezier conversion rather than auto-trace. The two wordmark paths are long because they are genuine glyph outlines; they're abbreviated here and complete in the file.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 140" width="600" height="140" role="img" aria-labelledby="ssTitle ssDesc" fill="none">
  <title id="ssTitle">SurveyStream</title>
  <desc id="ssDesc">Horizontal logo lockup: an interlocking S-curve mark with an active flow accent, followed by the SurveyStream wordmark.</desc>

  <defs>
    <linearGradient id="ssFlow" gradientUnits="userSpaceOnUse" x1="12" y1="106.07" x2="79.79" y2="33.93">
      <stop offset="0" stop-color="#00E5A3"/>
      <stop offset="1" stop-color="#06B6D4"/>
    </linearGradient>
  </defs>

  <g id="surveystream-lockup">

    <g id="mark" stroke-linecap="round" stroke-linejoin="round">
      <g id="mark-loop" stroke="#0F172A" stroke-width="10.08">
        <path id="mark-loop-upper" d="M64.23 44.94C57.59 39.27 45.98 37.39 36.18 40.39C26.38 43.39 20.96 50.49 23.08 57.54C25.2 64.58 34.31 69.74 45.08 69.99"/>
        <path id="mark-loop-lower" d="M27.56 95.06C34.2 100.73 45.81 102.61 55.61 99.61C65.41 96.61 70.83 89.51 68.71 82.46C66.59 75.42 57.49 70.26 46.71 70.01"/>
      </g>
      <path id="mark-flow" d="M18.35 42.46C13.12 49.32 12.68 57.69 17.19 64.84C21.7 71.99 30.58 77 40.92 78.24C32.74 77.26 32.74 62.74 40.92 61.76C51.46 60.5 62.13 63.32 69.34 69.27C76.55 75.22 79.33 83.5 76.75 91.33" stroke="url(#ssFlow)" stroke-width="4.19"/>
    </g>

    <g id="wordmark" stroke="none">
      <path id="wordmark-survey" fill="#0F172A" d="M129.97 97.05Q123.88 97.05 118.7 95.37Q113.51 93.7 110.39 90.08Q107.27 86.46 107.27 80.59Q107.27…
      <!-- 2834 chars of outline data, complete in the file -->"/>
      <path id="wordmark-stream" fill="#06B6D4" d="M366.03 97.05Q359.94 97.05 354.76 95.37Q349.58 93.7 346.45 90.08Q343.33 86.46 343.33 80.59Q343.3…
      <!-- 3608 chars of outline data, complete in the file -->"/>
    </g>

  </g>
</svg>

03 — Geometry

No freehand curve anywhere in it.

Arc → cubic, exactly

Each bowl is an ellipse arc converted to cubic beziers by the standard construction, split into three ≤90° spans:

P(t)  = (cx + rx·cos t, cy + ry·sin t)
P'(t) = (−rx·sin t,  ry·cos t)
α     = 4/3 · tan(Δt/4)
C₁ = P(t₁) + α·P'(t₁)
C₂ = P(t₂) − α·P'(t₂)

Maximum radial error for a 90° span is about 0.027% of the radius. At the size this mark is ever printed, that is well under a thousandth of a millimetre — mathematically clean anchor points with tangent continuity at every join.

Construction

  • Loop base: two tangent elliptical bowls, rx 30 / ry 20, centred (0, ∓20), each sweeping 230° from −38° to −268°. They touch at the origin, which is where the stroke hands off.
  • Flow accent: a concentric arc offset 11 units outward. It runs outside the upper bowl, crosses the base at the waist, then runs outside the lower bowl — so it swaps sides, which is what produces the interlock.
  • The crossing is a single G1-continuous cubic whose control points continue both arc tangents. No kink, no lump.
  • rx > ry gives the S a 0.78 width-to-height aspect. Circular bowls give 0.50, which reads spindly next to a Bold wordmark.

The interlock finally works — and the colour split is why

On the previous version of this mark I tried four interlocking constructions and abandoned all of them: overlapping gapped rings, an overshoot self-crossing, a true over/under weave, and a wide-bowl variant. Every one left a visible lump where two strokes of the same weight and colour met, or stopped reading as an S. Splitting the palette into a heavy slate base and a thin mint/cyan accent — your brief's structure — removes the problem entirely: a 4.2-unit accent crossing a 10.1-unit base reads as one passing over the other with no ambiguity to resolve.

What I could not make work: the literal pulse waveform

Your brief asks for the loop "blended with an active data stream/pulse waveform". I built seven versions — sharp ECG zigzags, smooth decaying sine tails at both terminals, tapered arc dashes, and discrete pulse packets — and rendered every one. The zigzags and sine tails looped back on themselves into a knot at the accent's terminal, because the tangent there is steep relative to any wavelength short enough to fit. The dashes and dots were invisible at logo scale, adding file size and nothing else.

What shipped treats the flow accent itself as the data stream — a line threading the loop and swapping sides. If you want a literal waveform, it needs to live somewhere with room to breathe: a horizontal lockup variant with the wave running along the baseline under the wordmark, or an animated web-only version where the accent's stroke-dashoffset travels. Both are separate pieces of work, and I'd rather say so than ship the knot.

04 — Colour

Your palette, with one thing you should know.

RoleValueOn whiteOn slateVerdict as text
Deep Slate #0F172A17.85:11.00:1 AAA on white
Cyan #06B6D42.43:17.35:1 Fails on white · AAA on slate
Mint #00E5A31.65:110.83:1 Fails on white · AAA on slate

"Stream" in cyan on white is 2.43:1

The precise position: WCAG 2.1 SC 1.4.3 explicitly exempts logotypes — "text that is part of a logo or brand name has no contrast requirement" — so this is not a conformance failure, and you can ship it. But an exemption is not the same as legibility. At the 96 px minimum width the cyan half of the wordmark visibly recedes against the slate half.

That's why lockup-600x140-slate.svg exists: identical geometry, wordmark entirely in Deep Slate. Use the two-tone version at display sizes and on dark grounds where cyan measures 7.35:1; use the slate version in small, dense, or print contexts. Reversed on slate, both halves are comfortably above AAA.

This palette diverges from the validated system

These are your specified values and I've built to them exactly. For the record, they are not the palette from the colour system:

  • #04171F#0F172A — ground moves from teal-navy to blue-slate
  • #10BBD1#06B6D4 — cyan gets slightly deeper and bluer
  • #2FE3A2#00E5A3 — mint gets more saturated

The blue-slate shift is the substantive one: it breaks the teal bias that tied the neutrals to the accent across the whole system. Worth a deliberate decision about which palette is canonical before either ships, rather than discovering the split later in a component library.

05 — Fit and import

Computed bounds, verified clipping-free.

MeasureValueNotes
Mark boundsx 12 → 79.79Left edge exactly on the 12-unit padding.
y 33.93 → 106.07Vertically centred in the 140-unit box.
Wordmark boundsx 103.84 → 588Right edge exactly on the padding. Baseline y 96.14.
Drawn content576 × 72.14Width-constrained fit at scale 0.7620.
Loop stroke10.08Matches the Archivo Bold stem weight.
Flow stroke4.19Thin enough that the crossing reads unambiguously.

About the vertical space

A 600×140 frame is 4.29:1. This lockup's natural ratio is 7.98:1, so fitting it to your box is width-constrained and leaves roughly 34 units of air above and below. That is correct behaviour, not a bug — I did not enlarge the mark to fill the frame, because the mark-to-wordmark ratio is a brand constant and stretching it to suit one canvas is how lockups drift.

If you want it tight-cropped instead, use viewBox="0 0 600 84.14" with the same paths, or the existing lockup-horizontal.svg which is already cropped to its own bounds.

Figma and web

  • Figma: imports as a clean group tree — mark-loop, mark-flow, wordmark-survey, wordmark-stream. The gradient lands as a real gradient fill on the stroke. If you need fills only, select #mark and use Outline Stroke.
  • Responsive: the width/height attributes are there for Figma's benefit. For fluid web use, drop them and let CSS drive: svg{width:100%;height:auto}. The default preserveAspectRatio already centres correctly.
  • Gradient is userSpaceOnUse, with coordinates matched to the mark's real bounding box — so it renders identically regardless of container size, and won't skew the way objectBoundingBox does on a stroked path.
  • Inlining in HTML: rename the three ids if the page carries more than one copy, or the url(#ssFlow) reference will bind to the first.