{
  "name": "travel-card-default",
  "type": "registry:block",
  "tier": "free",
  "dependencies": [
    "motion",
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "https://www.craftui.space/r/craftui-base.json"
  ],
  "files": [
    {
      "path": "components/craftui/blocks/travel-card/default.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useState, type ComponentProps } from \"react\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { PlaneIcon, Sun01Icon } from \"@hugeicons/core-free-icons\";\n\ntype HugeIcon = ComponentProps<typeof HugeiconsIcon>[\"icon\"];\n\ntype Stage = 1 | 2;\n\nconst CARD_W = 400;\nconst CARD_H = 550;\n\nconst stageConfig: Record<\n  Stage,\n  {\n    imageTop: number;\n    imageLeft: number;\n    imageWidth: number;\n    imageHeight: number;\n    imageRadius: number;\n    cardBg: string;\n    ctaTop: number;\n    ctaLeft: number;\n    ctaWidth: number;\n    ctaHeight: number;\n    ctaRadius: number;\n  }\n> = {\n  1: {\n    imageTop: 0,\n    imageLeft: 0,\n    imageWidth: CARD_W,\n    imageHeight: CARD_H,\n    imageRadius: 8,\n    cardBg: \"rgb(13, 27, 38)\",\n    ctaTop: CARD_H - 64,\n    ctaLeft: CARD_W - 146,\n    ctaWidth: 130,\n    ctaHeight: 48,\n    ctaRadius: 0,\n  },\n  2: {\n    imageTop: 20,\n    imageLeft: 20,\n    imageWidth: CARD_W - 40,\n    imageHeight: 280,\n    imageRadius: 0,\n    cardBg: \"rgb(255, 255, 255)\",\n    ctaTop: CARD_H - 68,\n    ctaLeft: 20,\n    ctaWidth: CARD_W - 40,\n    ctaHeight: 48,\n    ctaRadius: 0,\n  },\n};\n\nconst DEFAULT_IMAGE_URL =\n  \"https://images.unsplash.com/photo-1486870591958-9b9d0d1dda99?w=900&auto=format&fit=crop&q=80\";\n\nconst FLIGHT_PATH = \"M 286 510 Q 240 320 290 328\";\n\nexport interface TravelCardProps {\n  imageUrl?: string;\n  imageAlt?: string;\n  title?: string;\n  description?: string;\n  price?: string;\n  availabilityText?: string;\n  tagText?: string;\n  tagIcon?: HugeIcon;\n  exploreLabel?: string;\n  addToCartLabel?: string;\n  onAddToCart?: () => void;\n  className?: string;\n}\n\nexport default function TravelCard({\n  imageUrl = DEFAULT_IMAGE_URL,\n  imageAlt = \"Deep Blue Nights\",\n  title = \"Deep Blue Nights\",\n  description = \"For those who crave peace louder than the city. This view isn’t just scenery, it’s a whole reset.\",\n  price = \"$2,999\",\n  availabilityText = \"9 seats left\",\n  tagText = \"Perfect for summer\",\n  tagIcon = Sun01Icon,\n  exploreLabel = \"Explore\",\n  addToCartLabel = \"Add to Cart\",\n  onAddToCart,\n  className,\n}: TravelCardProps = {}) {\n  const [stage, setStage] = useState<Stage>(1);\n  const shouldReduceMotion = useReducedMotion();\n  const cfg = stageConfig[stage];\n  const expanded = stage === 2;\n\n  const toggle = () => {\n    if (stage === 2) onAddToCart?.();\n    setStage((s) => (s === 1 ? 2 : 1));\n  };\n\n  const morphTransition = shouldReduceMotion\n    ? { duration: 0 }\n    : {\n        type: \"spring\" as const,\n        duration: expanded ? 0.5 : 0.45,\n        bounce: 0.25,\n      };\n\n  const ctaTransition = shouldReduceMotion\n    ? { duration: 0 }\n    : {\n        type: \"spring\" as const,\n        duration: expanded ? 0.35 : 0.25,\n        bounce: 0.25,\n      };\n\n  const detailTransition = shouldReduceMotion\n    ? { duration: 0 }\n    : { duration: expanded ? 0.15 : 0.08, ease: [0.77, 0, 0.175, 1] as const };\n\n  const planeTransition = shouldReduceMotion\n    ? { duration: 0 }\n    : {\n        offsetDistance: {\n          type: \"spring\" as const,\n          duration: expanded ? 0.9 : 0.18,\n          bounce: 0.25,\n          delay: expanded ? 0 : 0.05,\n        },\n        scale: {\n          type: \"spring\" as const,\n          duration: expanded ? 0.5 : 0.45,\n          bounce: 0.25,\n          delay: expanded ? 0 : 0.05,\n        },\n        opacity: expanded\n          ? { duration: 0.9 }\n          : { duration: 0.45, delay: 0.05, times: [0, 0.15, 0.85, 1] },\n      };\n\n  return (\n    <div\n      className={`relative select-none ${className ?? \"\"}`}\n      style={{ width: CARD_W, height: CARD_H }}\n    >\n      <motion.div\n        animate={{ backgroundColor: cfg.cardBg }}\n        transition={{\n          backgroundColor: {\n            duration: 0.02,\n            ease: \"easeOut\",\n            delay: shouldReduceMotion ? 0 : expanded ? 0 : 0.35,\n          },\n        }}\n        className=\"absolute inset-0 overflow-hidden rounded-[8px] shadow-xl\"\n      >\n        <motion.div\n          animate={{\n            top: cfg.imageTop,\n            left: cfg.imageLeft,\n            width: cfg.imageWidth,\n            height: cfg.imageHeight,\n            borderRadius: cfg.imageRadius,\n          }}\n          transition={morphTransition}\n          className=\"absolute overflow-hidden\"\n        >\n          <img\n            src={imageUrl}\n            alt={imageAlt}\n            className=\"h-full w-full object-cover outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10\"\n          />\n        </motion.div>\n\n        <motion.div\n          animate={{ top: cfg.imageTop + cfg.imageHeight + 16 }}\n          transition={detailTransition}\n          className=\"absolute inset-x-0 px-5\"\n        >\n          <motion.div\n            animate={{\n              opacity: expanded ? 1 : 0,\n              filter: expanded ? \"blur(0px)\" : \"blur(2px)\",\n            }}\n            transition={{\n              duration: shouldReduceMotion ? 0 : 0.12,\n              ease: \"easeOut\",\n            }}\n          >\n            <div className=\"flex items-start justify-between gap-3\">\n              <h3 className=\"min-w-0 line-clamp-2 text-lg font-semibold tracking-tight text-balance text-[oklch(0.215_0.029_243.425)]\">\n                {title}\n              </h3>\n\n              <span className=\"flex shrink-0 items-center gap-1.5 rounded-[4px] bg-[oklch(0.956_0.004_236.498)] py-1 ps-1 pe-3 text-xs font-medium whitespace-nowrap text-[oklch(0.215_0.029_243.425)] tabular-nums\">\n                <span className=\"h-4 w-4 shrink-0\" />\n                {availabilityText}\n              </span>\n            </div>\n          </motion.div>\n\n          <motion.p\n            animate={{\n              opacity: expanded ? 1 : 0,\n              filter: expanded ? \"blur(0px)\" : \"blur(2px)\",\n            }}\n            transition={{\n              duration: shouldReduceMotion ? 0 : 0.12,\n              ease: \"easeOut\",\n              delay: shouldReduceMotion ? 0 : expanded ? 0.08 : 0,\n            }}\n            className=\"mt-2 line-clamp-2 text-sm leading-relaxed text-pretty text-[oklch(0.502_0.021_250.776)]\"\n          >\n            {description}\n          </motion.p>\n\n          <motion.div\n            animate={{\n              opacity: expanded ? 1 : 0,\n              filter: expanded ? \"blur(0px)\" : \"blur(2px)\",\n            }}\n            transition={{\n              duration: shouldReduceMotion ? 0 : 0.12,\n              ease: \"easeOut\",\n              delay: shouldReduceMotion ? 0 : expanded ? 0.16 : 0,\n            }}\n            className=\"mt-3 flex items-center gap-2\"\n          >\n            <span className=\"rounded-[4px] bg-[oklch(0.956_0.004_236.498)] px-3 py-1 text-sm font-medium whitespace-nowrap text-[oklch(0.215_0.029_243.425)] tabular-nums\">\n              {price}\n            </span>\n            <span className=\"flex items-center gap-1 rounded-[4px] bg-[oklch(0.956_0.004_236.498)] px-3 py-1 text-xs font-medium whitespace-nowrap text-[oklch(0.215_0.029_243.425)]\">\n              <HugeiconsIcon\n                icon={tagIcon}\n                size={12}\n                strokeWidth={2}\n                color=\"oklch(0.215 0.029 243.425)\"\n              />\n              {tagText}\n            </span>\n          </motion.div>\n        </motion.div>\n\n        <motion.button\n          type=\"button\"\n          onClick={toggle}\n          aria-label={expanded ? \"Add to cart\" : \"View details\"}\n          animate={{\n            top: cfg.ctaTop,\n            left: cfg.ctaLeft,\n            width: cfg.ctaWidth,\n            height: cfg.ctaHeight,\n            borderRadius: cfg.ctaRadius,\n          }}\n          transition={ctaTransition}\n          whileTap={{ scale: 0.96 }}\n          className=\"absolute flex items-center justify-center gap-2 bg-[oklch(0.215_0.029_243.425)] text-white shadow-lg\"\n        >\n          {!expanded && <span className=\"h-6 w-6 shrink-0\" />}\n\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {expanded ? (\n              <motion.span\n                key=\"cart-text\"\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: shouldReduceMotion ? 0 : 0.05 }}\n                className=\"text-sm font-semibold whitespace-nowrap select-none\"\n              >\n                {addToCartLabel}\n              </motion.span>\n            ) : (\n              <motion.span\n                key=\"explore-text\"\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: shouldReduceMotion ? 0 : 0.05 }}\n                className=\"text-sm font-semibold whitespace-nowrap select-none\"\n              >\n                {exploreLabel}\n              </motion.span>\n            )}\n          </AnimatePresence>\n        </motion.button>\n\n        <AnimatePresence>\n          <motion.span\n            key={stage}\n            aria-hidden\n            style={{\n              offsetPath: `path(\"${FLIGHT_PATH}\")`,\n              offsetRotate: \"0deg\",\n            }}\n            initial={{\n              offsetDistance: expanded ? \"0%\" : \"100%\",\n              scale: expanded ? 1.4 : 0.9,\n              opacity: 1,\n            }}\n            animate={{\n              offsetDistance: expanded ? \"100%\" : \"0%\",\n              scale: expanded ? 0.9 : 1.4,\n              opacity: expanded ? 1 : [1, 0, 0.9, 1],\n            }}\n            transition={planeTransition}\n            className=\"absolute flex h-4 w-4 items-center justify-center rounded-[4px] bg-[oklch(0.215_0.029_243.425)]\"\n          >\n            <HugeiconsIcon\n              icon={PlaneIcon}\n              size={12}\n              strokeWidth={2}\n              color=\"oklch(1 0 0)\"\n            />\n          </motion.span>\n        </AnimatePresence>\n      </motion.div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "~/components/craftui/blocks/travel-card/default.tsx"
    }
  ],
  "meta": {
    "displayName": "Travel Card - Travel Card",
    "description": "The image, background color, CTA button, and a badge icon all animate between a cover-photo stage and a detail stage with price and tags, connected by an icon that flies along a curved path between the two.",
    "category": "card",
    "tags": [
      "card",
      "travel",
      "product",
      "morph",
      "click",
      "animation"
    ],
    "animationType": "click"
  }
}