From acc04343fb09fef72b1cca9a33edbe47576cfb33 Mon Sep 17 00:00:00 2001 From: Radiumcoders Date: Thu, 2 Jul 2026 18:00:47 +0530 Subject: [PATCH 1/4] Add DialKit and redesign landing page with button preview selector Replace the grid-based landing page with a focused preview experience: users pick a button from a shadcn Select dropdown and see it centered on the page. Also installs DialKit with DialRoot in the root layout. --- app/layout.tsx | 3 + components/landing/landing-page.tsx | 180 +++++++++++--------------- components/ui/select.tsx | 192 ++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 34 +++++ 5 files changed, 303 insertions(+), 107 deletions(-) create mode 100644 components/ui/select.tsx diff --git a/app/layout.tsx b/app/layout.tsx index 4e1cd17..6c7b732 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,6 @@ import { Geist, Geist_Mono, Inter, Doto } from "next/font/google"; +import { DialRoot } from "dialkit"; +import "dialkit/styles.css"; import "./globals.css"; import { JsonLd } from "@/components/seo/json-ld"; import { cn } from "@/lib/utils"; @@ -56,6 +58,7 @@ export default function RootLayout({ {children} + ); diff --git a/components/landing/landing-page.tsx b/components/landing/landing-page.tsx index a2bf650..b1a6dc5 100644 --- a/components/landing/landing-page.tsx +++ b/components/landing/landing-page.tsx @@ -4,9 +4,9 @@ import { DeferredMount } from "@/components/landing/deferred-mount"; import { FitToContainer } from "@/components/landing/fit-to-container"; import { ThemeSync } from "@/components/theme-sync"; import { siteConfig } from "@/lib/seo"; -import { ArrowUpRight, Copy } from "@phosphor-icons/react"; +import { ArrowUpRight } from "@phosphor-icons/react"; import Link from "next/link"; -import { type ReactNode, useEffect, useRef, useState } from "react"; +import { type ReactNode, useState } from "react"; import { AquaButton } from "@/components/evil-buttons/aqua-button"; import { BrutalButton } from "@/components/evil-buttons/brutal-button"; @@ -37,6 +37,13 @@ import { DemonicButton } from "../evil-buttons/demonic-button"; import { PillButton } from "@/components/evil-buttons/pill-button"; import { ConfettiButton } from "@/components/evil-buttons/confetti-button"; import { HoldConfirmButton } from "@/components/evil-buttons/hold-confirm-button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; type ButtonShowcase = { name: string; @@ -45,9 +52,6 @@ type ButtonShowcase = { render: () => ReactNode; }; -// Static stand-in shown for the always-on WebGL buttons until their grid cell -// scrolls into view. Approximates the real button's footprint (rounded, dark -// pill) so swapping in the live shader causes no layout shift. function WebGLPlaceholder({ label }: { label: string }) { return ( @@ -254,122 +258,84 @@ const showcase: ButtonShowcase[] = [ }, ]; -function ButtonCell({ item }: { item: ButtonShowcase }) { - const [copied, setCopied] = useState(false); - const timerRef = useRef(null); - - useEffect(() => { - return () => { - if (timerRef.current !== null) clearTimeout(timerRef.current); - }; - }, []); - - const command = `npx shadcn@latest add @evilbuttons/${item.registryName}`; - - async function handleCopy(e: React.MouseEvent) { - e.preventDefault(); - e.stopPropagation(); - await navigator.clipboard.writeText(command); - setCopied(true); - if (timerRef.current !== null) clearTimeout(timerRef.current); - timerRef.current = window.setTimeout(() => setCopied(false), 2000); - } +export function LandingPage() { + const [selected, setSelected] = useState(showcase[0].registryName); + const active = showcase.find((item) => item.registryName === selected) ?? showcase[0]; return ( -
-
- {item.name} -
-
- {item.render()} -
-
+
+ + +
- - Docs + {siteConfig.name} + + Evil Buttons + - -
-
- ); -} -export function LandingPage() { - return ( -
- +
+ -
-
-
- {siteConfig.name} -

- Evil
Buttons -

-
+ Docs + -

- Animated buttons, built with an evil touch. -

-

- A shadcn/ui registry of {showcase.length} interactive button - components. Live previews, copy-paste docs, one-command CLI - installs. -

-
- - Browse Docs - - - GitHub - -
-
- -
-

- {showcase.length} Components -

-

- © {new Date().getFullYear()} {siteConfig.author.name} -

-
-
- {showcase.map((item) => ( - - ))} +
+
+ + {active.render()} +
+ +
+

+ {showcase.length} components +

+
+ + Browse Docs + + + GitHub + +
+
); -} +} \ No newline at end of file diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000..f03ac34 --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,192 @@ +"use client" + +import * as React from "react" +import { Select as SelectPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { CaretDownIcon, CheckIcon, CaretUpIcon } from "@phosphor-icons/react" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "item-aligned", + align = "center", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/package.json b/package.json index 039d4b0..1c4fcac 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "canvas-confetti": "^1.9.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dialkit": "^1.3.0", "fumadocs-core": "^16.8.5", "fumadocs-mdx": "^14.3.2", "motion": "^12.38.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9e5a7d..ff9f264 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + dialkit: + specifier: ^1.3.0 + version: 1.3.0(motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) fumadocs-core: specifier: ^16.8.5 version: 16.8.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) @@ -2333,6 +2336,30 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dialkit@1.3.0: + resolution: {integrity: sha512-//tnKhG+6gWhH3h9L7RpRPQZJkRBj0qQyUu1Og1l0VI6Nn/7iKaZZDFs/hG+Jsrtvwp4hlLrrmzj8M6hkXR3EA==} + peerDependencies: + motion: '>=11.0.0' + motion-v: '>=2.0.0' + react: '>=18.0.0' + react-dom: '>=18.0.0' + solid-js: '>=1.6.0' + svelte: '>=5.8.0' + vue: '>=3.3.0' + peerDependenciesMeta: + motion-v: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -6760,6 +6787,13 @@ snapshots: dependencies: dequal: 2.0.3 + dialkit@1.3.0(motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + diff@8.0.4: {} doctrine@2.1.0: From 03af01c95f5da753411c4c3b5bb78351bf941b7d Mon Sep 17 00:00:00 2001 From: Radiumcoders Date: Thu, 2 Jul 2026 18:21:01 +0530 Subject: [PATCH 2/4] Add DialKit landing preview with theme-aware controls Redesign the landing page with a shadcn Select picker and centered button preview. Each button has its own DialKit panel with tailored controls. Sync DialKit theme and styling with the site, position the panel top-left below the header, and remove navbar/footer borders. --- app/globals.css | 84 ++++++ app/layout.tsx | 4 +- components/dial-root.tsx | 10 + components/landing/landing-page.tsx | 269 ++------------------ components/landing/previews/index.tsx | 78 ++++++ components/landing/previews/interaction.tsx | 205 +++++++++++++++ components/landing/previews/shared.tsx | 26 ++ components/landing/previews/simple.tsx | 99 +++++++ components/landing/previews/theme.ts | 46 ++++ components/landing/previews/utility.tsx | 91 +++++++ components/landing/previews/visual.tsx | 246 ++++++++++++++++++ components/landing/showcase.ts | 37 +++ hooks/use-app-theme.ts | 30 +++ 13 files changed, 971 insertions(+), 254 deletions(-) create mode 100644 components/dial-root.tsx create mode 100644 components/landing/previews/index.tsx create mode 100644 components/landing/previews/interaction.tsx create mode 100644 components/landing/previews/shared.tsx create mode 100644 components/landing/previews/simple.tsx create mode 100644 components/landing/previews/theme.ts create mode 100644 components/landing/previews/utility.tsx create mode 100644 components/landing/previews/visual.tsx create mode 100644 components/landing/showcase.ts create mode 100644 hooks/use-app-theme.ts diff --git a/app/globals.css b/app/globals.css index adffb0d..ce5623c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -251,3 +251,87 @@ html.dark .docs-content .shiki span { font-weight: var(--shiki-dark-font-weight) !important; text-decoration: var(--shiki-dark-text-decoration) !important; } + +/* DialKit — match Evil Buttons page tokens and sharp corners */ +.dialkit-root { + --dial-radius: 0; + --dial-panel-offset-top: 5.5rem; + font-family: var(--font-sans), system-ui, sans-serif; +} + +.dialkit-panel[data-position="top-left"] { + top: var(--dial-panel-offset-top) !important; + left: 1.5rem !important; +} + +.dialkit-root[data-theme="light"] { + --dial-surface: color-mix(in oklab, var(--foreground) 4%, transparent); + --dial-surface-hover: color-mix(in oklab, var(--foreground) 8%, transparent); + --dial-surface-active: color-mix(in oklab, var(--foreground) 10%, transparent); + --dial-surface-subtle: color-mix(in oklab, var(--foreground) 6%, transparent); + --dial-text-root: var(--foreground); + --dial-text-section: var(--muted-foreground); + --dial-text-label: var(--muted-foreground); + --dial-text-focus: var(--foreground); + --dial-text-primary: var(--foreground); + --dial-text-secondary: var(--muted-foreground); + --dial-text-tertiary: color-mix(in oklab, var(--muted-foreground) 70%, transparent); + --dial-border: var(--border); + --dial-border-hover: color-mix(in oklab, var(--foreground) 18%, transparent); + --dial-glass-bg: var(--popover); + --dial-dropdown-bg: var(--popover); + --dial-backdrop-blur: 0px; + --dial-shadow: 0 1px 0 var(--border); + --dial-shadow-collapsed: 0 1px 0 var(--border); + --dial-shadow-dropdown: 0 8px 24px color-mix(in oklab, var(--foreground) 12%, transparent); +} + +.dialkit-root[data-theme="dark"] { + --dial-surface: color-mix(in oklab, var(--foreground) 6%, transparent); + --dial-surface-hover: color-mix(in oklab, var(--foreground) 10%, transparent); + --dial-surface-active: color-mix(in oklab, var(--foreground) 12%, transparent); + --dial-surface-subtle: color-mix(in oklab, var(--foreground) 8%, transparent); + --dial-text-root: var(--foreground); + --dial-text-section: var(--muted-foreground); + --dial-text-label: var(--muted-foreground); + --dial-text-focus: var(--foreground); + --dial-text-primary: var(--foreground); + --dial-text-secondary: var(--muted-foreground); + --dial-text-tertiary: color-mix(in oklab, var(--muted-foreground) 70%, transparent); + --dial-border: var(--border); + --dial-border-hover: color-mix(in oklab, var(--foreground) 22%, transparent); + --dial-glass-bg: var(--popover); + --dial-dropdown-bg: var(--popover); + --dial-backdrop-blur: 0px; + --dial-shadow: 0 1px 0 var(--border); + --dial-shadow-collapsed: 0 1px 0 var(--border); + --dial-shadow-dropdown: 0 12px 32px color-mix(in oklab, black 45%, transparent); +} + +.dialkit-panel-inner { + border-radius: 0 !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + padding-bottom: 12px !important; +} + +.dialkit-root + .dialkit-folder-root + > .dialkit-folder-content + > .dialkit-folder-inner { + padding-bottom: 8px !important; +} + +.dialkit-root .dialkit-panel-inner[data-collapsed="true"] { + border-radius: 0 !important; +} + +.dialkit-root .dialkit-toggle-thumb, +.dialkit-root .dialkit-shortcuts-row, +.dialkit-root .dialkit-shortcuts-row-key, +.dialkit-root .dialkit-select-trigger, +.dialkit-root .dialkit-select-content, +.dialkit-root .dialkit-preset-item, +.dialkit-root .dialkit-color-swatch { + border-radius: 0 !important; +} diff --git a/app/layout.tsx b/app/layout.tsx index 6c7b732..4998583 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,5 @@ import { Geist, Geist_Mono, Inter, Doto } from "next/font/google"; -import { DialRoot } from "dialkit"; +import { AppDialRoot } from "@/components/dial-root"; import "dialkit/styles.css"; import "./globals.css"; import { JsonLd } from "@/components/seo/json-ld"; @@ -58,7 +58,7 @@ export default function RootLayout({ {children} - + ); diff --git a/components/dial-root.tsx b/components/dial-root.tsx new file mode 100644 index 0000000..6dcb747 --- /dev/null +++ b/components/dial-root.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { DialRoot } from "dialkit"; +import { useAppTheme } from "@/hooks/use-app-theme"; + +export function AppDialRoot() { + const theme = useAppTheme(); + + return ; +} \ No newline at end of file diff --git a/components/landing/landing-page.tsx b/components/landing/landing-page.tsx index b1a6dc5..c4e30e9 100644 --- a/components/landing/landing-page.tsx +++ b/components/landing/landing-page.tsx @@ -1,42 +1,14 @@ "use client"; -import { DeferredMount } from "@/components/landing/deferred-mount"; import { FitToContainer } from "@/components/landing/fit-to-container"; +import { ButtonPreview } from "@/components/landing/previews"; +import { showcase } from "@/components/landing/showcase"; import { ThemeSync } from "@/components/theme-sync"; +import { useAppTheme } from "@/hooks/use-app-theme"; import { siteConfig } from "@/lib/seo"; import { ArrowUpRight } from "@phosphor-icons/react"; import Link from "next/link"; -import { type ReactNode, useState } from "react"; - -import { AquaButton } from "@/components/evil-buttons/aqua-button"; -import { BrutalButton } from "@/components/evil-buttons/brutal-button"; -import { CaptchaButton } from "@/components/evil-buttons/captcha-button"; -import ChromeButton from "@/components/evil-buttons/chrome-button"; -import { ClickPowerUp } from "@/components/evil-buttons/click-powerup"; -import { CommandButton } from "@/components/evil-buttons/command-button"; -import { CooldownButton } from "@/components/evil-buttons/cooldown-button"; -import { CopyButton } from "@/components/evil-buttons/copy-button"; -import { DoubtButton } from "@/components/evil-buttons/doubt-button"; -import DitherButton from "@/components/evil-buttons/dither-button"; -import EvilEyeButton from "@/components/evil-buttons/evil-eye-button"; -import { FrameButton } from "@/components/evil-buttons/frame-button"; -import GlitchButton from "@/components/evil-buttons/glitch-button"; -import GridButton from "@/components/evil-buttons/grid-button"; -import { HighlightButton } from "@/components/evil-buttons/highlight-button"; -import { HoldButton } from "@/components/evil-buttons/hold-button"; -import MinimalButton from "@/components/evil-buttons/minimal"; -import { MorphStatusButton } from "@/components/evil-buttons/morph-status-button"; -import MoviePassButton from "@/components/evil-buttons/movie-pass"; -import { RevealButton } from "@/components/evil-buttons/reveal-button"; -import ShinyButton from "@/components/evil-buttons/shiny-button"; -import { SlideToDetonate } from "@/components/evil-buttons/slide-to-detonate"; -import StickyButton from "@/components/evil-buttons/sticky"; -import { ThreeDButton } from "@/components/evil-buttons/3d-button"; -import TrollButton from "@/components/evil-buttons/troll-button"; -import { DemonicButton } from "../evil-buttons/demonic-button"; -import { PillButton } from "@/components/evil-buttons/pill-button"; -import { ConfettiButton } from "@/components/evil-buttons/confetti-button"; -import { HoldConfirmButton } from "@/components/evil-buttons/hold-confirm-button"; +import { useState } from "react"; import { Select, SelectContent, @@ -45,228 +17,17 @@ import { SelectValue, } from "@/components/ui/select"; -type ButtonShowcase = { - name: string; - href: string; - registryName: string; - render: () => ReactNode; -}; - -function WebGLPlaceholder({ label }: { label: string }) { - return ( - - {label} - - ); -} - -const showcase: ButtonShowcase[] = [ - { - name: "RevealButton", - href: "/docs/reveal-button", - registryName: "reveal-button", - render: () => , - }, - { - name: "CommandButton", - href: "/docs/command-button", - registryName: "command-button", - render: () => Save, - }, - { - name: "CopyButton", - href: "/docs/copy-button", - registryName: "copy-button", - render: () => , - }, - { - name: "ClickPowerUp", - href: "/docs/click-power-up", - registryName: "click-powerup", - render: () => Doom, - }, - { - name: "DitherButton", - href: "/docs/dither-button", - registryName: "dither-button", - render: () => Run It, - }, - { - name: "HoldButton", - href: "/docs/hold-button", - registryName: "hold-button", - render: () => , - }, - { - name: "DemonicButton", - href: "/docs/demonic-button", - registryName: "demonic-button", - render: () => , - }, - { - name: "EvilEyeButton", - href: "/docs/evil-eye-button", - registryName: "evil-eye-button", - render: () => ( - }> - Doom - - ), - }, - { - name: "AquaButton", - href: "/docs/aqua-button", - registryName: "aqua-button", - render: () => Deploy Doom, - }, - { - name: "BrutalButton", - href: "/docs/brutal-button", - registryName: "brutal-button", - render: () => Click Me, - }, - { - name: "ChromeButton", - href: "/docs/chrome-button", - registryName: "chrome-button", - render: () => ( - }> - Chromy - - ), - }, - { - name: "FrameButton", - href: "/docs/frame-button", - registryName: "frame-button", - render: () => Deploy, - }, - { - name: "GlitchButton", - href: "/docs/glitch-button", - registryName: "glitch-button", - render: () => Launch, - }, - { - name: "GridButton", - href: "/docs/grid-button", - registryName: "grid-button", - render: () => Click, - }, - { - name: "HighlightButton", - href: "/docs/highlight-button", - registryName: "highlight-button", - render: () => Send, - }, - { - name: "MinimalButton", - href: "/docs/minimal-button", - registryName: "minimal", - render: () => Apply, - }, - { - name: "MoviePassButton", - href: "/docs/movie-pass", - registryName: "movie-pass", - render: () => Deploy Doom, - }, - { - name: "ShinyButton", - href: "/docs/shiny-button", - registryName: "shiny-button", - render: () => Search, - }, - { - name: "StickyButton", - href: "/docs/sticky-button", - registryName: "sticky", - render: () => Try to Click, - }, - { - name: "ThreeDButton", - href: "/docs/3d-button", - registryName: "3d-button", - render: () => Continue, - }, - { - name: "TrollButton", - href: "/docs/troll-button", - registryName: "troll-button", - render: () => Click Me, - }, - { - name: "CaptchaButton", - href: "/docs/captcha-button", - registryName: "captcha-button", - render: () => Deploy Doom, - }, - { - name: "DoubtButton", - href: "/docs/doubt-button", - registryName: "doubt-button", - render: () => Delete everything, - }, - { - name: "SlideToDetonate", - href: "/docs/slide-to-detonate", - registryName: "slide-to-detonate", - render: () => Slide to detonate, - }, - { - name: "MorphStatusButton", - href: "/docs/morph-status-button", - registryName: "morph-status-button", - render: () => ( - new Promise((resolve) => setTimeout(resolve, 1200))} - > - Save changes - - ), - }, - { - name: "CooldownButton", - href: "/docs/cooldown-button", - registryName: "cooldown-button", - render: () => Send it, - }, - { - name: "PillButton", - href: "/docs/pill-button", - registryName: "pill-button", - render: () => ( - - ), - }, - { - name: "ConfettiButton", - href: "/docs/confetti-button", - registryName: "confetti-button", - render: () => Celebrate, - }, - { - name: "HoldConfirmButton", - href: "/docs/hold-confirm-button", - registryName: "hold-confirm-button", - render: () => , - }, -]; - export function LandingPage() { + const theme = useAppTheme(); const [selected, setSelected] = useState(showcase[0].registryName); - const active = showcase.find((item) => item.registryName === selected) ?? showcase[0]; + const active = + showcase.find((item) => item.registryName === selected) ?? showcase[0]; return (
-
+
- + @@ -64,7 +64,7 @@ export function LandingPage() { Docs @@ -72,22 +72,20 @@ export function LandingPage() {
-
-
- - - -
+
+ + +
-