{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-7",
  "title": "Hero 7: product screen that tilts into view",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Centered hero with a browser frame that leans back and settles flat as you scroll. Ships a sample dashboard drawn with theme tokens; swap in a screenshot or your own UI.",
  "dependencies": [
    "lucide-react",
    "motion@^12"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/badge.json",
    "https://ui.ballmac.com/r/browser-frame.json",
    "https://ui.ballmac.com/r/button.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/hero-7/hero-7.tsx",
      "content": "// Ballmac UI: Hero 7. https://ui.ballmac.com/blocks/hero-7\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, BarChart3, Bell, FileText, Home, Search, Settings, Users } from \"lucide-react\"\nimport { motion, useReducedMotion, useScroll, useTransform } from \"motion/react\"\n\nimport { Badge } from \"@/components/ballmac/badge\"\nimport { BrowserFrame } from \"@/components/ballmac/browser-frame\"\nimport { buttonVariants } from \"@/components/ballmac/button\"\nimport { cn } from \"@/lib/utils\"\n\ntype Action = { label: string; href: string }\n\ntype Hero7Props = Omit<React.ComponentProps<\"section\">, \"title\"> & {\n  /** Short label above the headline. */\n  eyebrow?: string\n  /** The headline. */\n  title?: string\n  /** One or two sentences under the headline. */\n  description?: string\n  /** Main call to action. */\n  primaryAction?: Action\n  /** Secondary call to action. */\n  secondaryAction?: Action\n  /** Address shown in the browser frame. */\n  url?: string\n  /** A screenshot of your product. Omit it to show the sample dashboard. */\n  screenshot?: { src: string; alt: string }\n  /** Your own UI instead of the screenshot or sample dashboard. It is laid out 1180px wide, then scaled to fit. */\n  screen?: React.ReactNode\n  /** How steeply the frame leans back before it scrolls into place, in degrees. */\n  tilt?: number\n}\n\nconst NAV = [\n  { label: \"Overview\", icon: Home, active: true },\n  { label: \"Reports\", icon: BarChart3 },\n  { label: \"Customers\", icon: Users },\n  { label: \"Invoices\", icon: FileText },\n  { label: \"Settings\", icon: Settings },\n]\n\nconst KPIS = [\n  { label: \"Revenue\", value: \"$128,430\", delta: \"+12.4%\" },\n  { label: \"Active customers\", value: \"2,847\", delta: \"+4.1%\" },\n  { label: \"Avg. invoice\", value: \"$1,260\", delta: \"+2.8%\" },\n  { label: \"Paid on time\", value: \"94.2%\", delta: \"+1.3%\" },\n]\n\n/** A sample product screen, drawn with theme tokens. Laid out at 1180 x 700. */\nfunction SampleDashboard() {\n  return (\n    <div aria-hidden=\"true\" inert className=\"bg-background text-foreground flex h-[700px] w-[1180px] text-[15px]\">\n      <aside className=\"bg-muted/40 flex w-[210px] shrink-0 flex-col gap-1 border-e p-4\">\n        <div className=\"mb-4 flex items-center gap-2.5 px-2\">\n          <span className=\"bg-foreground text-background flex size-7 items-center justify-center rounded-lg text-sm font-bold\">A</span>\n          <span className=\"text-base font-semibold\">Acme</span>\n        </div>\n        {NAV.map(({ label, icon: Icon, active }) => (\n          <span key={label} className={cn(\"flex items-center gap-3 rounded-lg px-3 py-2.5 font-medium\", active ? \"bg-background shadow-sm ring-1 ring-border\" : \"text-muted-foreground\")}>\n            <Icon className=\"size-[18px]\" />\n            {label}\n          </span>\n        ))}\n      </aside>\n      <div className=\"flex min-w-0 flex-1 flex-col gap-5 p-7\">\n        <div className=\"flex items-center justify-between\">\n          <div>\n            <p className=\"text-2xl font-semibold tracking-tight\">Overview</p>\n            <p className=\"text-muted-foreground mt-0.5 text-sm\">Last 30 days, compared to the previous period</p>\n          </div>\n          <div className=\"flex items-center gap-2.5\">\n            <span className=\"text-muted-foreground flex h-9 w-56 items-center gap-2 rounded-lg border px-3 text-sm\">\n              <Search className=\"size-4\" /> Search\n            </span>\n            <span className=\"flex size-9 items-center justify-center rounded-lg border\">\n              <Bell className=\"size-4\" />\n            </span>\n          </div>\n        </div>\n        <div className=\"grid grid-cols-4 gap-4\">\n          {KPIS.map((k) => (\n            <div key={k.label} className=\"bg-card rounded-xl border p-4\">\n              <p className=\"text-muted-foreground text-sm\">{k.label}</p>\n              <p className=\"mt-2 text-[26px] font-semibold tracking-tight\">{k.value}</p>\n              <p className=\"text-chart-2 mt-1 text-sm font-medium\">{k.delta}</p>\n            </div>\n          ))}\n        </div>\n        <div className=\"grid min-h-0 flex-1 grid-cols-[1fr_300px] gap-4\">\n          <div className=\"bg-card flex flex-col rounded-xl border p-5\">\n            <div className=\"flex items-center justify-between\">\n              <p className=\"font-medium\">Revenue</p>\n              <div className=\"text-muted-foreground flex gap-4 text-sm\">\n                <span className=\"flex items-center gap-1.5\"><span className=\"bg-chart-1 size-2 rounded-full\" /> This period</span>\n                <span className=\"flex items-center gap-1.5\"><span className=\"bg-chart-3 size-2 rounded-full\" /> Previous</span>\n              </div>\n            </div>\n            <svg viewBox=\"0 0 600 220\" preserveAspectRatio=\"none\" className=\"mt-3 min-h-0 w-full flex-1\" aria-hidden=\"true\">\n              <defs>\n                <linearGradient id=\"hero7-fill\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">\n                  <stop offset=\"0%\" style={{ stopColor: \"var(--chart-1)\", stopOpacity: 0.35 }} />\n                  <stop offset=\"100%\" style={{ stopColor: \"var(--chart-1)\", stopOpacity: 0 }} />\n                </linearGradient>\n              </defs>\n              {[55, 110, 165].map((y) => (\n                <line key={y} x1=\"0\" x2=\"600\" y1={y} y2={y} stroke=\"var(--border)\" strokeDasharray=\"3 5\" />\n              ))}\n              <path d=\"M0 170 C60 160 90 130 150 140 S250 175 310 120 S420 90 470 70 S560 60 600 40 L600 220 L0 220Z\" fill=\"url(#hero7-fill)\" />\n              <path d=\"M0 170 C60 160 90 130 150 140 S250 175 310 120 S420 90 470 70 S560 60 600 40\" fill=\"none\" stroke=\"var(--chart-1)\" strokeWidth=\"2.5\" strokeLinecap=\"round\" />\n              <path d=\"M0 185 C70 175 110 160 170 165 S270 170 330 150 S430 140 480 125 S560 115 600 105\" fill=\"none\" stroke=\"var(--chart-3)\" strokeWidth=\"2\" strokeDasharray=\"5 5\" strokeLinecap=\"round\" />\n            </svg>\n          </div>\n          <div className=\"bg-card rounded-xl border p-5\">\n            <p className=\"font-medium\">Recent invoices</p>\n            <ul className=\"mt-3 space-y-3.5\">\n              {[\n                [\"Northwind Studio\", \"$4,200\", \"Paid\"],\n                [\"Globex Corp\", \"$12,800\", \"Due\"],\n                [\"Initech\", \"$960\", \"Paid\"],\n                [\"Umbrella Labs\", \"$3,450\", \"Paid\"],\n              ].map(([n, a, s]) => (\n                <li key={n} className=\"flex items-center justify-between gap-3\">\n                  <div className=\"min-w-0\">\n                    <p className=\"truncate font-medium\">{n}</p>\n                    <p className={cn(\"text-xs\", s === \"Paid\" ? \"text-chart-2\" : \"text-chart-3\")}>{s}</p>\n                  </div>\n                  <span className=\"tabular-nums\">{a}</span>\n                </li>\n              ))}\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nfunction Hero7({\n  eyebrow = \"New · Reports that write themselves\",\n  title = \"See your whole business in one calm view.\",\n  description = \"Revenue, customers and invoices in a single dashboard that updates as it happens. No exports, no stale numbers.\",\n  primaryAction = { label: \"Start free\", href: \"#\" },\n  secondaryAction = { label: \"Book a demo\", href: \"#\" },\n  url = \"app.acme.com/overview\",\n  screenshot,\n  screen,\n  tilt = 24,\n  className,\n  ...props\n}: Hero7Props) {\n  const reduce = useReducedMotion()\n  const frameRef = React.useRef<HTMLDivElement>(null)\n  const { scrollYProgress } = useScroll({ target: frameRef, offset: [\"start 98%\", \"start 30%\"] })\n  const rotateX = useTransform(scrollYProgress, [0, 1], [reduce ? 0 : tilt, 0])\n  const scale = useTransform(scrollYProgress, [0, 1], [reduce ? 1 : 0.92, 1])\n  const lift = useTransform(scrollYProgress, [0, 1], [reduce ? 0 : 36, 0])\n\n  return (\n    <section data-slot=\"hero-7\" className={cn(\"relative isolate overflow-hidden\", className)} {...props}>\n      <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-x-0 top-0 -z-10 h-[38rem] bg-[radial-gradient(60%_60%_at_50%_0%,color-mix(in_oklch,var(--chart-1)_16%,transparent),transparent)]\" />\n      <div className=\"mx-auto max-w-6xl px-4 pt-20 sm:px-6 md:pt-28\">\n        <div className=\"mx-auto flex max-w-3xl flex-col items-center text-center\">\n          <Badge variant=\"outline\" className=\"rounded-full px-3 py-1\">{eyebrow}</Badge>\n          <h1 className=\"mt-6 text-4xl font-semibold tracking-[-0.045em] text-balance sm:text-5xl lg:text-6xl lg:leading-[1.04]\">{title}</h1>\n          <p className=\"text-muted-foreground mt-6 max-w-xl text-lg leading-relaxed text-pretty\">{description}</p>\n          <div className=\"mt-9 flex flex-wrap justify-center gap-3\">\n            <a className={buttonVariants({ size: \"lg\", shape: \"pill\" })} href={primaryAction.href}>\n              {primaryAction.label} <ArrowRight  className=\"rtl:rotate-180\"/>\n            </a>\n            <a className={buttonVariants({ variant: \"outline\", size: \"lg\", shape: \"pill\" })} href={secondaryAction.href}>{secondaryAction.label}</a>\n          </div>\n        </div>\n        <div className=\"relative mt-14 pb-16 [perspective:1600px] md:mt-20 md:pb-24\">\n          <motion.div ref={frameRef} style={{ rotateX, scale, y: lift, transformOrigin: \"50% 0%\" }} className=\"relative\">\n            <div aria-hidden=\"true\" className=\"bg-chart-1/20 absolute inset-x-[8%] -bottom-6 h-24 rounded-full blur-3xl\" />\n            <BrowserFrame\n              url={url}\n              secure\n              screenWidth={1180}\n              aspectRatio={1180 / 700}\n              className=\"shadow-[0_50px_120px_-40px_rgb(0_0_0/0.45)]\"\n              {...(screenshot && !screen ? { src: screenshot.src, alt: screenshot.alt } : {})}\n            >\n              {screen ?? (screenshot ? null : <SampleDashboard />)}\n            </BrowserFrame>\n          </motion.div>\n          <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-background to-transparent\" />\n        </div>\n      </div>\n    </section>\n  )\n}\n\nexport { Hero7, type Hero7Props }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/hero-7/hero-7.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "hero",
      "product",
      "screenshot",
      "scroll",
      "browser",
      "dashboard"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "A landing hero that lets the product do the talking. Edit eyebrow, title, description and actions; pass `screenshot` ({ src, alt }) or `screen` (any UI laid out 1180px wide) to replace the sample dashboard.",
      "whenToUse": [
        "Products with a strong UI",
        "Pages where a first-screen screenshot sells the product"
      ],
      "whenNotToUse": [
        "Developer tools led by a command (use hero-2)",
        "Products with no visual UI"
      ],
      "composesWith": [
        "header-1",
        "logo-cloud-1",
        "features-1",
        "testimonials-1"
      ],
      "a11y": [
        {
          "keys": "Scroll",
          "action": "The tilt is decorative and is turned off for reduced motion; the sample screen is hidden from assistive technology"
        }
      ],
      "customization": [
        "tilt: starting lean in degrees (0 for none)",
        "url: address shown in the browser frame",
        "screen: any ReactNode, scaled to fit"
      ]
    },
    "examples": [
      "hero-7-demo",
      "hero-7-custom"
    ],
    "url": "https://ui.ballmac.com/components/hero-7"
  },
  "categories": [
    "blocks",
    "hero"
  ],
  "type": "registry:block"
}