haplollc/ThinkingOrbs

★ 77⑂ 5

Dotted, honestly-3D loading indicators for AI and agent interfaces in SwiftUI. Nine hand-tuned designs, two tuned sizes.

About haplollc/ThinkingOrbs

haplollc/ThinkingOrbs is an open-source project on GitHub, mainly written in Swift. Dotted, honestly-3D loading indicators for AI and agent interfaces in SwiftUI. Nine hand-tuned designs, two tuned sizes. It currently holds 77 stars and 5 forks with 0 open issues, and was last pushed on an unknown date (repository created unknown).

Project Overview

AI Homed tracks it on the Today's Trending board, currently at rank #96 with 0 new stars today.

GitHub Repository Details

Repository haplollc/ThinkingOrbs · default branch - · size 0 KB · watchers 0 · source: GitHub REST API and repository README

README

https://github.com/haplollc/ThinkingOrbs/blob/HEAD/All nine ThinkingOrbs designs animating side by side

ThinkingOrbs

Dotted, genuinely 3D loading indicators for AI and agent interfaces in SwiftUI.
Nine hand-tuned designs, two purpose-tuned sizes, one line to drop in.

https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Swift 6 https://github.com/haplollc/ThinkingOrbs/blob/HEAD/iOS 17+ https://github.com/haplollc/ThinkingOrbs/blob/HEAD/macOS, tvOS, watchOS and visionOS https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Swift Package Manager https://github.com/haplollc/ThinkingOrbs/blob/HEAD/MIT License

---

ThinkingOrb(.searching)

That is the whole integration. Eight of the nine designs are real 3D forms, rotated, depth-shaded and z-sorted, and the ninth is a morphing outline. All of them are drawn only in grayscale dots, so they sit quietly in any interface, light or dark. Every orb pauses itself offscreen, stays in phase with the others on screen, and respects Reduce Motion.

Download the 30-second demo video (MP4)

Installation

In Xcode: File > Add Package Dependencies and paste

https://github.com/haplollc/ThinkingOrbs

Or in Package.swift:

dependencies: [
    .package(url: "https://github.com/haplollc/ThinkingOrbs", from: "1.0.0")
]

Then add ThinkingOrbs to your target's dependencies.

Quick start

import SwiftUI
import ThinkingOrbs

struct AssistantStatus: View { var body: some View { ThinkingOrbLabel("Searching the web…", design: .searching) } }

The designs

Every design is a case of OrbDesign. Pick the one that says what your agent is actually doing.

| Regular | Small | Design | In code | Reach for it when | |:---:|:---:|---|---|---| | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Working, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Working, small | Working
Particles on tilted orbits | .working | General-purpose busy | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Searching, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Searching, small | Searching
A scan meridian sweeps a dotted globe | .searching | Web search, retrieval, lookups | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Solving, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Solving, small | Solving
Bands scramble, then click back solved | .solving | Reasoning, math, code | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Listening, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Listening, small | Listening
A waveform rolls through the rings | .listening | Voice input, transcription | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Connecting, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Connecting, small | Connecting
A constellation wires itself | .connecting | Tool calls, APIs, sync | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Weaving, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Weaving, small | Weaving
Three strands plait around the sphere | .weaving | Planning, multi-step agents | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Composing, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Composing, small | Composing
An undulating multi-band sash | .composing | Writing a reply | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Breathing, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Breathing, small | Breathing
A ring slowly morphing | .breathing | Idle thinking, waiting on a model | | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Shaping, regular | https://github.com/haplollc/ThinkingOrbs/blob/HEAD/Shaping, small | Shaping
Circle → triangle → square | .shaping | Design, image and layout work |

ThinkingOrb(.connecting)

ForEach(OrbDesign.allCases) { design in // all nine, with names Label { Text(design.title) } icon: { ThinkingOrb(design, size: .small) } }

Sizes

Two tuned sizes, and they are separate designs, not one design scaled: the small one has fewer, bigger dots and its own tempo, so it stays legible next to text.

ThinkingOrb(.working)                  // .regular: 64 pt, for avatars, empty states, hero moments
ThinkingOrb(.working, size: .small)    // .small: 20 pt, for inline text, toolbars, list rows
ThinkingOrb(.solving, diameter: 56)    // the 64 pt design, drawn at 56 pt

Status labels

https://github.com/haplollc/ThinkingOrbs/blob/HEAD/A Thinking pill and four status chips, their text shimmering

ThinkingOrbLabel puts an orb beside a shimmering status line. Style it like any other view:

ThinkingOrbLabel("Searching the web…", design: .searching)

ThinkingOrbLabel("Thinking…", design: .composing, size: .regular, diameter: 48) .font(.title3) .padding(8) .padding(.trailing, 20) .background(.thinMaterial, in: .capsule)

The shimmer is a modifier too, for any text that should read as live:

Text("Composing a reply…")
    .thinkingShimmer()

Titles localize the way Text does. A string literal is looked up in your strings, a String value is shown as-is, and you can pass your own Text:

ThinkingOrbLabel("Searching the web…", design: .searching)                   // localized, like Text
ThinkingOrbLabel("status.syncing", tableName: "Agent", design: .connecting)   // from your own table
ThinkingOrbLabel(modelOutput, design: .composing)                             // a String shows as-is

Recipes

Map your agent's state to a design. Wrap it in a ZStack so the old and new orb share one slot while they crossfade:

enum AgentPhase {
    case idle, searching, reasoning, callingTools, writing

var orb: OrbDesign { switch self { case .idle: .breathing case .searching: .searching case .reasoning: .solving case .callingTools: .connecting case .writing: .composing } } }

ZStack { ThinkingOrb(phase.orb) .id(phase.orb) .transition(.opacity.animation(.easeInOut(duration: 0.25))) }

An "assistant is typing" bubble:

HStack(alignment: .bottom, spacing: 10) {
    ThinkingOrb(.composing, diameter: 36)
    Text("Writing a reply…")
        .thinkingShimmer()
        .padding(.horizontal, 14)
        .padding(.vertical, 10)
        .background(.fill.tertiary, in: .rect(cornerRadius: 18))
}

A status in the navigation bar:

.toolbar {
    ToolbarItem(placement: .principal) {
        ThinkingOrbLabel("Syncing…", design: .connecting)
            .font(.subheadline)
    }
}

A button that is busy:

Button {
    Task { await generate() }
} label: {
    if isGenerating {
        ThinkingOrbLabel("Generating…", design: .shaping)
    } else {
        Label("Generate", systemImage: "sparkles")
    }
}
.buttonStyle(.bordered)

An empty state while a model loads:

ContentUnavailableView {
    ThinkingOrb(.breathing)
} description: {
    Text("Warming up the model…")
}

A list row:

LabeledContent("Indexing photos") {
    ThinkingOrb(.searching, size: .small)
}

Appearance

The ink is strictly monochrome and follows the environment's color scheme: dark dots on light backgrounds, light dots on dark ones, with the depth shading mirrored so near dots always read strongest. To put an orb on a surface that doesn't match the app's scheme, pin it:

ThinkingOrb(.weaving)
    .environment(\.colorScheme, .dark)   // light dots, for a dark card in a light app

Speed and pausing

ThinkingOrb(.listening, speed: 1.5)                 // a multiplier on the tuned speed
ThinkingOrb(.listening, isPaused: !isRecording)     // freezes on the current frame

Accessibility

Performance

One Canvas per orb inside one TimelineView, with no view per dot. Orbs park their timeline while scrolled out of view, on either axis, so a card in a carousel that has itself scrolled off the page stops too. That holds from the very first layout, so orbs that start below the fold never run. They also park while the app is in the background.

Measured on an iPhone 17 Pro Max, the heaviest design (.composing, 566 dots) takes 65 µs to compute a frame and 0.27 ms to rasterize it, against 8.3 ms per frame at 120 Hz. Most designs cost a fraction of that.

Draw it yourself

The geometry is public, for SpriteKit, Metal, a watch complication or a pen plotter. A frame is a finished draw list: every value is final and the array order is the draw order.

let frame = OrbDesign.connecting.frame(size: .regular, at: seconds)

for line in frame.lines { // draw edges first // line.x1, line.y1, line.x2, line.y2, line.w in points // line.white and line.a are ink and opacity, as for dots } for dot in frame.dots { // then dots, far to near // dot.x, dot.y, dot.r in points, in a 64 × 64 box // dot.white is ink (0 is darkest, mirror it on dark), dot.a is opacity }

seconds is any running time in seconds at normal speed, such as a TimelineView date's timeIntervalSinceReferenceDate. The design's tuned speed is applied for you.

Faithful to the original

These are Jakub Antalik's designs, and the port keeps them exact:

Requirements

Development

xcodebuild test -scheme ThinkingOrbs -destination "platform=iOS Simulator,name=iPhone 17 Pro"
Scripts/differential-sweep.sh      # prove parity with the original web engine
Scripts/render-media.sh            # re-render every GIF in this README

Credits

The designs, their tuning and the original engine are thinking-orbs by Jakub Antalik, MIT licensed. See them on the web at orbs.jakubantalik.com. This package is a native Swift port.

License

ThinkingOrbs is available under the MIT license, which carries both the original copyright and ours.

Made by Haplo LLC.

GitHub Stars & Activity

77Stars
5Forks
0Open issues
SwiftLanguage

GitHub Popularity

GitHub stars77
Forks5
Open issues0
Primary languageSwift
License-
Stars gained today0
Created-
Last pushed-

Trending History

Daily boardrank #96 · ▲ 0 stars

Related AI Projects

1

Beingpax / VoiceInk

Swift★ 6,492⑂ 924▲ 24 stars
2

mattt / iMCP

Swift★ 1,634⑂ 120▲ 11 stars
3

savka777 / jev-use

Swift★ 77⑂ 8
4

affaan-m / ECC

JavaScript★ 263,994⑂ 39,485▲ 826 stars
5

tensorflow / tensorflow

C++★ 200,221⑂ 77,033▲ 30 stars
6

Significant-Gravitas / AutoGPT

Python★ 187,469⑂ 46,006▲ 27 stars
7

anthropics / claude-code

TypeScript★ 147,260⑂ 24,085▲ 419 stars
8

ggml-org / llama.cpp

C++★ 129,000⑂ 23,514▲ 136 stars

More AI Rankings