Skip to content

SaltiePulse

  • Layer: Primitive
  • Base: Custom SVG — no Ark UI dependency
  • Motion: rAF-driven SVG attribute updates; skipped entirely under prefers-reduced-motion: reduce

Three phases carry a directional grammar: quiet water when waiting, outward ripples when thinking, inward convergence when an answer is surfacing. The primitive itself does not auto-advance — callers choose the phase.

Idle → Thinking → Emerging

One primitive, four sizes. Pick by where Saltie is rendered — inline chip, row marker, panel header, empty state.

xs · sm · md · lg

Microcopy lives in the component. Callers pick phase + context and Saltie supplies the voice. general is poker-faced; reviewing, planning, researching tilt the language toward the task.

general / reviewing / planning / researching — thinking then emerging

Chat thinking · titlebar dot · empty state

PropTypeDefaultDescription
phase"idle" | "thinking" | "emerging"requiredWhich phase to render.
size"xs" | "sm" | "md" | "lg""md"16 / 24 / 48 / 80 px.
context"general" | "reviewing" | "planning" | "researching""general"Microcopy pool. Non-general pools cover thinking + emerging only; idle falls back to general.
showLabelbooleanfalse at xs, true elseShow copy next to the pulse.
labelstringOverride the resolved label entirely. Wins over pools.
labelVariant"short" | "long""short" at sm, "long" at md/lgOnly meaningful for context="general" — contextual pools are long-only.
classstringExtra class on the outer span.
  • Direction is meaning. Outward = input dropped in. Inward = output surfacing. Never invert — the metaphor only works one way.
  • Cycle times are fixed across themes. Saltie is 200 million years old. Idle breathes on 6s; thinking ripples on 2.2s; emerging converges in 2.4s then holds for 1.4s. A fast pulse reads anxious. A slow pulse reads considered.
  • Stroke resolves through var(--colors-accent), so themes work without extra plumbing. Switch delight / relight / gilt mid-render and the colour updates in one frame.
  • Reduced motion is detected via matchMedia at mount; under prefers-reduced-motion: reduce the component renders a static settled pose and never starts requestAnimationFrame.
  • aria-label is phase-only ("Saltie idle" / "Saltie thinking" / "Saltie answering"). Visible copy is flavour; screen readers get intent.

Each context supplies a long pool of one-liners. Pool selection is deterministic — a minute-granularity hash of context + phase + size, so the quip is stable for the duration of one phase but varies across mounts.

  • General carries both a single-word short default ("Lurking." / "Chewing." / "Surfacing.") and a long pool.
  • Reviewing is close-up, in the jaws: "Chewing on it.", "Reading the grain.", "Checking the bones."
  • Planning is wide-angle: "Lining up the shot.", "Picking the angle.", "Mapping the shallows."
  • Researching is going deep: "Sounding the depths.", "Consulting the mangroves.", "Waking the council."

SALTIE_PULSE_RARE_THINKING_GENERAL exports the "Letting it marinate." quip as a named constant. It is not in any default pool — pass it via label when you want the flavour without polluting the poker-faced default. Keep it rare.

  • No background, no border on the component itself — composition sits with the caller.
  • No color shifts on errors or success; pulse carries only presence and phase.
  • No CSS keyframes — the rAF engine writes SVG attributes directly so the timings match the reference exactly and cleanup is deterministic.
TokenSource
--colors-accentStroke + fill across all phases
text.secondaryLabel colour
font.monoLabel typeface