{
  "name": "rewards-card-default",
  "type": "registry:block",
  "tier": "free",
  "dependencies": [],
  "registryDependencies": [
    "https://www.craftui.space/r/craftui-base.json"
  ],
  "files": [
    {
      "path": "components/craftui/designs/rewards-card/default.tsx",
      "content": "// Static by design — no \"use client\", no hooks, no motion. Renders on the\n// server and ships zero JS.\n//\n// Built on the \"Nova\" token set (shadcn Stone base, Blue primary, radius 0),\n// shared with the Upgrade Modal. Tailwind only — tokens are stated once below\n// as literal class strings, since Tailwind scans source text. The haze at the\n// top is the \"layer blur\" trick: one ellipse in chart-1 pushed above the card\n// and blurred by 90px, clipped by the card, so the blue fades into the white\n// with no gradient stops to tune.\n//\n// The three tiles carry hand-drawn line illustrations — single-weight ink, one\n// off-white paper shape behind each, exactly one accent per tile — drawn on a\n// 96 grid so they scale with the tile. They read as: grow your earnings →\n// the reward → the easy action.\n\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst NOVA = {\n  fg: \"text-[#0C0A09]\",\n  mutedFg: \"text-[#79716B]\",\n  card: \"bg-[#FFFFFF]\",\n  muted: \"bg-[#F5F5F4]\",\n  primaryText: \"text-[#1447E6]\",\n  primaryFgBg: \"bg-[#EFF6FF]\",\n  chart1: \"bg-[#8EC5FF]\",\n  chart5: \"bg-[#193CB8]\",\n  cardRing:\n    \"shadow-[0_0_0_1px_rgb(12_10_9/0.10),0_24px_48px_-12px_rgb(12_10_9/0.14)]\",\n  text2xl: \"text-[24px] leading-[32px] tracking-[-0.35px]\",\n  textBase: \"text-[16px] leading-[24px]\",\n  textSm: \"text-[14px] leading-[20px]\",\n} as const;\n\n// Illustration colours — the same tokens as raw values, since SVG paint\n// attributes cannot take a class.\nconst INK = {\n  fg: \"#0C0A09\",\n  card: \"#FFFFFF\",\n  primary: \"#1447E6\",\n  primaryFg: \"#EFF6FF\",\n  chart1: \"#8EC5FF\",\n} as const;\n\n// Fixed-pixel composition scaled to the caller's width by a viewBox, with a\n// 48px margin on the stage so the drop shadow is not clipped — see Upgrade\n// Modal.\nconst STAGE = { width: 592, height: 500 } as const;\n\ninterface RewardsCardProps {\n  title?: string;\n  subtext?: string;\n  ctaLabel?: string;\n  className?: string;\n}\n\nconst ink = (color: string, width: number) => ({\n  fill: \"none\",\n  stroke: color,\n  strokeWidth: width,\n  strokeLinecap: \"round\" as const,\n  strokeLinejoin: \"round\" as const,\n});\n\nfunction WateringCan() {\n  return (\n    <svg viewBox=\"0 0 96 96\" className=\"block size-full\" aria-hidden=\"true\">\n      <circle cx=\"48\" cy=\"50\" r=\"32\" fill={INK.primaryFg} />\n      <path\n        d=\"M30 73C40 72 50 74 60 73C64 72.5 68 73.5 70 73\"\n        {...ink(INK.fg, 1.5)}\n      />\n      <path\n        d=\"M24 40C25 37.5 27.5 37 30.5 37L38 37C40 37 41 38.5 40.5 40.5L39 50.5C38.8 52.5 37.3 53.5 35.5 53.5L27.5 53.5C25.5 53.5 24.3 52 24.3 50Z\"\n        {...ink(INK.fg, 2)}\n      />\n      <path d=\"M29 37C28.5 30.5 37.5 30.5 37 37\" {...ink(INK.fg, 2)} />\n      <path\n        d=\"M40.5 42 48 37M40 46.5 49 41.5M48 37C50.5 35.8 51.5 38.5 49 41.5\"\n        {...ink(INK.fg, 2)}\n      />\n      <path\n        d=\"M51.5 40C56 44 58 50 57 56M54.5 37.5C60 41 63 48 62 55\"\n        {...ink(INK.chart1, 2.5)}\n      />\n      <path\n        d=\"M48 73C48 69 48 66 48 63M57 73C57 68 57 65 57 61M66 73C66 69 66 66 66 63\"\n        {...ink(INK.fg, 1.5)}\n      />\n      <path\n        d=\"M48 68C45 67.5 44 65.5 44.5 64C46.5 64.5 47.5 66 48 68M57 66.5C60 66 61 64 60.5 62.5C58.5 63 57.5 64.5 57 66.5M66 68C69 67.5 70 65.5 69.5 64C67.5 64.5 66.5 66 66 68\"\n        {...ink(INK.fg, 1.5)}\n      />\n      {[\n        [48, 59],\n        [57, 57],\n        [66, 59],\n      ].map(([cx, cy]) => (\n        <circle\n          key={`${cx}-${cy}`}\n          cx={cx}\n          cy={cy}\n          r=\"4\"\n          fill={INK.primary}\n          stroke={INK.fg}\n          strokeWidth={1.5}\n        />\n      ))}\n    </svg>\n  );\n}\n\nfunction GiftBox() {\n  return (\n    <svg viewBox=\"0 0 96 96\" className=\"block size-full\" aria-hidden=\"true\">\n      {/* Figma's rotation is counter-clockwise, SVG's is clockwise — the sign\n          flips so the paper tilts the same way and stays under the box. */}\n      <rect\n        x=\"22\"\n        y=\"22\"\n        width=\"58\"\n        height=\"58\"\n        fill={INK.primaryFg}\n        transform=\"rotate(5 22 22)\"\n      />\n      <path\n        d=\"M29 47C42 46 55 46 67 45L69 74C56 75 43 75 31 76Z\"\n        {...ink(INK.fg, 2)}\n      />\n      <path\n        d=\"M25 38C40 37 56 36 72 36L71 46C56 46 40 47 26 48Z\"\n        {...ink(INK.fg, 2)}\n      />\n      <path d=\"M48 47 48 75M30 61 68 60\" {...ink(INK.primary, 2)} />\n      <path\n        d=\"M48 37C40 26 33 30 42 37M48 37C56 26 63 30 54 37M48 37C42 42 40 40 44 37M48 37C54 42 56 40 52 37\"\n        {...ink(INK.primary, 2)}\n      />\n      <path d=\"M78 18 78 26M74 22 82 22M20 20 23 23\" {...ink(INK.card, 1.5)} />\n    </svg>\n  );\n}\n\nfunction TapChecklist() {\n  return (\n    <svg viewBox=\"0 0 96 96\" className=\"block size-full\" aria-hidden=\"true\">\n      <rect\n        x=\"20\"\n        y=\"18\"\n        width=\"46\"\n        height=\"56\"\n        fill={INK.card}\n        transform=\"rotate(-3 20 18)\"\n      />\n      <path\n        d=\"M27 27 35 26.5 35.5 34.5 27.5 35ZM27 41 35 40.5 35.5 48.5 27.5 49ZM27 55 35 54.5 35.5 62.5 27.5 63Z\"\n        {...ink(INK.fg, 1.5)}\n      />\n      <path\n        d=\"M40 30C46 29 52 30 58 29M40 44C45 43 50 44 56 43M40 58C46 57 50 58 54 57\"\n        {...ink(INK.fg, 1.5)}\n      />\n      <path d=\"M28.5 30.5 31 33.5 36 27\" {...ink(INK.primary, 2)} />\n      <path\n        d=\"M58 48 59 76 65 70 69 80 73 78 69 68 77 67Z\"\n        {...ink(INK.fg, 2)}\n      />\n      <path d=\"M52 44 55 47M58 40 58 44M64 44 61 47\" {...ink(INK.fg, 1.5)} />\n    </svg>\n  );\n}\n\nconst TILES: {\n  name: string;\n  fill: string;\n  Art: () => ReactNode;\n  lifted?: boolean;\n}[] = [\n  { name: \"Watering can\", fill: NOVA.chart1, Art: WateringCan },\n  { name: \"Gift box\", fill: NOVA.chart5, Art: GiftBox, lifted: true },\n  { name: \"Tap checklist\", fill: NOVA.muted, Art: TapChecklist },\n];\n\nfunction Tile({\n  fill,\n  lifted,\n  children,\n}: {\n  fill: string;\n  lifted?: boolean;\n  children: ReactNode;\n}) {\n  // The white border is real padding, not an inset shadow: the artwork sits\n  // inside it rather than painting over it, which is how the Figma tile is\n  // built (stroke inside, art clipped to the fill). The middle tile is lifted\n  // by giving its neighbours the 8px it lacks.\n  return (\n    <div\n      className={cn(\n        \"size-24 shrink-0 rounded-none p-1 shadow-[0_8px_20px_-4px_rgb(12_10_9/0.16)]\",\n        NOVA.card,\n        lifted ? \"mt-0\" : \"mt-2\",\n      )}\n    >\n      <div className={cn(\"size-full overflow-hidden\", fill)}>{children}</div>\n    </div>\n  );\n}\n\nfunction Card({\n  title = \"Get rewarded fast\",\n  subtext = \"Choose and earn fast with these simple offers.\",\n  ctaLabel = \"Show 10 easy offers\",\n}: Omit<RewardsCardProps, \"className\">) {\n  return (\n    <article\n      className={cn(\n        \"relative flex w-[496px] shrink-0 flex-col items-center gap-10 overflow-hidden rounded-none px-8 pt-16 pb-8 font-sans\",\n        NOVA.card,\n        NOVA.fg,\n        NOVA.cardRing,\n      )}\n    >\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"pointer-events-none absolute -top-[230px] left-1/2 h-[340px] w-[720px] -translate-x-1/2 rounded-full opacity-55 blur-[90px]\",\n          NOVA.chart1,\n        )}\n      />\n\n      <div className=\"relative flex items-start gap-6\">\n        {TILES.map(({ name, fill, Art, lifted }) => (\n          <Tile key={name} fill={fill} lifted={lifted}>\n            <Art />\n          </Tile>\n        ))}\n      </div>\n\n      <div className=\"relative flex flex-col items-center gap-1 text-center\">\n        <h2 className={cn(\"font-medium\", NOVA.text2xl)}>{title}</h2>\n        <p className={cn(\"w-full\", NOVA.textBase, NOVA.mutedFg)}>{subtext}</p>\n      </div>\n\n      <span\n        className={cn(\n          \"relative flex h-9 w-full items-center justify-center rounded-none font-medium whitespace-nowrap\",\n          NOVA.primaryFgBg,\n          NOVA.primaryText,\n          NOVA.textSm,\n        )}\n      >\n        {ctaLabel}\n      </span>\n    </article>\n  );\n}\n\nexport default function RewardsCard({ className, ...props }: RewardsCardProps) {\n  return (\n    <svg\n      viewBox={`0 0 ${STAGE.width} ${STAGE.height}`}\n      width={STAGE.width}\n      height={STAGE.height}\n      // 592px is STAGE.width spelled out: Tailwind scans source text, so a\n      // class built from the constant would never be generated.\n      className={cn(\"h-auto w-full max-w-[592px]\", className)}\n    >\n      <foreignObject width={STAGE.width} height={STAGE.height}>\n        <div className=\"flex size-full items-start justify-center p-12\">\n          <Card {...props} />\n        </div>\n      </foreignObject>\n    </svg>\n  );\n}\n",
      "type": "registry:component",
      "target": "~/components/craftui/designs/rewards-card/default.tsx"
    }
  ],
  "meta": {
    "displayName": "Rewards Card - Rewards Card",
    "description": "An onboarding card on the shadcn Nova preset with a layer-blurred blue haze across the top, three inset-bordered tiles carrying hand-drawn ink illustrations — watering can and coin sprouts, gift box, tapped checklist — centred copy and a soft primary action. Every value reads from one token object shared with the Upgrade Modal. Static — no hooks, no motion, zero JavaScript.",
    "category": "card",
    "tags": [
      "design",
      "static",
      "card",
      "onboarding",
      "illustration",
      "hand-drawn",
      "blur",
      "shadcn",
      "nova",
      "tokens"
    ],
    "animationType": "none"
  }
}