Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
920 changes: 920 additions & 0 deletions components/evil-buttons/thruster-button.tsx

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions components/landing/previews/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
MoviePassButtonPreview,
ShinyButtonPreview,
StickyButtonPreview,
ThrusterButtonPreview,
ThreeDButtonPreview,
TrollButtonPreview,
MinecraftButtonPreview,
Expand Down Expand Up @@ -63,6 +64,7 @@ import {
StaticMoviePassButtonPreview,
StaticShinyButtonPreview,
StaticStickyButtonPreview,
StaticThrusterButtonPreview,
StaticThreeDButtonPreview,
StaticTrollButtonPreview,
StaticMinecraftButtonPreview,
Expand All @@ -87,6 +89,7 @@ const dialButtonPreviews: Record<string, ComponentType> = {
"movie-pass": MoviePassButtonPreview,
"shiny-button": ShinyButtonPreview,
sticky: StickyButtonPreview,
"thruster-button": ThrusterButtonPreview,
"3d-button": ThreeDButtonPreview,
"troll-button": TrollButtonPreview,
"doubt-button": DoubtButtonPreview,
Expand Down Expand Up @@ -118,6 +121,7 @@ const staticButtonPreviews: Record<string, ComponentType> = {
"movie-pass": StaticMoviePassButtonPreview,
"shiny-button": StaticShinyButtonPreview,
sticky: StaticStickyButtonPreview,
"thruster-button": StaticThrusterButtonPreview,
"3d-button": StaticThreeDButtonPreview,
"troll-button": StaticTrollButtonPreview,
"doubt-button": StaticDoubtButtonPreview,
Expand Down
21 changes: 21 additions & 0 deletions components/landing/previews/simple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import MoviePassButton, {
} from "@/components/evil-buttons/movie-pass";
import ShinyButton from "@/components/evil-buttons/shiny-button";
import StickyButton from "@/components/evil-buttons/sticky";
import ThrusterButton from "@/components/evil-buttons/thruster-button";
import { ThreeDButton } from "@/components/evil-buttons/3d-button";
import { DeferredWebGLPreview } from "./shared";
import { LiveProps } from "./live-props";
Expand Down Expand Up @@ -149,6 +150,26 @@ export function StickyButtonPreview() {
);
}

export function ThrusterButtonPreview() {
const p = useDialKit(
"ThrusterButton",
{
label: "Liftoff",
thrust: [3200, 400, 8000],
tilt: [10, 0, 30],
},
{ id: "thruster-button" },
);

return (
<LiveProps>
<ThrusterButton thrust={p.thrust} tilt={p.tilt}>
{p.label}
</ThrusterButton>
</LiveProps>
);
}

export function ThreeDButtonPreview() {
const p = useDialKit(
"ThreeDButton",
Expand Down
10 changes: 10 additions & 0 deletions components/landing/previews/static.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import MinimalButton from "@/components/evil-buttons/minimal";
import MoviePassButton from "@/components/evil-buttons/movie-pass";
import ShinyButton from "@/components/evil-buttons/shiny-button";
import StickyButton from "@/components/evil-buttons/sticky";
import ThrusterButton from "@/components/evil-buttons/thruster-button";
import { ThreeDButton } from "@/components/evil-buttons/3d-button";
import { DeferredWebGLPreview } from "./shared";
import { themeColors } from "./theme";
Expand Down Expand Up @@ -251,6 +252,15 @@ export function StaticStickyButtonPreview() {
return <StickyButton>Try to Click</StickyButton>;
}

export function StaticThrusterButtonPreview() {
// The card scales its preview, so give the craft a fixed-size playground.
return (
<div className="relative h-60 w-88">
<ThrusterButton>Liftoff</ThrusterButton>
</div>
);
}

export function StaticThreeDButtonPreview() {
return <ThreeDButton>Continue</ThreeDButton>;
}
Expand Down
1 change: 1 addition & 0 deletions components/landing/showcase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const showcase: ShowcaseEntry[] = [
{ name: "MoviePassButton", href: "/docs/movie-pass", registryName: "movie-pass", variants: ["tilt", "snap"] },
{ name: "ShinyButton", href: "/docs/shiny-button", registryName: "shiny-button" },
{ name: "StickyButton", href: "/docs/sticky-button", registryName: "sticky" },
{ name: "ThrusterButton", href: "/docs/thruster-button", registryName: "thruster-button" },
{ name: "ThreeDButton", href: "/docs/3d-button", registryName: "3d-button" },
{ name: "TrollButton", href: "/docs/troll-button", registryName: "troll-button" },
{ name: "DoubtButton", href: "/docs/doubt-button", registryName: "doubt-button" },
Expand Down
2 changes: 1 addition & 1 deletion components/live-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function LivePreview({ source, fallback }: LivePreviewProps) {

return (
<DocsFrame key={theme} innerClassName="flex flex-col">
<div className="flex min-h-104 items-center justify-center px-6 py-10">
<div className="relative flex min-h-104 items-center justify-center px-6 py-10">
<LivePropsProvider value={report}>
<ButtonPreview registryName={source.registryName} dial />
</LivePropsProvider>
Expand Down
2 changes: 2 additions & 0 deletions components/mdx-custom-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CliBlock } from "@/components/cli-block";
import { ClickPowerUp } from "@/components/evil-buttons/click-powerup";
import MinimalButton from "@/components/evil-buttons/minimal";
import StickyButton from "@/components/evil-buttons/sticky";
import ThrusterButton from "@/components/evil-buttons/thruster-button";
import { PreviewCard } from "@/components/preview-card";
import type { MDXComponents } from "mdx/types";
import { isValidElement, type ReactNode } from "react";
Expand Down Expand Up @@ -101,6 +102,7 @@ export function getCustomMDXComponents(): MDXComponents {
Cmd,
ClickPowerUp,
StickyButton,
ThrusterButton,
ShinyButton,
MoviePassButton,
MinimalButton,
Expand Down
68 changes: 68 additions & 0 deletions content/docs/thruster-button.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: ThrusterButton
description: A magnetic button that rests on the floor of its container, then flies to the cursor anywhere inside it on compressed-air jets, trailing WebGL smoke, and lands where it is when the cursor leaves.
category: Mischief
---

## Preview

<PreviewCard live>
<ThrusterButton>Liftoff</ThrusterButton>
</PreviewCard>

## Install

<Cmd>@evilbuttons/thruster-button</Cmd>

## Usage

The button fills its nearest positioned ancestor and treats it as its playground, so give it a `relative` container with some height.

```tsx
import ThrusterButton from "@/components/evil-buttons/thruster-button";

export function ButtonDemo() {
return (
<div className="relative h-80 w-full">
<ThrusterButton>Launch</ThrusterButton>
</div>
);
}
```

## Variants

### Afterburner

High thrust makes it snap across the playground and slam on the brakes.

<PreviewCard>
<ThrusterButton thrust={7000}>Full Burn</ThrusterButton>
</PreviewCard>

### Low Fuel

Weak jets make it drift over slowly, sag and overshoot before it settles.

<PreviewCard>
<ThrusterButton thrust={900} tilt={18}>Running on Fumes</ThrusterButton>
</PreviewCard>

## Props

The component spreads native `button` props and forwards its ref to the button.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `React.ReactNode` | - | The button label or content. |
| `thrust` | `number` | `3200` | Net jet acceleration in px/s². Lower values drift, higher values snap. |
| `tilt` | `number` | `10` | Maximum bank in degrees while the craft accelerates sideways. |
| `playgroundClassName` | `string` | - | Classes for the playground, which is `absolute inset-0` by default. Use it to inset the floor or walls. |
| `className` | `string` | - | Extra classes passed to the button. |

## Good to know

- It starts parked on the floor. Move the cursor into the playground and it lifts off and flies its center onto the pointer, bouncing off the walls and ceiling. Move the cursor out and it comes straight down and lands where it is.
- Two air jets sit on each of the bottom, left and right edges. There are no top jets, so it descends by easing off the bottom pair and letting gravity pull it down, and it hisses softly to hold a hover.
- The smoke is a small WebGL2 fluid simulation (advection, pressure projection and vorticity confinement) filling the playground. The jets inject velocity and density into it, so the gas curls, rolls along the floor on landing and drifts where it was vented. Without WebGL2 the button still flies, just without smoke.
- The loop stops once it has landed and the smoke clears. Reduced motion and `disabled` keep it parked.
1 change: 1 addition & 0 deletions public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ npx shadcn@latest add @evilbuttons/<component-name>

- [ClickPowerUp](https://www.evilbuttons.com/docs/click-power-up): A bracketed button that arms on hover and powers up with an accent flash and bracket burst on every click.
- [StickyButton](https://www.evilbuttons.com/docs/sticky-button): A magnetic button whose body and label are pulled toward the cursor from a wide field, with parallax depth and tilt.
- [ThrusterButton](https://www.evilbuttons.com/docs/thruster-button): A magnetic button that rests on the floor of its container, then flies to the cursor anywhere inside it on compressed-air jets, trailing WebGL smoke, and lands where it is when the cursor leaves.
- [ShinyButton](https://www.evilbuttons.com/docs/shiny-button): A glossy, gradient-styled button with a layered inner glow and a glint that sweeps across on every click.
- [MoviePassButton](https://www.evilbuttons.com/docs/movie-pass): A cinema ticket button that tears along its perforation on click, hinging open or snapping apart.
- [MinimalButton](https://www.evilbuttons.com/docs/minimal-button): A quiet dark button with a hairline ring, inner highlight, soft drop shadow, and a chevron that nudges on hover.
Expand Down
9 changes: 9 additions & 0 deletions public/r/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
"components/evil-buttons/sticky.tsx"
]
},
{
"name": "thruster-button",
"type": "registry:ui",
"title": "ThrusterButton",
"description": "A magnetic button that rests on the floor of its container, then flies to the cursor anywhere inside it on compressed-air jets, trailing WebGL smoke, and lands where it is when the cursor leaves.",
"files": [
"components/evil-buttons/thruster-button.tsx"
]
},
{
"name": "shiny-button",
"type": "registry:ui",
Expand Down
20 changes: 20 additions & 0 deletions public/r/thruster-button.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions registry.components.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
"file": "components/evil-buttons/sticky.tsx",
"dependencies": ["motion", "clsx", "tailwind-merge"]
},
{
"name": "thruster-button",
"exportName": "ThrusterButton",
"docSlug": "thruster-button",
"title": "ThrusterButton",
"description": "A magnetic button that rests on the floor of its container, then flies to the cursor anywhere inside it on compressed-air jets, trailing WebGL smoke, and lands where it is when the cursor leaves.",
"file": "components/evil-buttons/thruster-button.tsx",
"dependencies": ["motion", "clsx", "tailwind-merge"]
},
{
"name": "shiny-button",
"exportName": "ShinyButton",
Expand Down
Loading