Files
ar.badieiandCursor c2376b3b9e
Build & Push Docker Image / build (push) Has been cancelled
feat: redesign landing page with brand system and section blocks
Refresh the homepage around new hero/services/roadmap sections, shared design tokens, and updated brand assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 17:00:55 +03:30

149 lines
5.6 KiB
TypeScript

import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { type as typeScale } from "@/lib/typography"
import { cn } from "@/lib/utils"
/**
* Unified button system
*
* Variants (marketing hierarchy):
* - default → Primary (main CTA / conversion)
* - secondary → Secondary (bordered supporting action)
* - ghost → Ghost (minimal / nav)
*
* Aliases kept for shadcn / existing blocks:
* - outline → same as secondary
*
* Utility variants:
* - destructive, link
*
* Surfaces:
* - default → normal page canvas
* - brand → teal / dark marketing panels (inverse contrast)
*
* Sizes: sm | default (medium) | lg | icon-sm | icon | icon-lg
*
* Persian text: padding and height are sized for IRANSansX so glyphs
* are not clipped; labels use `type.button` / `type["button-lg"]`.
*/
const buttonVariants = cva(
[
"inline-flex items-center justify-center whitespace-nowrap",
"rounded-[var(--btn-radius)]",
"transition-[color,background-color,border-color,box-shadow,opacity,transform]",
"duration-200 ease-out",
"outline-none select-none shrink-0",
"disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none",
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
"[&_svg:not([class*='size-'])]:size-[var(--btn-icon-size)]",
"focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:border-ring",
"active:scale-[0.98]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
].join(" "),
{
variants: {
variant: {
// Primary — brand neon rim via shadow-glow-*
default:
"bg-primary text-primary-foreground shadow-glow-sm hover:bg-primary-hover hover:shadow-glow-primary active:bg-primary-active",
// Secondary (bordered) — preferred name
secondary:
"border border-surface bg-transparent text-foreground shadow-surface-sm hover:border-primary/45 hover:bg-accent hover:text-accent-foreground hover:shadow-glow-sm active:bg-accent/80 dark:border-white/18 dark:hover:border-primary/50 dark:hover:bg-accent",
// Alias of secondary (shadcn / imported blocks)
outline:
"border border-surface bg-transparent text-foreground shadow-surface-sm hover:border-primary/45 hover:bg-accent hover:text-accent-foreground hover:shadow-glow-sm active:bg-accent/80 dark:border-white/18 dark:hover:border-primary/50 dark:hover:bg-accent",
// Ghost
ghost:
"bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-white/8",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 hover:shadow-md focus-visible:ring-destructive/25 dark:focus-visible:ring-destructive/40 active:bg-destructive/85",
link:
"bg-transparent text-primary underline-offset-4 hover:underline hover:text-primary-hover active:text-primary-active",
},
surface: {
default: "",
/** Inverse contrast for brand / dark marketing panels */
brand: "",
},
size: {
sm: cn(
"h-[var(--btn-height-sm)] gap-[var(--btn-gap-sm)] px-[var(--btn-px-sm)] [--btn-icon-size:0.875rem] has-[>svg]:px-[calc(var(--btn-px-sm)-0.125rem)]",
typeScale.button
),
default: cn(
"h-[var(--btn-height-md)] gap-[var(--btn-gap-md)] px-[var(--btn-px-md)] [--btn-icon-size:1rem] has-[>svg]:px-[calc(var(--btn-px-md)-0.25rem)]",
typeScale.button
),
lg: cn(
"h-[var(--btn-height-lg)] gap-[var(--btn-gap-lg)] px-[var(--btn-px-lg)] [--btn-icon-size:1.25rem] has-[>svg]:px-[calc(var(--btn-px-lg)-0.25rem)]",
typeScale["button-lg"]
),
"icon-sm":
"size-[var(--btn-height-sm)] gap-0 p-0 [--btn-icon-size:0.875rem]",
icon: "size-[var(--btn-height-md)] gap-0 p-0 [--btn-icon-size:1rem]",
"icon-lg":
"size-[var(--btn-height-lg)] gap-0 p-0 [--btn-icon-size:1.25rem]",
},
},
compoundVariants: [
{
variant: "default",
surface: "brand",
className:
"bg-white text-brand-dark shadow-glow-sm hover:bg-brand-light hover:text-brand-dark hover:shadow-glow-primary active:bg-brand-light/90",
},
{
variant: ["secondary", "outline"],
surface: "brand",
className:
"border-white/40 bg-transparent text-white shadow-none hover:border-white/60 hover:bg-white/10 hover:text-white hover:shadow-[0_0_0_1px_rgb(255_255_255_/_0.35),0_0_18px_-4px_rgb(255_255_255_/_0.35)] active:bg-white/15",
},
{
variant: "ghost",
surface: "brand",
className:
"text-white hover:bg-white/10 hover:text-white active:bg-white/15",
},
{
variant: "link",
surface: "brand",
className: "text-white hover:text-white/90",
},
],
defaultVariants: {
variant: "default",
surface: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
surface,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
data-variant={variant ?? "default"}
data-surface={surface ?? "default"}
data-size={size ?? "default"}
className={cn(buttonVariants({ variant, size, surface }), className)}
{...props}
/>
)
}
export { Button, buttonVariants }