Skip to content

Commit 0f9a1b2

Browse files
committed
refactor(ui): extract GlowOrbs decorative backdrop into design-system
Replace ~19 hand-rolled blur-orb backdrop blocks across `apps/console` and `apps/website` with a single stateless `<GlowOrbs>` primitive exposing a closed `preset` set (`hero`/`duo`/`section`/`corner`/`ambient`) and a token-only `tone`, mirroring how `Button`/`Badge` expose named variants instead of raw style props. `tone` is a discriminated union keyed on `preset` so an ambient-only tone cannot reach a static preset. Each tone's color utilities are spelled out as complete literals (Tailwind only emits classes it finds in source); geometry is shared. `EmptyState` now renders through the same `ambient` preset as `AmbientBackground` instead of duplicating the orb markup locally. `SupportedPlatforms` stays inline (its Docker-brand hex orb is not a design token). Parity is at the shape/tone family level, not pixel-identical, so a few sites shift tone or orb position.
1 parent 275a66e commit 0f9a1b2

22 files changed

Lines changed: 285 additions & 81 deletions

File tree

ui/apps/console/src/components/common/AmbientBackground.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ConnectionGrid } from "@shellhub/design-system/components";
1+
import { ConnectionGrid, GlowOrbs } from "@shellhub/design-system/components";
22

33
interface AmbientBackgroundProps {
44
variant?: "default" | "error";
@@ -11,24 +11,7 @@ export default function AmbientBackground({
1111

1212
return (
1313
<div className="absolute inset-0 overflow-hidden pointer-events-none">
14-
{/* Gradient blobs */}
15-
<div
16-
className={`absolute -top-32 -left-32 w-[500px] h-[500px] rounded-full blur-[120px] animate-pulse-subtle ${
17-
isError ? "bg-accent-red/[0.06]" : "bg-primary/10"
18-
}`}
19-
/>
20-
<div
21-
className={`absolute -bottom-48 -right-32 w-[400px] h-[400px] rounded-full blur-[100px] animate-pulse-subtle ${
22-
isError ? "bg-primary/[0.04]" : "bg-accent-cyan/8"
23-
}`}
24-
style={{ animationDelay: "1s" }}
25-
/>
26-
<div
27-
className={`absolute top-1/3 right-1/4 w-[300px] h-[300px] rounded-full blur-[80px] animate-pulse-subtle ${
28-
isError ? "bg-accent-red/[0.03]" : "bg-accent-blue/5"
29-
}`}
30-
style={{ animationDelay: "2s" }}
31-
/>
14+
<GlowOrbs preset="ambient" tone={isError ? "error" : "brand"} />
3215

3316
<ConnectionGrid />
3417

ui/apps/console/src/components/common/EmptyState.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ReactNode, useId } from "react";
22
import { IconBadge } from "@shellhub/design-system/primitives";
3+
import { GlowOrbs } from "@shellhub/design-system/components";
34

45
export type EmptyStateAccent = "primary" | "yellow";
56

@@ -28,31 +29,26 @@ interface AccentStyles {
2829
badge: string;
2930
icon: string;
3031
overline: string;
31-
orbPrimary: string;
32-
orbSecondary: string;
3332
}
3433

3534
/**
3635
* Accent styles. Full literal class strings (never interpolated fragments) so
3736
* the Tailwind JIT keeps them. The hero icon inherits the badge's text color
3837
* via `currentColor`; feature-card icons stay primary-accented in all variants.
3938
* Typed as `Record<EmptyStateAccent, …>` so adding an accent without a matching
40-
* entry is a compile error rather than a runtime `undefined`.
39+
* entry is a compile error rather than a runtime `undefined`. The decorative
40+
* orbs are driven by `<GlowOrbs preset="ambient">` (see below), not these tokens.
4141
*/
4242
const ACCENT = {
4343
primary: {
4444
badge: "bg-primary/10 border-primary/20 shadow-primary/5",
4545
icon: "text-primary",
4646
overline: "text-primary/80",
47-
orbPrimary: "bg-primary/5",
48-
orbSecondary: "bg-accent-blue/5",
4947
},
5048
yellow: {
5149
badge: "bg-accent-yellow/10 border-accent-yellow/20 shadow-accent-yellow/5",
5250
icon: "text-accent-yellow",
5351
overline: "text-accent-yellow/80",
54-
orbPrimary: "bg-accent-yellow/5",
55-
orbSecondary: "bg-primary/5",
5652
},
5753
} satisfies Record<EmptyStateAccent, AccentStyles>;
5854

@@ -86,12 +82,9 @@ export default function EmptyState({
8682
aria-hidden="true"
8783
className="absolute inset-0 overflow-hidden pointer-events-none -mx-8 -mt-8 -mb-4"
8884
>
89-
<div
90-
className={`absolute -top-32 left-1/3 w-[500px] h-[500px] rounded-full blur-[120px] animate-pulse-subtle ${styles.orbPrimary}`}
91-
/>
92-
<div
93-
className={`absolute bottom-0 right-1/4 w-[400px] h-[400px] rounded-full blur-[100px] animate-pulse-subtle ${styles.orbSecondary}`}
94-
style={{ animationDelay: "1s" }}
85+
<GlowOrbs
86+
preset="ambient"
87+
tone={accent === "yellow" ? "warning" : "brand"}
9588
/>
9689
<div className="absolute inset-0 grid-bg opacity-30" />
9790
</div>
@@ -123,9 +116,7 @@ export default function EmptyState({
123116

124117
{/* Feature highlights */}
125118
{features?.length ? (
126-
<ul
127-
className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-10"
128-
>
119+
<ul className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-10">
129120
{features.map((feature, idx) => (
130121
<li
131122
key={feature.title}

ui/apps/console/src/components/common/PageHeader.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ReactNode } from "react";
22
import { IconBadge, type Palette } from "@shellhub/design-system/primitives";
3+
import { GlowOrbs } from "@shellhub/design-system/components";
34

45
interface PageHeaderProps {
56
icon: ReactNode;
@@ -28,12 +29,7 @@ export default function PageHeader({
2829
: "bg-surface page-header-band"
2930
}`}
3031
>
31-
{variant === "decorated" && (
32-
<>
33-
<div className="absolute inset-0 bg-gradient-to-br from-primary/15 via-surface to-accent-cyan/10" />
34-
<div className="absolute top-0 right-0 w-60 h-60 bg-primary/10 rounded-full blur-2xl -translate-y-1/3 translate-x-1/4" />
35-
</>
36-
)}
32+
{variant === "decorated" && <GlowOrbs preset="corner" tone="primary" />}
3733

3834
<div
3935
className={`${variant === "decorated" ? "relative " : ""}flex flex-col sm:flex-row sm:items-center justify-between gap-4`}

ui/apps/console/src/components/common/WelcomeScreen.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ArrowRightIcon,
88
BookOpenIcon,
99
} from "@heroicons/react/24/outline";
10-
import { ConnectionGrid } from "@shellhub/design-system/components";
10+
import { ConnectionGrid, GlowOrbs } from "@shellhub/design-system/components";
1111
import { GithubIcon, IconBadge } from "@shellhub/design-system/primitives";
1212

1313
interface WelcomeScreenProps {
@@ -49,9 +49,7 @@ export default function WelcomeScreen({ namespaceName }: WelcomeScreenProps) {
4949
{/* Hero */}
5050
<div className="relative pt-16 pb-12 overflow-hidden">
5151
<ConnectionGrid />
52-
<div className="absolute inset-0 bg-gradient-radial from-primary/10 via-transparent to-transparent" />
53-
<div className="absolute top-10 left-1/4 w-96 h-96 bg-primary/8 rounded-full blur-3xl" />
54-
<div className="absolute bottom-0 right-1/4 w-72 h-72 bg-accent-cyan/6 rounded-full blur-3xl" />
52+
<GlowOrbs preset="duo" tone="primary" />
5553

5654
<div className="relative text-center max-w-lg mx-auto">
5755
<div className="animate-float mb-8 inline-block">

ui/apps/website/src/pages/enterprise/HeroEnterprise.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Link } from "react-router-dom";
22
import { Badge, Button } from "@shellhub/design-system/primitives";
3+
import { GlowOrbs } from "@shellhub/design-system/components";
34
import { ArrowRight } from "@/components/ArrowRight";
45
import { Reveal, ConnectionGrid } from "../landing/components";
56

67
export function HeroEnterprise() {
78
return (
89
<section className="relative pt-32 pb-24 overflow-hidden">
910
<ConnectionGrid />
10-
<div className="absolute inset-0 bg-gradient-radial from-primary/8 via-transparent to-transparent pointer-events-none" />
11-
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-primary/6 rounded-full blur-3xl pointer-events-none" />
11+
<GlowOrbs preset="section" tone="primary" />
1212

1313
<div className="max-w-7xl mx-auto px-8 relative z-10 text-center">
1414
<Reveal>

ui/apps/website/src/pages/features/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Card,
66
WindowChrome,
77
} from "@shellhub/design-system/primitives";
8+
import { GlowOrbs } from "@shellhub/design-system/components";
89
import {
910
CheckIcon,
1011
PlayCircleIcon,
@@ -64,8 +65,7 @@ function Hero() {
6465
return (
6566
<section className="relative pt-32 pb-24 overflow-hidden">
6667
<ConnectionGrid />
67-
<div className="absolute inset-0 bg-gradient-radial from-primary/8 via-transparent to-transparent pointer-events-none" />
68-
<div className="absolute top-1/4 left-1/3 w-[500px] h-[500px] bg-accent-cyan/5 rounded-full blur-3xl pointer-events-none" />
68+
<GlowOrbs preset="section" tone="cyan" />
6969

7070
<div className="max-w-7xl mx-auto px-8 relative z-10 text-center">
7171
<Reveal>

ui/apps/website/src/pages/getting-started/StepPath.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
BuildingOffice2Icon,
55
ServerIcon,
66
} from "@heroicons/react/24/outline";
7+
import { GlowOrbs } from "@shellhub/design-system/components";
78
import {
89
Badge,
910
Button,
@@ -27,8 +28,7 @@ export function StepPath({ onSelectCloud, onSelectSelfHosted }: StepPathProps) {
2728
<Reveal delay={0}>
2829
<ShimmerCard className="h-full">
2930
<div className="relative bg-card border border-primary/30 rounded-xl p-8 flex flex-col h-full hover:border-primary/50 transition-all duration-300 shadow-[0_0_40px_rgba(102,122,204,0.15)] overflow-hidden">
30-
<div className="absolute inset-0 bg-gradient-to-br from-primary/[0.08] via-primary/[0.02] to-transparent pointer-events-none" />
31-
<div className="absolute top-0 right-0 w-40 h-40 bg-primary/[0.08] rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl pointer-events-none" />
31+
<GlowOrbs preset="corner" tone="primary" />
3232
<div className="relative flex items-center gap-3 mb-4">
3333
<IconBadge
3434
color="primary"

ui/apps/website/src/pages/getting-started/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect } from "react";
2-
import { ConnectionGrid } from "@shellhub/design-system/components";
2+
import { ConnectionGrid, GlowOrbs } from "@shellhub/design-system/components";
33
import { SiteLayout } from "@/components/SiteLayout";
44
import { StepPath } from "./StepPath";
55
import { StepSetup } from "./StepSetup";
@@ -21,7 +21,7 @@ export default function GettingStarted() {
2121
<SiteLayout className="flex flex-col">
2222
<main className="flex-1 flex flex-col items-center pt-24 pb-20 relative grid-bg">
2323
<ConnectionGrid />
24-
<div className="absolute inset-0 bg-gradient-radial from-primary/10 via-transparent to-transparent pointer-events-none" />
24+
<GlowOrbs preset="section" tone="primary" />
2525

2626
<div className="relative z-10 w-full max-w-4xl flex flex-col items-center">
2727
{/* Progress indicator */}

ui/apps/website/src/pages/how-it-works/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
IconBadge,
1818
WindowChrome,
1919
} from "@shellhub/design-system/primitives";
20+
import { GlowOrbs } from "@shellhub/design-system/components";
2021
import { ArrowRight } from "@/components/ArrowRight";
2122
import { SiteLayout } from "@/components/SiteLayout";
2223
import { Section, SectionHeader } from "@/components/marketing";
@@ -100,8 +101,7 @@ export default function HowItWorks() {
100101
{/* ── Hero ─────────────────────────────────────────────────── */}
101102
<section className="relative pt-32 pb-24 overflow-hidden">
102103
<ConnectionGrid />
103-
<div className="absolute inset-0 bg-gradient-radial from-primary/8 via-transparent to-transparent pointer-events-none" />
104-
<div className="absolute top-1/3 left-1/4 w-96 h-96 bg-accent-cyan/6 rounded-full blur-3xl pointer-events-none" />
104+
<GlowOrbs preset="section" tone="cyan" />
105105

106106
<div className="max-w-7xl mx-auto px-8 relative z-10 text-center">
107107
<Reveal>

ui/apps/website/src/pages/integrations/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
IconBadge,
2121
WindowChrome,
2222
} from "@shellhub/design-system/primitives";
23+
import { GlowOrbs } from "@shellhub/design-system/components";
2324
import { ArrowRight } from "@/components/ArrowRight";
2425
import { SiteLayout } from "@/components/SiteLayout";
2526
import { Section, SectionHeader } from "@/components/marketing";
@@ -67,8 +68,7 @@ export default function Integrations() {
6768
{/* ─────────── Hero ─────────── */}
6869
<section className="relative pt-32 pb-24 overflow-hidden">
6970
<ConnectionGrid />
70-
<div className="absolute inset-0 bg-gradient-radial from-primary/8 via-transparent to-transparent pointer-events-none" />
71-
<div className="absolute top-1/3 left-1/4 w-96 h-96 bg-accent-cyan/6 rounded-full blur-3xl pointer-events-none" />
71+
<GlowOrbs preset="section" tone="cyan" />
7272

7373
<div className="max-w-7xl mx-auto px-8 relative z-10 text-center">
7474
<Reveal>

0 commit comments

Comments
 (0)