Skip to content

Features Orbit

Integrations section: copy with a chip list on one side, and a central gradient orb with integration bubbles orbiting on two counter-rotating glass rings (bubbles stay upright). Pure CSS.

glass/section/features-orbit
Open ↗

Source

import type { CSSProperties } from "react";
import { cn } from "@/lib/utils";

export interface FeaturesOrbitProps {
  eyebrow?: string;
  title?: string;
  description?: string;
  /** Integration names (2 letters shown in the bubble). Split across two rings. */
  integrations?: string[];
  cta?: { label: string; href: string };
  className?: string;
}

const GLASS = "da-stroke bg-da-surface backdrop-blur-da backdrop-saturate-150";

/** Integrations section: copy on one side, a central Halo orb with integration bubbles orbiting on two counter-rotating rings. Pure CSS. */
export function FeaturesOrbit({
  eyebrow = "Integrations",
  title = "Plugs into the tools you already live in",
  description = "Halo joins your calls on Zoom, Meet and Teams, then sends recaps and tasks to Slack, Notion, Linear, Jira, HubSpot and 40 more.",
  integrations = ["Zoom", "Meet", "Teams", "Slack", "Notion", "Linear", "Jira", "HubSpot", "Asana", "Salesforce"],
  cta = { label: "Browse all integrations", href: "#integrations" },
  className,
}: FeaturesOrbitProps) {
  const inner = integrations.slice(0, 4);
  const outer = integrations.slice(4);

  const ring = (items: string[], radius: number, dir: "cw" | "ccw", duration: number) => (
    <div
      className={cn("glass-orbit absolute inset-0 m-auto rounded-full border border-da-border", dir === "ccw" && "is-reverse")}
      style={{ width: radius * 2, height: radius * 2, animationDuration: `${duration}s` } as CSSProperties}
    >
      {items.map((name, i) => {
        const a = (i / items.length) * Math.PI * 2;
        return (
          <span
            key={name}
            title={name}
            className={cn(
              "glass-orbit-item absolute grid size-12 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full font-da-display text-xs font-semibold shadow-da-md",
              GLASS,
            )}
            style={{ left: `${50 + Math.cos(a) * 50}%`, top: `${50 + Math.sin(a) * 50}%`, animationDuration: `${duration}s` } as CSSProperties}
          >
            {name.slice(0, 2)}
          </span>
        );
      })}
    </div>
  );

  return (
    <section aria-labelledby="glass-orbit-title" className={cn("da-section overflow-hidden text-da-fg", className)}>
      <div className="da-container grid items-center gap-12 lg:grid-cols-2">
        <div>
          <p className="text-sm font-semibold text-da-primary">{eyebrow}</p>
          <h2
            id="glass-orbit-title"
            className="mt-3 font-da-display text-[clamp(2rem,4.5vw,3.25rem)] leading-[1.08] font-semibold tracking-da-display text-balance"
          >
            {title}
          </h2>
          <p className="mt-4 text-lg text-da-muted-fg">{description}</p>
          <ul className="mt-6 flex flex-wrap gap-2" aria-label="Supported tools">
            {integrations.map((n) => (
              <li key={n} className={cn("rounded-da-pill px-3 py-1 text-sm font-medium", GLASS)}>
                {n}
              </li>
            ))}
          </ul>
          <a
            href={cta.href}
            className="da-focus da-transition mt-8 inline-flex h-12 items-center rounded-da-pill bg-da-fg px-6 font-semibold text-da-bg shadow-da-md hover:-translate-y-0.5"
          >
            {cta.label}
          </a>
        </div>
        <div aria-hidden className="relative mx-auto aspect-square w-full max-w-[440px] scale-[0.8] sm:scale-100">
          <div className="absolute inset-0 m-auto size-40 rounded-full bg-da-primary/30 blur-3xl" />
          {ring(outer, 200, "ccw", 60)}
          {ring(inner, 120, "cw", 40)}
          <div className="absolute inset-0 m-auto grid size-24 place-items-center rounded-full bg-gradient-to-br from-da-primary to-da-accent shadow-da-lg">
            <span className="size-8 rounded-full bg-da-bg/90" />
          </div>
        </div>
      </div>
      <style href="glass-orbit" precedence="default">{`
        @keyframes glass-orbit { to { rotate: 360deg } }
        @keyframes glass-orbit-counter { to { rotate: -360deg } }
        .glass-orbit { animation: glass-orbit 40s linear infinite }
        .glass-orbit.is-reverse { animation-name: glass-orbit-counter }
        .glass-orbit-item { animation: glass-orbit-counter 40s linear infinite }
        .glass-orbit.is-reverse .glass-orbit-item { animation-name: glass-orbit }
        @media (prefers-reduced-motion: reduce) { .glass-orbit, .glass-orbit-item { animation: none } }
      `}</style>
    </section>
  );
}

export default FeaturesOrbit;

modules/glass/section/features-orbit/index.tsx

Props

PropTypeDefaultDescription
eyebrow / title / descriptionstring—Copy.
integrationsstring[]—Names (first 4 inner ring, rest outer).
cta{ label, href }—Link.
classNamestring—Classes.

Other features variants in Glass

Features in other art directions

Features Grid

Six features in a single ruled box (1/2/3 columns) with numbered tiles, color-cycling icon blocks that press on hover, and a two-column section header.

BrutalistFeatures

Features Numbered

Full-width ruled rows with giant faded step numbers, uppercase titles, descriptions and a stat chip; each row floods with a different primary color on hover and the number turns solid.

BrutalistFeatures

Features Tabs

Tabbed feature explorer: ruled mono tab bar (ink active tab), then a big split panel with title, description and square-bullet list on the left and a stacked mock preview of the channel on the right.

BrutalistFeatures

Features Versus

Before/after comparison: two slightly tilted cards side by side — the old way in paper with red crosses, the new way in solid yellow with ink checks — row by row on the same topics.

BrutalistFeatures

Features Alternating

Three zig-zag rows, each pairing a numbered kicker, title, copy and link with an HTML product mock-up (routing rules, on-call week, AI postmortem) on a soft tinted plate.

MinimalFeatures

Features Bento

Bento grid of five hairline tiles on a 6-column layout, each with a small token-themed HTML illustration (routing lines, uptime bars, MTTR metric, on-call week) above an icon, title and description.

MinimalFeatures

Features List

Dense four-column feature list: each item has a small indigo line icon, a title and a one-sentence description under a hairline top rule. Eight defaults, left-aligned header.

MinimalFeatures

Features Steps

Five-step workflow (detect, page, coordinate, resolve, learn) laid out horizontally on desktop and vertically on mobile, joined by a hairline, with numbered nodes and timing labels.

MinimalFeatures

Features Index

Features as a numbered index: a label + headline in the left columns, and a two-column ruled list on the right where each item is “01 — Title” with a short description.

Mono CleanFeatures

Features Numbered

Three large numbered steps: giant outlined-looking numerals in muted ink above a hairline, step title and text; columns on desktop, stacked on mobile.

Mono CleanFeatures

Features Showcase

Work showcase as the feature: an asymmetric grid (fixed row heights; wide, tall, square and full-width spans) of neutral gray image frames with mono captions (studio — project, template used); captions underline on hover.

Mono CleanFeatures

Features Specs

Technical spec sheet: grouped key/value definition lists in a 4-column grid, each group under a full-ink rule with a mono heading — reads like a product data sheet.

Mono CleanFeatures

Features Bento

Data-driven bento grid of bordered cards: cash-flow line chart, DSO metric, approval flow, currency list and integrations — each a small realistic mock.

Neo CorporateFeatures

Features Grid

Classic 3×2 bordered feature grid sharing hairline borders (one outer frame), each cell with a blue icon square, title, text and a “Learn more” link.

Neo CorporateFeatures

Features Tabs

Underline tabs (Radix Tabs) switching between product areas: copy + check points on the left, a bordered data card (status rows) on the right.

Neo CorporateFeatures

Features Versus

Before/after comparison: a muted “spreadsheets and email” column with red crosses next to a blue-bordered “with Ledgerly” column with green checks.

Neo CorporateFeatures

Features Alternating

Alternating zig-zag rows: copy with check points on one side, a soft blob-shaped tinted panel holding a small product vignette (suppliers, actions, report) on the other.

Organic SoftFeatures

Features Bento

Soft bento: rounded tinted tiles of mixed sizes — a monthly emissions bar chart, a human equivalence (“= 42 flights”), supplier avatars, a certifications tile and a leaf quote.

Organic SoftFeatures

Features Cards

Three tall rounded cards in sage, clay and paper tints; each has an icon sitting in an organic blob, a serif title and calm copy.

Organic SoftFeatures

Features Journey

Horizontal journey: numbered circles connected by a hand-drawn wavy dashed path (vertical list on mobile), each step with a serif title, text and a mono time estimate.

Organic SoftFeatures

Features Checklist

Interactive split section: copy and a mint progress bar on the left, a white card of four round-checkbox steps on the right that turn mint with a bouncy tick when clicked; celebration at 100%.

Soft FlatFeatures

Features Icons

Centered 4-column grid of eight small features, each with a large round pastel icon badge (colors rotate), rounded title and one-liner.

Soft FlatFeatures

Features Pastel

2×2 grid of large pastel feature cards (peach, mint, lavender, butter), each with icon, rounded title, copy and a tiny flat illustration (mini board, bar week, reminder toast, chat bubbles).

Soft FlatFeatures

Features Steps

Three numbered step cards in peach, lavender and mint joined by a dashed path on desktop, each with a white emoji medallion, mono step label, title and text.

Soft FlatFeatures