GODRICH

AI Landing Page Design Kit: 9 Glowing Orb Sections

AI landing page design has gone AI-native without painting a gradient background. The page stays one flat dark color, and a single ball of light — an orb —

An orb is nothing more than a circle filled with stacked radial gradients: a white highlight near its upper left, a colored body, and a wider ring of light sitting behind it. There is no photograph and no filter: blur anywhere in this kit, which is why a page full of them still weighs almost nothing. Scroll the one-pager below and count how many things are actually lit at any one moment.

Open the full page

That page comes apart into nine pieces, and the grid below keeps them in scroll order rather than ranked by how impressive they look: the first screen, the nav that sits above it, four blocks of body content, the sign-up band, the footer, and last, the controls that turn up between all of them. Popularity would have put the CTA band first, but nobody meets a CTA band first.

Auto-plays · click a tile to jump to its section · all nine in one zip

Every section was rendered at 480×300 and measured frame by frame, so the light budget is a number rather than an impression. The cumulative moved area across all nine adds up to 81.368 percent of one frame, and the drifting CTA band alone accounts for 33.242 of that — a little over 40 percent of the entire kit's motion in a single section.

Section Page behind it Moved area Avg intensity Frames with motion
01 Rising-orb hero ink #17141a 9.735% 43.7 23/23
02 Gliding orb-dot nav ink 2.076% 48.5 15/23
03 Glimmer-response card grid ink 10.699% 48.7 18/23
04 Waking feature list ink 4.368% 40.5 23/23
05 Orb-badge pricing white #ffffff 0.681% 36.2 16/23
06 Orb-avatar testimonials ink 10.181% 40.9 17/23
07 Drifting-orb CTA band orange #ff4d1f 33.242% 25.5 18/23
08 Orbit-beacon footer white 6.163% 70.5 23/23
09 Orb buttons & chat input ink 4.223% 40.3 12/23

Six of the nine sit directly on the ink stage. Pricing is the only component that puts orbs on white; the CTA band and the footer keep their own ink block and simply change the color of the page around it, which is how you check that the style survives a bright section.

01Rising-orb hero

A 104px sphere floats up and down inside a 152px box while the soft ring of light behind it swells on the same two-second breath. Four gradients are stacked to build it: a white highlight, a sky-blue cheek, a lilac reflection on the far side, and a deep blue body that sinks almost to the stage color at its rim. Both loops end on the value they started with, so the hero never snaps back.

radial-gradienttranslateYscalearia-hidden
.ho__orb {
  background:
    radial-gradient(circle at 34% 28%, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 44%),
    radial-gradient(circle at 28% 66%, rgba(110, 200, 255, .9), rgba(110, 200, 255, 0) 58%),
    radial-gradient(circle at 74% 72%, rgba(185, 160, 255, .8), rgba(185, 160, 255, 0) 56%),
    radial-gradient(circle at 50% 50%, rgba(47, 109, 246, .95) 10%, rgba(23, 20, 26, .96) 92%);
  animation-name: hoFloat; animation-duration: $duration; animation-timing-function: $easing;
  animation-iteration-count: infinite;
}
@keyframes hoFloat {
  0%, 100% { transform: translateY(7px); }
  50% { transform: translateY(-7px); }
}
@keyframes hoPulse {
  0%, 100% { transform: scale(.82); opacity: .5; }
  50% { transform: scale(1); opacity: .9; }
}

02Gliding orb-dot nav

An 8px dot lives under the five nav links and slides across to whichever one is current. Its five stopping points ship as CSS variables that the demo refills on load from each tab's own offsetLeft and width, so the dot lands centered on the label in any language rather than near it, and the preview loop visits three of them before returning home at 86 percent of the cycle. A real click moves the same dot through a 300ms transition, and the link it lands on carries aria-current="page".

translateXtransitionaria-current
.nv__dot {
  --p1: 1.5px; --p2: 38px; --p3: 80px; --p4: 122px; --p5: 164px;
  transform: translateX(var(--p1));
  transition: transform $duration $easing;
}
@keyframes nvGlide {
  0%, 8% { transform: translateX(var(--p1)); }
  24%, 34% { transform: translateX(var(--p3)); }
  50%, 62% { transform: translateX(var(--p5)); }
  86%, 100% { transform: translateX(var(--p1)); }
}

03Glimmer-response card grid

Four cards each carry a 28px orb, and pointing at one raises the card 2px, deepens its shadow, and grows the orb to 1.35 scale from its own center. The preview walks that same state through the four cards at 120ms intervals. Nothing tracks the cursor, so the whole grid costs zero JavaScript.

scaletranslateYbox-shadowgrid-template-columns
.cg__card:hover, .cg__card:focus-visible {
  transform: translateY(-2px); box-shadow: $shadow-raised;
}
.cg__card:hover .cg__orb, .cg__card:focus-visible .cg__orb { transform: scale(1.35); }
@keyframes cgLift {
  0%, 8% { transform: translateY(0); box-shadow: 0 2px 6px rgba(23, 20, 26, .28); }
  22%, 46% { transform: translateY(-2px); box-shadow: $shadow-raised; }
  62%, 100% { transform: translateY(0); box-shadow: 0 2px 6px rgba(23, 20, 26, .28); }
}
@keyframes cgOrb {
  0%, 8% { transform: scale(1); }
  22%, 46% { transform: scale(1.35); }
  62%, 100% { transform: scale(1); }
}

04Waking feature list

Six rows brighten in turn down the list, each one starting 334ms after the row above it. The bright window shuts at 16 percent of the two-second loop, which is 320ms — just under the 334ms gap, so two rows are never lit at once. The label brightens with its orb, so the row that is awake is readable even with color stripped out.

animation-delayopacityradial-gradient
@mixin orbSkin($hue) {
  background:
    radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .92) 0%, rgba(255, 255, 255, 0) 44%),
    radial-gradient(circle at 52% 54%, $hue 0%, rgba($hue, .45) 58%, rgba($subject-ink, .92) 100%);
}
.fl__orb { width: 14px; height: 14px; border-radius: 50%; box-shadow: 0 0 10px rgba($color, .5); @include orbSkin($color); }
.fl.is-demo .fl__row:nth-child(2) .fl__orb, .fl.is-demo .fl__row:nth-child(2) .fl__label { animation-delay: 334ms; }
@keyframes flWake {
  0%   { opacity: .32; transform: scale(.86); }
  5%   { opacity: 1;   transform: scale(1.18); }
  11%  { opacity: 1;   transform: scale(1.06); }
  16%  { opacity: .32; transform: scale(.86); }
  100% { opacity: .32; transform: scale(.86); }
}

05Orb-badge pricing

A 28px badge bobs 6px above the recommended plan once per second, and that card is the only one with a firmer border and a deeper shadow. All three plans keep the same button and the same text color, so the recommendation is carried by weight rather than by a color change. At 0.681 percent of moved area this is the quietest thing in the kit, and it is the only component whose orb sits on white.

translateYbox-shadowborder-color
.pr__orb {
  position: absolute; top: -14px; left: 50%; width: 28px; height: 28px; border-radius: 50%;
  background:
    radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .95) 0%, rgba(255, 255, 255, 0) 46%),
    radial-gradient(circle at 52% 56%, $color 0%, rgba($color, .9) 54%, rgba($subject-ink, .82) 100%);
  box-shadow: 0 4px 12px rgba($color, .45);
  transform: translateX(-50%);
}
.pr__card--pick { border-color: rgba($subject-ink, .32); box-shadow: $shadow-raised; }
@keyframes prBob {
  0%   { transform: translateX(-50%) translateY(0); }
  50%  { transform: translateX(-50%) translateY(-6px); }
  100% { transform: translateX(-50%) translateY(0); }
}

06Orb-avatar testimonials

Each of the three quote cards gets a 28px orb avatar in its own hue — blue, pink, mint — in place of a stock headshot or a customer logo. Hovering a card lifts it 4px and takes its orb from 78 percent opacity to full at 1.18 scale. The preview steps through the three cards 667ms apart, which is the two-second loop divided three ways.

radial-gradientscaletranslateY
.tm__orb--first  { @include orbSkin($subject-blue); }
.tm__orb--second { @include orbSkin($subject-pink); }
.tm__orb--third  { @include orbSkin($subject-mint); }
.tm__card:hover .tm__orb, .tm__card:active .tm__orb { opacity: 1; transform: scale(1.18); }
@keyframes tmOrbPop {
  0%   { opacity: .78; transform: scale(1); }
  12%  { opacity: 1;   transform: scale(1.18); }
  28%  { opacity: 1;   transform: scale(1.14); }
  40%  { opacity: .78; transform: scale(1); }
  100% { opacity: .78; transform: scale(1); }
}

07Drifting-orb CTA band

A single 208px orb drifts behind the band, 34 percent of its own width to either side of center, which is why this one section moves 33.242 percent of the frame. overflow: hidden on the band clips it, so the light never leaks onto the orange page around it. The button in front is solid cream and lifts 1px on hover — in this style the button is the one thing that must not glow.

translateXradial-gradienttranslateY
.cb__orb {
  position: absolute; left: 50%; top: 50%;
  margin-left: -104px; margin-top: -104px;
  width: 208px; height: 208px; border-radius: 50%; z-index: 0; pointer-events: none;
  background: radial-gradient(circle at 50% 50%,
    rgba($color, .55) 0%,
    rgba($subject-blue, .40) 38%,
    rgba($subject-blue, 0) 70%);
  animation-name: orbDrift;
  animation-duration: $duration;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
@keyframes orbDrift {
  0%   { transform: translateX(-34%); }
  50%  { transform: translateX(34%); }
  100% { transform: translateX(-34%); }
}

08Orbit-beacon footer

Three mini orbs travel along the footer's top rule, two of them on a two-second crossing and the quick one on half that. Negative delays of -400ms, -500ms, and -1200ms start them 20, 50, and 60 percent into their own loops, which spaces them along the track at roughly 15, 50, and 62 percent, so the rule is never bare in a screenshot. Each dot rides a carrier that is as wide as the rule itself, which is what lets a translateX percentage mean percent-of-track rather than percent-of-dot.

translateXanimation-delaygrid-template-columns
.fo__beacon {
  position: absolute; left: 0; top: 50%; width: 100%; height: 0;
  animation-name: beaconCross;
  animation-timing-function: $easing;
  animation-iteration-count: infinite;
  animation-fill-mode: backwards;
}
.fo__beacon--slow  { animation-duration: $duration; animation-delay: -400ms; }
.fo__beacon--fast  { animation-duration: $dur-loop * .5; animation-delay: -500ms; }
.fo__beacon--drift { animation-duration: $duration; animation-delay: -1200ms; }
@keyframes beaconCross {
  from { transform: translateX(-8%); }
  to   { transform: translateX(108%); }
}

09Orb buttons & chat input

The rounded chat field grows a single 4px halo ring the moment anything inside it takes focus, and the 8px dot tucked into the send button jumps to 1.55 scale when the button is pressed. One ring, never two — stack a second layer and the input turns into an orb of its own, competing with the real ones. Motion here comes in short bursts, which is why this section registers movement in only 12 of 23 frame comparisons.

box-shadowtransitionaria-label
.ch__field {
  box-shadow: 0 0 0 0 rgba($color, 0);
  transition: box-shadow $dur-base $easing, border-color $dur-base $easing;
}
.ch__field:focus-within {
  border-color: rgba($color, .62);
  box-shadow: 0 0 0 4px rgba($color, .34);
}
.ch__dot {
  position: absolute; right: 5px; top: 5px;
  width: 8px; height: 8px; border-radius: 50%; opacity: .6;
  background: radial-gradient(circle at 50% 50%, rgba($subject-sky, 1) 0%, rgba($subject-blue, 1) 70%);
  transform-origin: 50% 50%;
  transition: transform $dur-instant $easing, opacity $dur-instant $easing;
}
.ch__send:active .ch__dot { transform: scale(1.55); opacity: 1; }

Custom tailwind.config

The value worth centralizing here is not the palette but the shadows. A card rests at rest and deepens to raised as it lifts, and a focused input gets halo exactly once; those three lines are the whole depth system.

theme: {
  extend: {
    colors: {
      ink: "#17141a", surface: "#252228", line: "#332f38",
      cream: "#fff7e6", mute: "#beb7ad",
      sky: "#6ec8ff", brand: "#2f6df6", lilac: "#b9a0ff",
      flame: "#ff4d1f", paper: "#ffffff",
      pink: "#ff9ebb", mint: "#4cd4a6",
    },
    borderRadius: { card: "16px", control: "12px", chip: "4px", pill: "999px" },
    boxShadow: {
      rest: "0 6px 12px rgba(23,20,26,.32)",
      raised: "0 14px 24px rgba(23,20,26,.32)",
      halo: "0 0 0 4px rgba(185,160,255,.34)",
    },
    spacing: { gutter: "16px" },
    transitionTimingFunction: {
      spring: "cubic-bezier(.2,.8,.2,1)",
      pop: "cubic-bezier(.2,1.4,.4,1)",
    },
  },
},
Role Token Where it belongs
Orb hue, sections 01–03 sky #6ec8ff The top of the page, where attention is cheapest
Orb hue, sections 04–06 brand #2f6df6 The middle, where the product is explained
Orb hue, sections 07–09 lilac #b9a0ff The closing band, footer, and controls
Resting depth shadow-rest Cards and solid buttons while idle
Lifted depth shadow-raised Hover, focus, and the recommended pricing card
Focus halo shadow-halo Inputs only, one layer

A pure CSS version of the same values ships as tokens.css, so the kit works whether or not Tailwind is in the project.

Vibe-Coding Prompt

Hand this to Cursor or Claude Code with the config above already in the project. The order of the instructions matters: the rule about what may glow has to land before any color list, or the model will happily light the background too. The unabridged version travels as prompt.md inside the zip.

Build a landing page on one flat #17141a background. Nothing on the page glows except
the orbs: buttons are solid cream #fff7e6 with ink text and lift 1px on hover.
An orb is a circle of stacked radial-gradients — a white highlight at 30% 30%, a colored
body, and a wider halo circle behind it. Never a photo, never filter: blur.
Hue ladder: #6ec8ff for the first three sections, #2f6df6 for the middle three,
#b9a0ff for the last three. Give every orb aria-hidden="true" and say the active state,
the order and the recommended plan in text, borders or shadow as well.

Where it breaks (the trap)

The trap that cost the most time was the animation shorthand. Four of the nine sections stagger their orbs with animation-delay, and writing animation: name 2s ease infinite after the delay silently resets that delay to zero — every orb then wakes at the same instant and the stagger disappears. The fix is to spell the properties out one by one and add animation-fill-mode: backwards; otherwise each delayed element flashes its unanimated state for the length of its own delay: 360ms in the card grid, 1667ms on the last row of the feature list.

The second trap is a habit that every one of these sections tempts you into: building hierarchy by dimming text. Cream on the plain ink stage measures 17.11:1, and the four lines in the CTA band each cross the orb at a different height, so each has its own worst pixel: the headline passes over #53528a at 6.70:1, while the subtitle is the only line that meets the core head-on at #6e6096, where capping the core alpha at .55 keeps full-strength cream at 5.22:1. The subtitle was therefore the line that broke, because it had been set to 72 percent cream to sit quietly under the headline — that same pixel computes to 3.58:1, under the 4.5:1 floor, so the transparency came off and the subtitle is now full cream at 5.22:1 with its rank carried by size and weight instead (19px/800 against 11px/500). Over a light source you cannot dim type; opacity is a hierarchy tool that stops working the moment something glows underneath it. All nine sections, both vanilla and React, are packed in one archive that opens with e3bm95my, and the numbers above come from the same render that produced the preview loops.

Accessibility

Orbs carry no information, so every one of them is marked aria-hidden="true" and whatever they decorate is stated somewhere a screen reader can find: the nav marks its current link with aria-current, the pricing card announces itself with a border and a label, and the feature list reads in document order whether or not anything is lit. Contrast was computed from the demo colors with the relative luminance formula in the WCAG 2.1 contrast minimum, not eyeballed.

Pairing Ratio Verdict
Cream #fff7e6 on ink #17141a 17.11:1 pass
Sky #6ec8ff eyebrow on ink 9.87:1 pass
Muted body, cream at 72% over ink (#beb7ad) 9.18:1 pass
Card title, cream on ink tinted 6% sky (#1c1f28) 15.44:1 pass
Pricing text, ink on white 18.24:1 pass
Pricing subline, ink at 62% on white (#6f6d71) 5.12:1 pass
Chat placeholder, cream at 62% on the field (#2a262a) 6.32:1 pass
CTA headline, cream over its own worst pixel (#53528a) 6.70:1 pass
CTA subtitle, full cream over the orb core (#6e6096) 5.22:1 pass
The same line dimmed to 72% cream 3.58:1 fails — which is why nothing over an orb is dimmed
Brand blue #2f6df6 on ink 4.03:1 decorative icons only, never text

Under prefers-reduced-motion each section keeps its meaning and drops its movement. The hero orb stands still at 90 percent opacity, the nav dot parks on the current tab, all six feature rows sit fully lit instead of taking turns, the pricing badge holds its place above the recommended plan, and the footer beacons freeze at the 20, 50, and 62 percent marks so the rule still reads as a track. The chat field is the interesting one: its animation stops, but the focus ring and the typed-in text are forced on, because those are feedback rather than decoration. Since every orb is a radial-gradient rather than an image, a browser that cannot draw one falls back to flat color and the layout survives.

If soft light on a pale surface is closer to what you are after, the neumorphism kit solves the same nine slots with double shadows, and the trustworthy SaaS layout does it in daylight. More of these live in the design category.

FAQ

Why not just put a gradient on the background?

Because the glow has to come from somewhere specific. A full-bleed aurora lights every pixel equally, and once the background glows the orbs read as smudges rather than objects. Flattening the page to one solid color is what buys a single light source its meaning.

How many orbs can one page carry?

Roughly one per section, with one that is clearly the biggest. Across the nine measured sections here the total moved area is 81.368 percent of a frame, and 33.242 of that belongs to the CTA band alone — the imbalance is deliberate, since a page where every section moves the same amount has no climax.

Does the kit work without Tailwind?

Yes. The archive carries tokens.css with the same colors, radii, shadows, and gradient recipes as CSS custom properties, plus React versions of all nine sections with .tsx and .module.scss files. The SCSS sources compile on their own, so nothing in the kit depends on a build step you do not already have.

Enter the archive password

The password is inside this article. You will find it as you read.