{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "envelope",
  "type": "registry:component",
  "title": "Envelope",
  "description": "A kraft envelope that pops its wax seal, swings the flap open and lets a letter spring up – then tucks it back in and reseals itself.",
  "categories": [
    "illustration",
    "Objects"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/envelope.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { animate, motion, useInView, useMotionValue, useReducedMotion, useTransform } from \"motion/react\";\nimport { useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type EnvelopeHue = \"sky\" | \"emerald\" | \"amber\" | \"violet\" | \"rose\" | \"orange\" | \"indigo\" | \"teal\";\n\nconst hues: Record<EnvelopeHue, string> = {\n  sky: \"[--seal:var(--color-sky-600)] [--seal-hi:var(--color-sky-400)] [--seal-lo:var(--color-sky-800)]\",\n  emerald: \"[--seal:var(--color-emerald-600)] [--seal-hi:var(--color-emerald-400)] [--seal-lo:var(--color-emerald-800)]\",\n  amber: \"[--seal:var(--color-amber-600)] [--seal-hi:var(--color-amber-400)] [--seal-lo:var(--color-amber-800)]\",\n  violet: \"[--seal:var(--color-violet-600)] [--seal-hi:var(--color-violet-400)] [--seal-lo:var(--color-violet-800)]\",\n  rose: \"[--seal:var(--color-rose-600)] [--seal-hi:var(--color-rose-400)] [--seal-lo:var(--color-rose-800)]\",\n  orange: \"[--seal:var(--color-orange-600)] [--seal-hi:var(--color-orange-400)] [--seal-lo:var(--color-orange-800)]\",\n  indigo: \"[--seal:var(--color-indigo-600)] [--seal-hi:var(--color-indigo-400)] [--seal-lo:var(--color-indigo-800)]\",\n  teal: \"[--seal:var(--color-teal-600)] [--seal-hi:var(--color-teal-400)] [--seal-lo:var(--color-teal-800)]\",\n};\n\ntype Stage = \"sealed\" | \"unsealed\" | \"open\" | \"out\";\n\nexport interface EnvelopeProps {\n  hue?: EnvelopeHue;\n  title?: string;\n  message?: string;\n  from?: string;\n  monogram?: string;\n  autoPlay?: boolean;\n  className?: string;\n}\n\nconst SW = 400;\nconst SH = 300;\nconst EX = 50;\nconst EY = 124;\nconst EW = 300;\nconst EH = 172;\nconst FLAP_H = 104;\n\nfunction useFit<T extends HTMLElement>(width: number) {\n  const ref = useRef<T>(null);\n  const [k, setK] = useState(1);\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const measure = () => setK(el.offsetWidth / width);\n    measure();\n    const ro = new ResizeObserver(measure);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [width]);\n  return [ref, k] as const;\n}\n\nconst order: Stage[] = [\"sealed\", \"unsealed\", \"open\", \"out\"];\n\nfunction dwell(from: Stage, to: Stage) {\n  if (from === \"unsealed\") return 260;\n  if (from === \"open\" && to === \"out\") return 420;\n  if (from === \"open\" && to === \"sealed\") return 400;\n  return 0;\n}\n\nfunction useCalmMotion() {\n  const prefers = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && !!prefers;\n}\n\nexport function Envelope({\n  hue = \"rose\",\n  title = \"You're invited\",\n  message = \"Join us for the Northwind launch on Friday, 6 pm, at the old boathouse.\",\n  from = \"— Ines & the team\",\n  monogram = \"N\",\n  autoPlay = true,\n  className,\n}: EnvelopeProps) {\n  const reduce = useCalmMotion();\n  const root = useRef<HTMLDivElement>(null);\n  const inView = useInView(root);\n  const [fitRef, k] = useFit<HTMLButtonElement>(SW);\n  const liningId = `lining-${useId().replace(/:/g, \"\")}`;\n  const [stage, setStage] = useState<Stage>(\"sealed\");\n  const [target, setTarget] = useState<Stage>(\"sealed\");\n  const [touched, setTouched] = useState(false);\n\n  useEffect(() => {\n    if (stage === target) return;\n    const forward = order.indexOf(target) > order.indexOf(stage);\n    const next: Stage = forward ? order[order.indexOf(stage) + 1] : stage === \"out\" ? \"open\" : \"sealed\";\n    const t = setTimeout(() => setStage(next), reduce ? 0 : dwell(stage, next));\n    return () => clearTimeout(t);\n  }, [stage, target, reduce]);\n\n  useEffect(() => {\n    if (!autoPlay || touched || !inView || stage !== target) return;\n    const t = setTimeout(() => setTarget(target === \"out\" ? \"sealed\" : \"out\"), target === \"out\" ? 3200 : 1800);\n    return () => clearTimeout(t);\n  }, [autoPlay, touched, inView, stage, target]);\n\n  const click = () => {\n    setTouched(true);\n    setTarget(target === \"out\" ? \"sealed\" : \"out\");\n  };\n\n  const flapOpen = stage === \"open\" || stage === \"out\";\n  const sealOn = stage === \"sealed\";\n  const letterUp = stage === \"out\";\n\n  const flap = useMotionValue(0);\n  useEffect(() => {\n    const c = animate(flap, flapOpen ? 180 : 0, reduce ? { duration: 0 } : { duration: 0.5, ease: [0.65, 0, 0.35, 1] });\n    return () => c.stop();\n  }, [flapOpen, reduce, flap]);\n  const flapZ = useTransform(flap, (r) => (r > 90 ? 1 : 30));\n  const flapShade = useTransform(flap, [0, 90, 180], [0, 0.18, 0]);\n\n  return (\n    <div\n      ref={root}\n      className={cn(\n        \"relative w-full max-w-[460px] overflow-hidden rounded-2xl border bg-card p-4 shadow-xs/5 sm:p-6\",\n        \"[--paper:var(--color-white)] [--paper-2:var(--color-orange-50)] [--paper-3:var(--color-amber-50)] [--paper-edge:var(--color-amber-950)]\",\n        hues[hue],\n        className,\n      )}\n    >\n      <div\n        aria-hidden\n        className=\"dot-grid pointer-events-none absolute inset-0 opacity-50 [mask-image:radial-gradient(closest-side,black,transparent)]\"\n      />\n      <button\n        aria-label={target === \"out\" ? \"Put the letter away\" : `Open the letter: ${title}`}\n        className=\"group/env relative mx-auto block aspect-[4/3] w-full max-w-[400px] cursor-pointer rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n        onClick={click}\n        ref={fitRef}\n        type=\"button\"\n      >\n        <div\n          className=\"absolute top-0 left-0 origin-top-left text-left\"\n          style={{ height: SH, width: SW, transform: `scale(${k})` }}\n        >\n          <motion.div\n            animate={{ y: flapOpen ? 0 : -52 }}\n            className=\"absolute inset-0\"\n            initial={false}\n            transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 170, damping: 24 }}\n          >\n          <div\n            aria-hidden\n            className=\"absolute rounded-[50%] bg-foreground/15 blur-lg\"\n            style={{ height: 18, left: EX + 20, top: EY + EH - 6, width: EW - 40 }}\n          />\n\n          <div className=\"absolute\" style={{ height: EH, left: EX, perspective: 900, top: EY, width: EW }}>\n            <div className=\"absolute inset-0 overflow-hidden rounded-[10px] bg-(--seal)\">\n              <div className=\"absolute inset-0 bg-[repeating-linear-gradient(135deg,rgb(255_255_255/0.2)_0_3px,transparent_3px_10px)]\" />\n            </div>\n\n            <motion.div\n              animate={{ y: letterUp ? -128 : 0 }}\n              className=\"absolute z-10 flex flex-col rounded-[6px] bg-white px-5 pt-4 pb-3 text-neutral-800 shadow-[0_1px_2px_rgb(0_0_0/0.12),0_6px_16px_-6px_rgb(0_0_0/0.2)]\"\n              initial={false}\n              style={{ height: 152, left: 18, top: 12, width: EW - 36 }}\n              transition={\n                reduce\n                  ? { duration: 0 }\n                  : letterUp\n                    ? { type: \"spring\", stiffness: 170, damping: 22, mass: 0.9 }\n                    : { duration: 0.42, ease: [0.65, 0, 0.35, 1] }\n              }\n            >\n              <div className=\"flex items-center justify-between\">\n                <span className=\"flex size-5 items-center justify-center rounded-full bg-(--seal) font-semibold text-[10px] text-white\">\n                  {monogram}\n                </span>\n                <span className=\"font-mono text-[9px] text-neutral-500 tabular-nums\">No. 0428</span>\n              </div>\n              <p className=\"mt-2 font-semibold text-[15px] tracking-tight\">{title}</p>\n              <p className=\"mt-1 text-[10.5px] text-neutral-500 leading-snug\">{message}</p>\n              <div className=\"mt-auto flex items-end justify-between\">\n                <span className=\"font-medium text-[10px] text-neutral-600 italic\">{from}</span>\n                <span className=\"rounded-full bg-(--seal) px-2 py-0.5 font-medium text-[9px] text-white\">RSVP</span>\n              </div>\n            </motion.div>\n\n            <svg aria-hidden className=\"absolute inset-0 z-20 overflow-visible\" viewBox={`0 0 ${EW} ${EH}`}>\n              <path\n                className=\"fill-(--paper-2) stroke-(--paper-edge)\"\n                strokeOpacity={0.1}\n                d={`M0 10 Q0 0 10 0 L${EW / 2} ${FLAP_H - 8} L${EW - 10} 0 Q${EW} 0 ${EW} 10 V${EH - 10} Q${EW} ${EH} ${EW - 10} ${EH} H10 Q0 ${EH} 0 ${EH - 10} Z`}\n              />\n              <path className=\"fill-(--paper)\" d={`M0 ${EH - 10} L${EW / 2 - 14} ${EH / 2 + 18} Q${EW / 2} ${EH / 2 + 8} ${EW / 2 + 14} ${EH / 2 + 18} L${EW} ${EH - 10} Q${EW} ${EH} ${EW - 10} ${EH} H10 Q0 ${EH} 0 ${EH - 10} Z`} />\n              <path\n                className=\"fill-none stroke-(--paper-edge)\"\n                d={`M2 ${EH - 6} L${EW / 2 - 14} ${EH / 2 + 18} Q${EW / 2} ${EH / 2 + 8} ${EW / 2 + 14} ${EH / 2 + 18} L${EW - 2} ${EH - 6}`}\n                strokeOpacity={0.12}\n                strokeWidth={1.5}\n              />\n              <path className=\"fill-none stroke-(--paper-edge)\" d={`M2 4 L${EW / 2} ${FLAP_H - 6} L${EW - 2} 4`} strokeOpacity={0.1} strokeWidth={1.5} />\n            </svg>\n\n            <motion.div\n              className=\"absolute inset-x-0 top-0 origin-top [transform-style:preserve-3d]\"\n              style={{ height: FLAP_H + 4, rotateX: flap, zIndex: flapZ }}\n            >\n              <svg aria-hidden className=\"absolute inset-0 overflow-visible drop-shadow-[0_2px_3px_rgb(0_0_0/0.08)] [backface-visibility:hidden]\" viewBox={`0 0 ${EW} ${FLAP_H + 4}`}>\n                <path\n                  className=\"fill-(--paper) stroke-(--paper-edge)\"\n                  d={`M10 0 H${EW - 10} Q${EW} 0 ${EW - 6} 6 L${EW / 2 + 16} ${FLAP_H - 6} Q${EW / 2} ${FLAP_H + 4} ${EW / 2 - 16} ${FLAP_H - 6} L6 6 Q0 0 10 0 Z`}\n                  strokeOpacity={0.12}\n                />\n                <path\n                  className=\"fill-none stroke-(--paper-edge)\"\n                  d={`M6 6 L${EW / 2 - 16} ${FLAP_H - 6} Q${EW / 2} ${FLAP_H + 4} ${EW / 2 + 16} ${FLAP_H - 6} L${EW - 6} 6`}\n                  strokeOpacity={0.1}\n                  strokeWidth={4}\n                  transform=\"translate(0 1.5)\"\n                />\n                <motion.path\n                  className=\"fill-black\"\n                  d={`M10 0 H${EW - 10} Q${EW} 0 ${EW - 6} 6 L${EW / 2 + 16} ${FLAP_H - 6} Q${EW / 2} ${FLAP_H + 4} ${EW / 2 - 16} ${FLAP_H - 6} L6 6 Q0 0 10 0 Z`}\n                  style={{ fillOpacity: flapShade }}\n                />\n              </svg>\n              <svg\n                aria-hidden\n                className=\"absolute inset-0 overflow-visible [backface-visibility:hidden] [transform:rotateX(180deg)]\"\n                viewBox={`0 0 ${EW} ${FLAP_H + 4}`}\n              >\n                <defs>\n                  <pattern height=\"10\" id={`${liningId}`} patternTransform=\"rotate(45)\" patternUnits=\"userSpaceOnUse\" width=\"10\">\n                    <rect className=\"fill-(--seal)\" height=\"10\" width=\"10\" />\n                    <rect fill=\"white\" fillOpacity={0.2} height=\"10\" width=\"3\" />\n                  </pattern>\n                </defs>\n                <g transform={`scale(1 -1) translate(0 ${-(FLAP_H + 4)})`}>\n                  <path\n                    className=\"fill-(--paper)\"\n                    d={`M10 0 H${EW - 10} Q${EW} 0 ${EW - 6} 6 L${EW / 2 + 16} ${FLAP_H - 6} Q${EW / 2} ${FLAP_H + 4} ${EW / 2 - 16} ${FLAP_H - 6} L6 6 Q0 0 10 0 Z`}\n                  />\n                  <path\n                    d={`M18 6 H${EW - 18} L${EW / 2 + 12} ${FLAP_H - 14} Q${EW / 2} ${FLAP_H - 6} ${EW / 2 - 12} ${FLAP_H - 14} Z`}\n                    fill={`url(#${liningId})`}\n                  />\n                </g>\n              </svg>\n            </motion.div>\n\n            <motion.div\n              animate={\n                sealOn\n                  ? { opacity: 1, scale: 1, rotate: 0, y: 0 }\n                  : { opacity: 0, scale: reduce ? 1 : 1.3, rotate: reduce ? 0 : -14, y: reduce ? 0 : -10 }\n              }\n              aria-hidden\n              className=\"absolute z-40 size-[52px]\"\n              initial={false}\n              style={{ left: EW / 2 - 26, top: FLAP_H - 36 }}\n              transition={\n                reduce\n                  ? { duration: 0.15 }\n                  : sealOn\n                    ? { type: \"spring\", stiffness: 520, damping: 22, mass: 0.7, delay: 0.42 }\n                    : { duration: 0.26, ease: [0.23, 1, 0.32, 1] }\n              }\n            >\n              <svg className=\"size-full overflow-visible drop-shadow-[0_2px_2px_rgb(0_0_0/0.25)]\" viewBox=\"0 0 46 46\">\n                <path\n                  className=\"fill-(--seal)\"\n                  d=\"M23 1.5c3 0 4.2 2.4 6.6 3.2 2.6.8 5-.7 7 1 2 1.8 1.2 4.3 2.4 6.6 1.2 2.4 3.9 3.4 3.9 6.2 0 2.9-2.4 4-3 6.6-.6 2.7 1.1 4.9-.5 7.1-1.7 2.3-4.4 1.6-6.7 3-2.3 1.4-3 4.2-5.8 4.6-2.8.4-4.2-2-6.8-2.6-2.7-.6-5 1-7.1-.7-2.1-1.8-1.3-4.5-2.6-6.8C9.1 28 6.2 27 5.9 24.2c-.3-2.8 2.2-4.1 2.7-6.8.5-2.6-1.2-5 .4-7.2 1.7-2.2 4.4-1.4 6.8-2.8C18.2 6 19.9 1.5 23 1.5Z\"\n                />\n                <circle className=\"fill-none stroke-(--seal-lo)\" cx=\"23\" cy=\"23\" r=\"12.5\" strokeOpacity={0.5} strokeWidth=\"1.5\" />\n                <circle className=\"fill-(--seal-hi)\" cx=\"18\" cy=\"16\" opacity={0.35} r=\"5\" />\n                <text\n                  className=\"fill-(--seal-lo) font-semibold\"\n                  dominantBaseline=\"central\"\n                  fontSize=\"15\"\n                  textAnchor=\"middle\"\n                  x=\"23\"\n                  y=\"23.5\"\n                >\n                  {monogram}\n                </text>\n              </svg>\n            </motion.div>\n          </div>\n          </motion.div>\n        </div>\n      </button>\n    </div>\n  );\n}\n"
    }
  ]
}