{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "TearTicket-TS-TW",
	"title": "TearTicket",
	"description": "Ticket whose perforated stub tears off by hand: paper bridges stretch into fibres and snap one by one from the far end, the torn edges are jagged and fit each other, the freed stub dangles and drops, and the body is stamped as used. The artwork tilts in 3D with parallax on hover.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "TearTicket/TearTicket.tsx",
			"content": "'use client';\n\nimport React, {\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  type CSSProperties,\n  type KeyboardEvent,\n  type PointerEvent,\n  type ReactNode\n} from 'react';\nimport { motion, useMotionTemplate, useReducedMotion, useSpring, useTransform } from 'motion/react';\n\nexport type TearTicketOrientation = 'horizontal' | 'vertical';\n\nexport interface TearTicketProps {\n  children?: ReactNode;\n  stub?: ReactNode;\n  image?: string;\n  imageAlt?: string;\n  scrim?: boolean;\n  imageRadius?: number;\n  orientation?: TearTicketOrientation;\n  torn?: boolean;\n  defaultTorn?: boolean;\n  onTear?: () => void;\n  width?: number;\n  height?: number;\n  stubSize?: number;\n  radius?: number;\n  holes?: number;\n  holeSize?: number;\n  notch?: number;\n  roughness?: number;\n  tearAngle?: number;\n  stretch?: number;\n  resistance?: number;\n  rotate?: number;\n  tilt?: boolean;\n  tiltMax?: number;\n  tiltReach?: number;\n  parallax?: number;\n  perspective?: number;\n  background?: string;\n  color?: string;\n  border?: boolean;\n  borderColor?: string;\n  borderWidth?: number;\n  stubBackground?: string;\n  recenter?: boolean;\n  disabled?: boolean;\n  ariaLabel?: string;\n  className?: string;\n}\n\ninterface Point {\n  x: number;\n  y: number;\n}\n\ninterface Bridge extends Point {\n  y0: number;\n  y1: number;\n  mid: number;\n  pts: number[][];\n}\n\ninterface End extends Point {\n  v: number;\n}\n\ninterface Geometry {\n  vertical: boolean;\n  cross: number;\n  body: string;\n  stub: string;\n  bridges: Bridge[];\n  ends: End[];\n  bodyOutline: string;\n  stubOutline: string;\n}\n\ntype Phase = 'idle' | 'held' | 'free' | 'drop' | 'return';\n\ninterface Sim {\n  raf: number;\n  last: number;\n  phase: Phase;\n  id: number | null;\n  sign: number;\n  hinge: Point;\n  hingeV: number;\n  grab: Point;\n  start: Point;\n  point: Point;\n  a0: number;\n  theta: number;\n  thetaV: number;\n  sx: number;\n  sy: number;\n  vx: number;\n  vy: number;\n  spin: number;\n  pvx: number;\n  pvy: number;\n  pt: number;\n  fade: number;\n  age: number;\n  bx: number;\n  bv: number;\n  snapped: boolean[];\n  snapAt: number[];\n  span: number[];\n}\n\ninterface Cfg {\n  geo: Geometry;\n  tearAngle: number;\n  stretch: number;\n  resistance: number;\n  height: number;\n  notch: number;\n  reduce: boolean | null;\n  onTear?: () => void;\n  controlled: boolean;\n}\n\nconst TILT_SPRING = { stiffness: 220, damping: 24, mass: 0.6 };\nconst GRAVITY = 2400;\nconst ART_INSET = 8;\nconst ART_SPAN = 0.78;\nconst RETRACT = 0.17;\n\nconst clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));\nconst rad = (deg: number) => (deg * Math.PI) / 180;\nconst wrap = (a: number) => Math.atan2(Math.sin(a), Math.cos(a));\nconst noise = (seed: number) => {\n  let s = seed | 0;\n  return () => {\n    s = (s + 0x6d2b79f5) | 0;\n    let t = Math.imul(s ^ (s >>> 15), 1 | s);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n};\nconst f = (n: number) => n.toFixed(2);\n\nconst buildGeometry = (\n  W: number,\n  H: number,\n  S: number,\n  R: number,\n  holes: number,\n  hole: number,\n  notch: number,\n  rough: number,\n  vertical: boolean\n): Geometry => {\n  const main = vertical ? H : W;\n  const cross = vertical ? W : H;\n  const x = main - S;\n  const hr = hole / 2;\n  const n = Math.max(1, Math.round(holes));\n  const span = cross - 2 * notch;\n  const bridge = Math.max(2, (span - n * hole) / (n + 1));\n  const random = noise(n * 7919 + Math.round(cross));\n  const at = (u: number, v: number): Point => (vertical ? { x: v, y: u } : { x: u, y: v });\n  const pt = (u: number, v: number) => (vertical ? `${f(v)},${f(u)}` : `${f(u)},${f(v)}`);\n  const arc = (r: number, sweep: number, u: number, v: number) =>\n    `A${f(r)},${f(r)} 0 0 ${vertical ? 1 - sweep : sweep} ${pt(u, v)}`;\n  const bridges: Bridge[] = [];\n  for (let i = 0; i <= n; i += 1) {\n    const y0 = notch + i * (bridge + hole);\n    const y1 = y0 + bridge;\n    const steps = Math.max(2, Math.round(bridge / 2.2));\n    const pts: number[][] = [];\n    for (let k = 1; k < steps; k += 1) pts.push([x + (random() - 0.5) * 2 * rough, y0 + (bridge * k) / steps]);\n    bridges.push({ y0, y1, mid: (y0 + y1) / 2, pts, ...at(x, (y0 + y1) / 2) });\n  }\n  let body = `M${pt(R, 0)}L${pt(x - notch, 0)}${arc(notch, 0, x, notch)}`;\n  bridges.forEach((b, i) => {\n    b.pts.forEach(p => {\n      body += `L${pt(p[0], p[1])}`;\n    });\n    body += `L${pt(x, b.y1)}`;\n    if (i < n) body += arc(hr, 0, x, b.y1 + hole);\n  });\n  body += `${arc(notch, 0, x - notch, cross)}L${pt(R, cross)}${arc(R, 1, 0, cross - R)}L${pt(0, R)}${arc(R, 1, R, 0)}Z`;\n  let stub = `M${pt(x + notch, 0)}L${pt(main - R, 0)}${arc(R, 1, main, R)}L${pt(main, cross - R)}${arc(R, 1, main - R, cross)}L${pt(x + notch, cross)}${arc(notch, 0, x, cross - notch)}`;\n  for (let i = n; i >= 0; i -= 1) {\n    const b = bridges[i];\n    for (let k = b.pts.length - 1; k >= 0; k -= 1) stub += `L${pt(b.pts[k][0], b.pts[k][1])}`;\n    stub += `L${pt(x, b.y0)}`;\n    if (i > 0) stub += arc(hr, 0, x, b.y0 - hole);\n  }\n  stub += `${arc(notch, 0, x + notch, 0)}Z`;\n  const ends = [\n    { ...at(x, notch), v: notch },\n    { ...at(x, cross - notch), v: cross - notch }\n  ];\n  const bodyOutline = `M${pt(x, cross - notch)}${arc(notch, 0, x - notch, cross)}L${pt(R, cross)}${arc(R, 1, 0, cross - R)}L${pt(0, R)}${arc(R, 1, R, 0)}L${pt(x - notch, 0)}${arc(notch, 0, x, notch)}`;\n  const stubOutline = `M${pt(x, notch)}${arc(notch, 0, x + notch, 0)}L${pt(main - R, 0)}${arc(R, 1, main, R)}L${pt(main, cross - R)}${arc(R, 1, main - R, cross)}L${pt(x + notch, cross)}${arc(notch, 0, x, cross - notch)}`;\n  return { vertical, cross, body, stub, bridges, ends, bodyOutline, stubOutline };\n};\n\nconst TearTicket: React.FC<TearTicketProps> = ({\n  children = null,\n  stub = null,\n  image = '',\n  imageAlt = '',\n  scrim = true,\n  imageRadius = 8,\n  orientation = 'horizontal',\n  torn,\n  defaultTorn = false,\n  onTear,\n  width = 460,\n  height = 250,\n  stubSize = 150,\n  radius = 16,\n  holes = 12,\n  holeSize = 6,\n  notch = 3,\n  roughness = 0,\n  tearAngle = 30,\n  stretch = 30,\n  resistance = 0.45,\n  rotate = 4,\n  tilt = true,\n  tiltMax = 9,\n  tiltReach = 260,\n  parallax = 6,\n  perspective = 1000,\n  background = '#27272a',\n  color = '#f5f5f5',\n  border = true,\n  borderColor = '',\n  borderWidth = 1,\n  stubBackground = '',\n  recenter = true,\n  disabled = false,\n  ariaLabel = 'Tear off the stub',\n  className = ''\n}) => {\n  const reduce = useReducedMotion();\n  const controlled = torn !== undefined;\n  const [inner, setInner] = useState(defaultTorn);\n  const used = controlled ? torn : inner;\n  const [grabbing, setGrabbing] = useState(false);\n  const [instant, setInstant] = useState(used);\n  const [fit, setFit] = useState(1);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const stageRef = useRef<HTMLDivElement>(null);\n  const bodyRef = useRef<HTMLDivElement>(null);\n  const stubRef = useRef<HTMLDivElement>(null);\n  const fibres = useRef<(SVGPathElement | null)[]>([]);\n  const vertical = orientation === 'vertical';\n  const geo = useMemo(\n    () => buildGeometry(width, height, stubSize, radius, holes, holeSize, notch, roughness, vertical),\n    [width, height, stubSize, radius, holes, holeSize, notch, roughness, vertical]\n  );\n  const cfg = useRef<Cfg>({} as Cfg);\n  cfg.current = { geo, tearAngle, stretch, resistance, height, notch, reduce, onTear, controlled };\n  const sim = useRef<Sim>({\n    raf: 0,\n    last: 0,\n    phase: 'idle',\n    id: null,\n    sign: 1,\n    hinge: { x: 0, y: 0 },\n    hingeV: 0,\n    grab: { x: 0, y: 0 },\n    start: { x: 0, y: 0 },\n    point: { x: 0, y: 0 },\n    a0: 0,\n    theta: 0,\n    thetaV: 0,\n    sx: 0,\n    sy: 0,\n    vx: 0,\n    vy: 0,\n    spin: 0,\n    pvx: 0,\n    pvy: 0,\n    pt: 0,\n    fade: 1,\n    age: 0,\n    bx: 0,\n    bv: 0,\n    snapped: [],\n    snapAt: [],\n    span: []\n  });\n\n  const tiltX = useSpring(0, TILT_SPRING);\n  const tiltY = useSpring(0, TILT_SPRING);\n  const plane = useMotionTemplate`perspective(${perspective}px) rotate(${rotate}deg) rotateX(${tiltX}deg) rotateY(${tiltY}deg)`;\n  const depth = tiltMax > 0 ? parallax / tiltMax : 0;\n  const artX = useTransform(tiltY, v => -v * depth);\n  const artY = useTransform(tiltX, v => v * depth);\n  const art = useMotionTemplate`translate(${artX}px, ${artY}px)`;\n  const inkX = useTransform(tiltY, v => v * depth * 0.22);\n  const inkY = useTransform(tiltX, v => -v * depth * 0.22);\n  const ink = useMotionTemplate`translate(${inkX}px, ${inkY}px)`;\n\n  useLayoutEffect(() => {\n    const el = rootRef.current;\n    if (!el) return undefined;\n    const measure = () => setFit(Math.min(1, el.clientWidth / width) || 1);\n    measure();\n    const ro = new ResizeObserver(measure);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [width]);\n\n  const paint = (now: number): boolean => {\n    const s = sim.current;\n    const c = cfg.current;\n    const stubEl = stubRef.current;\n    const bodyEl = bodyRef.current;\n    if (stubEl) {\n      stubEl.style.transform = `translate(${s.sx.toFixed(2)}px, ${s.sy.toFixed(2)}px) rotate(${((s.theta * s.sign * 180) / Math.PI).toFixed(3)}deg)`;\n      stubEl.style.opacity = s.fade.toFixed(3);\n    }\n    const up = c.geo.vertical;\n    if (bodyEl) bodyEl.style.transform = `translate${up ? 'Y' : 'X'}(${s.bx.toFixed(2)}px)`;\n    const cos = Math.cos(s.theta * s.sign);\n    const sin = Math.sin(s.theta * s.sign);\n    const lx = up ? 1.6 : 0;\n    const ly = up ? 0 : 1.6;\n    let busy = false;\n    c.geo.bridges.forEach((b, i) => {\n      const dx = b.x - s.hinge.x;\n      const dy = b.y - s.hinge.y;\n      const tx = s.hinge.x + dx * cos - dy * sin + s.sx;\n      const ty = s.hinge.y + dx * sin + dy * cos + s.sy;\n      const ox = b.x + (up ? 0 : s.bx);\n      const oy = b.y + (up ? s.bx : 0);\n      const gx = tx - ox;\n      const gy = ty - oy;\n      const gap = Math.hypot(gx, gy);\n      const near = fibres.current[i * 2];\n      const far = fibres.current[i * 2 + 1];\n      if (!near || !far) return;\n      const live = s.phase !== 'idle' && !c.reduce;\n      if (!s.snapped[i]) {\n        if (!live || gap < 0.35) {\n          near.style.opacity = '0';\n          far.style.opacity = '0';\n          return;\n        }\n        const k = clamp(gap / c.stretch, 0, 1);\n        const sag = gap * 0.18;\n        const w = (1.7 - 1.15 * k).toFixed(2);\n        const sx = (up ? sag : 0) + gx / 2;\n        const sy = (up ? 0 : sag) + gy / 2;\n        near.setAttribute(\n          'd',\n          `M${f(ox - lx)},${f(oy - ly)}Q${f(ox - lx + sx)},${f(oy - ly + sy)} ${f(tx - lx)},${f(ty - ly)}`\n        );\n        far.setAttribute(\n          'd',\n          `M${f(ox + lx)},${f(oy + ly)}Q${f(ox + lx + gx - sx)},${f(oy + ly + gy - sy)} ${f(tx + lx)},${f(ty + ly)}`\n        );\n        near.style.strokeWidth = w;\n        far.style.strokeWidth = w;\n        near.style.opacity = '1';\n        far.style.opacity = '1';\n        s.span[i] = gap;\n        return;\n      }\n      const t = (now - s.snapAt[i]) / 1000 / RETRACT;\n      if (!live || t >= 1 || !s.snapAt[i]) {\n        near.style.opacity = '0';\n        far.style.opacity = '0';\n        return;\n      }\n      busy = true;\n      const left = (1 - t) * (1 - t);\n      const len = (s.span[i] || c.stretch) * 0.5 * left;\n      const ux = gap > 0.01 ? gx / gap : 1;\n      const uy = gap > 0.01 ? gy / gap : 0;\n      near.setAttribute('d', `M${f(ox)},${f(oy)}L${f(ox + ux * len)},${f(oy + uy * len)}`);\n      far.setAttribute('d', `M${f(tx)},${f(ty)}L${f(tx - ux * len)},${f(ty - uy * len)}`);\n      near.style.strokeWidth = '0.9';\n      far.style.strokeWidth = '0.9';\n      near.style.opacity = left.toFixed(2);\n      far.style.opacity = left.toFixed(2);\n    });\n    return busy;\n  };\n\n  const finish = () => {\n    const c = cfg.current;\n    if (stubRef.current) stubRef.current.style.visibility = 'hidden';\n    if (!c.controlled) setInner(true);\n    c.onTear?.();\n  };\n\n  const step = (now: number) => {\n    const s = sim.current;\n    const c = cfg.current;\n    const dt = clamp((now - s.last) / 1000, 0.001, 0.034);\n    s.last = now;\n    const limit = rad(c.tearAngle);\n    if (s.phase === 'held') {\n      const count = c.geo.bridges.length;\n      let intact = 0;\n      for (let i = 0; i < count; i += 1) if (!s.snapped[i]) intact += 1;\n      const hold = count ? intact / count : 0;\n      const follow = 0.92 * (1 - clamp(c.resistance, 0, 0.95) * hold);\n      const a = Math.atan2(s.point.y - s.hinge.y, s.point.x - s.hinge.x);\n      const want = clamp(wrap(a - s.a0) * s.sign * follow, 0, limit + 0.1);\n      s.theta += (want - s.theta) * (1 - Math.exp(-dt / 0.035));\n      const up = c.geo.vertical;\n      const away = clamp(((up ? s.point.y - s.start.y : s.point.x - s.start.x) || 0) * 0.05, -2, 4);\n      const side = clamp(((up ? s.point.x - s.start.x : s.point.y - s.start.y) || 0) * 0.05, -3, 3);\n      const px = up ? side : away;\n      const py = up ? away : side;\n      s.sx += (px - s.sx) * (1 - Math.exp(-dt / 0.05));\n      s.sy += (py - s.sy) * (1 - Math.exp(-dt / 0.05));\n      const slack = Math.hypot(s.sx, s.sy);\n      let left = 0;\n      c.geo.bridges.forEach((b, i) => {\n        if (s.snapped[i]) return;\n        const d = Math.abs(b.mid - s.hingeV);\n        if (2 * d * Math.sin(s.theta / 2) + slack > c.stretch || s.theta >= limit) {\n          s.snapped[i] = true;\n          s.snapAt[i] = now;\n          s.bv -= 560 / c.geo.bridges.length;\n        } else left += 1;\n      });\n      if (left === 0) {\n        s.phase = 'free';\n        s.bv -= 150;\n      }\n    } else if (s.phase === 'free') {\n      const cos = Math.cos(s.theta * s.sign);\n      const sin = Math.sin(s.theta * s.sign);\n      const gx = s.grab.x - s.hinge.x;\n      const gy = s.grab.y - s.hinge.y;\n      const wx = s.point.x - s.hinge.x - (gx * cos - gy * sin);\n      const wy = s.point.y - s.hinge.y - (gx * sin + gy * cos);\n      s.sx += (wx - s.sx) * (1 - Math.exp(-dt / 0.045));\n      s.sy += (wy - s.sy) * (1 - Math.exp(-dt / 0.045));\n      const hang = limit * 0.55 + clamp(s.pvx * 0.0009 * s.sign, -0.3, 0.3);\n      s.theta += (hang - s.theta) * (1 - Math.exp(-dt / 0.12));\n    } else if (s.phase === 'drop') {\n      s.age += dt;\n      s.vy += GRAVITY * dt;\n      s.sx += s.vx * dt;\n      s.sy += s.vy * dt;\n      s.theta += s.spin * dt;\n      if (s.age > 0.16) s.fade = clamp(1 - (s.age - 0.16) / 0.42, 0, 1);\n      if (s.fade <= 0) {\n        s.phase = 'idle';\n        finish();\n      }\n    } else if (s.phase === 'return') {\n      s.thetaV += (-300 * s.theta - 24 * s.thetaV) * dt;\n      s.theta += s.thetaV * dt;\n      s.sx += (0 - s.sx) * (1 - Math.exp(-dt / 0.07));\n      s.sy += (0 - s.sy) * (1 - Math.exp(-dt / 0.07));\n      if (Math.abs(s.theta) < 0.0008 && Math.abs(s.thetaV) < 0.01 && Math.hypot(s.sx, s.sy) < 0.05) {\n        s.theta = 0;\n        s.thetaV = 0;\n        s.sx = 0;\n        s.sy = 0;\n        s.phase = 'idle';\n      }\n    }\n    s.bv += (-520 * s.bx - 30 * s.bv) * dt;\n    s.bx += s.bv * dt;\n    const busy = paint(now);\n    const moving = Math.abs(s.bx) > 0.02 || Math.abs(s.bv) > 0.5;\n    if (s.phase !== 'idle' || moving || busy) s.raf = requestAnimationFrame(step);\n    else {\n      s.bx = 0;\n      s.bv = 0;\n      paint(now);\n      s.raf = 0;\n    }\n  };\n  const run = () => {\n    const s = sim.current;\n    if (s.raf) return;\n    s.last = performance.now();\n    s.raf = requestAnimationFrame(step);\n  };\n\n  const reset = () => {\n    const s = sim.current;\n    cancelAnimationFrame(s.raf);\n    Object.assign(s, {\n      raf: 0,\n      phase: 'idle',\n      id: null,\n      theta: 0,\n      thetaV: 0,\n      sx: 0,\n      sy: 0,\n      fade: 1,\n      age: 0,\n      bx: 0,\n      bv: 0\n    });\n    s.snapped = [];\n    s.snapAt = [];\n    s.span = [];\n    if (stubRef.current) stubRef.current.style.visibility = '';\n    paint(performance.now());\n  };\n\n  useEffect(() => {\n    if (used) {\n      const s = sim.current;\n      if (s.phase === 'idle' && stubRef.current) stubRef.current.style.visibility = 'hidden';\n      return;\n    }\n    setInstant(false);\n    reset();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [used]);\n  useEffect(() => {\n    reset();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [geo]);\n  useEffect(() => {\n    const s = sim.current;\n    return () => cancelAnimationFrame(s.raf);\n  }, []);\n\n  const local = (e: PointerEvent<HTMLDivElement>): Point => {\n    const r = stageRef.current!.getBoundingClientRect();\n    const k = r.width / width || 1;\n    return { x: (e.clientX - r.left) / k, y: (e.clientY - r.top) / k };\n  };\n  const tearNow = () => {\n    const s = sim.current;\n    cancelAnimationFrame(s.raf);\n    s.raf = 0;\n    s.phase = 'idle';\n    setInstant(true);\n    finish();\n  };\n  const onStubDown = (e: PointerEvent<HTMLDivElement>) => {\n    const s = sim.current;\n    if (disabled || used || e.button !== 0 || s.id !== null || s.phase === 'drop') return;\n    try {\n      e.currentTarget.setPointerCapture(e.pointerId);\n    } catch {}\n    const p = local(e);\n    s.id = e.pointerId;\n    s.start = p;\n    s.point = p;\n    s.pt = performance.now();\n    s.pvx = 0;\n    s.pvy = 0;\n    if (s.theta < 0.01) {\n      const far = (geo.vertical ? p.x : p.y) < geo.cross / 2;\n      const end = geo.ends[far ? 1 : 0];\n      s.sign = (far ? 1 : -1) * (geo.vertical ? -1 : 1);\n      s.hinge = { x: end.x, y: end.y };\n      s.hingeV = end.v;\n      if (stubRef.current) stubRef.current.style.transformOrigin = `${s.hinge.x}px ${s.hinge.y}px`;\n    }\n    const cos = Math.cos(-s.theta * s.sign);\n    const sin = Math.sin(-s.theta * s.sign);\n    const ux = p.x - s.sx - s.hinge.x;\n    const uy = p.y - s.sy - s.hinge.y;\n    s.grab = { x: s.hinge.x + ux * cos - uy * sin, y: s.hinge.y + ux * sin + uy * cos };\n    s.a0 = Math.atan2(s.grab.y - s.hinge.y, s.grab.x - s.hinge.x) - (s.theta * s.sign) / 0.92;\n    s.phase = 'held';\n    s.thetaV = 0;\n    tiltX.set(0);\n    tiltY.set(0);\n    setGrabbing(true);\n    run();\n  };\n  const onStubMove = (e: PointerEvent<HTMLDivElement>) => {\n    const s = sim.current;\n    if (s.id !== e.pointerId) return;\n    const p = local(e);\n    const now = performance.now();\n    const dt = Math.max(0.004, (now - s.pt) / 1000);\n    s.pvx += ((p.x - s.point.x) / dt - s.pvx) * 0.35;\n    s.pvy += ((p.y - s.point.y) / dt - s.pvy) * 0.35;\n    s.pt = now;\n    s.point = p;\n    if (cfg.current.reduce && Math.hypot(p.x - s.start.x, p.y - s.start.y) > 28) {\n      s.id = null;\n      setGrabbing(false);\n      tearNow();\n    }\n  };\n  const onStubUp = (e: PointerEvent<HTMLDivElement>) => {\n    const s = sim.current;\n    if (s.id !== e.pointerId) return;\n    s.id = null;\n    try {\n      if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);\n    } catch {}\n    setGrabbing(false);\n    if (s.phase === 'free') {\n      const still = performance.now() - s.pt > 80;\n      s.vx = still ? 0 : clamp(s.pvx, -1600, 1600);\n      s.vy = still ? 0 : clamp(s.pvy, -1600, 1200);\n      s.spin = clamp(s.vx * 0.004, -6, 6) + 1.2 * s.sign;\n      s.age = 0;\n      s.phase = 'drop';\n    } else if (s.phase === 'held') {\n      s.phase = 'return';\n    }\n    run();\n  };\n  const onStubKey = (e: KeyboardEvent<HTMLDivElement>) => {\n    if (disabled || used || (e.key !== 'Enter' && e.key !== ' ')) return;\n    e.preventDefault();\n    if (!e.repeat) tearNow();\n  };\n\n  useEffect(() => {\n    if (!tilt || reduce || disabled) return undefined;\n    const move = (e: globalThis.PointerEvent) => {\n      const el = rootRef.current;\n      if (!el || e.pointerType === 'touch' || sim.current.id !== null) return;\n      const r = el.getBoundingClientRect();\n      const nx = clamp((e.clientX - (r.left + r.width / 2)) / (r.width / 2 + tiltReach), -1, 1);\n      const ny = clamp((e.clientY - (r.top + r.height / 2)) / (r.height / 2 + tiltReach), -1, 1);\n      tiltY.set(nx * tiltMax);\n      tiltX.set(-ny * tiltMax);\n    };\n    window.addEventListener('pointermove', move);\n    return () => window.removeEventListener('pointermove', move);\n  }, [tilt, reduce, disabled, tiltMax, tiltReach, tiltX, tiltY]);\n\n  return (\n    <div\n      ref={rootRef}\n      className={`group relative select-none [width:min(var(--tt-w),100%)] [color:var(--tt-ink)] [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] data-[disabled]:opacity-60${className ? ` ${className}` : ''}`}\n      data-used={used ? '' : undefined}\n      data-orientation={orientation}\n      data-shift={used && recenter ? (vertical ? 'y' : 'x') : undefined}\n      data-instant={instant ? '' : undefined}\n      data-grabbing={grabbing ? '' : undefined}\n      data-disabled={disabled ? '' : undefined}\n      style={\n        {\n          '--tt-w': `${width}px`,\n          '--tt-h': `${height}px`,\n          '--tt-stub': `${stubSize}px`,\n          '--tt-bg': background,\n          '--tt-stub-bg': stubBackground || background,\n          '--tt-ink': color,\n          '--tt-edge': borderColor || `color-mix(in srgb, ${color} 16%, transparent)`,\n          '--tt-edge-w': borderWidth,\n          '--tt-parallax': `${parallax}px`,\n          '--tt-body-w': `${vertical ? width : width - stubSize}px`,\n          '--tt-body-h': `${vertical ? height - stubSize : height}px`,\n          '--tt-inset': `${ART_INSET}px`,\n          '--tt-span': ART_SPAN,\n          '--tt-art-radius': `${imageRadius}px`,\n          '--tt-fit': fit,\n          height: `${height * fit}px`\n        } as CSSProperties\n      }\n    >\n      <div\n        ref={stageRef}\n        className=\"absolute top-0 left-0 origin-top-left [width:var(--tt-w)] [height:var(--tt-h)] [transform:scale(var(--tt-fit))] [transition:transform_650ms_cubic-bezier(0.22,1,0.36,1)] group-data-[shift=x]:[transform:translateX(calc(var(--tt-stub)*var(--tt-fit)/2))_scale(var(--tt-fit))] group-data-[shift=y]:[transform:translateY(calc(var(--tt-stub)*var(--tt-fit)/2))_scale(var(--tt-fit))] group-data-[instant]:[transition-duration:0ms] motion-reduce:[transition:none]\"\n      >\n        <motion.div className=\"absolute inset-0\" style={{ transform: plane }}>\n          <div ref={bodyRef} className=\"pointer-events-none absolute inset-0\">\n            {border ? (\n              <svg\n                className=\"pointer-events-none absolute inset-0 h-full w-full overflow-visible [&>path]:fill-none [&>path]:[stroke:var(--tt-edge)] [&>path]:[stroke-width:var(--tt-edge-w)]\"\n                viewBox={`0 0 ${width} ${height}`}\n                aria-hidden=\"true\"\n              >\n                <path d={geo.bodyOutline} />\n              </svg>\n            ) : null}\n            <div\n              className=\"pointer-events-auto absolute inset-0 [background:var(--tt-bg)]\"\n              style={{ clipPath: `path('${geo.body}')` }}\n            >\n              {image ? (\n                <div className=\"absolute overflow-hidden [top:var(--tt-inset)] [left:var(--tt-inset)] [width:calc(var(--tt-body-w)_-_var(--tt-inset)*2)] [height:calc(var(--tt-body-h)*var(--tt-span)_-_var(--tt-inset)*2)] [border-radius:var(--tt-art-radius)]\">\n                  <motion.img\n                    className=\"absolute max-w-none object-cover [top:calc(var(--tt-parallax)*-1)] [left:calc(var(--tt-parallax)*-1)] [width:calc(100%_+_var(--tt-parallax)*2)] [height:calc(100%_+_var(--tt-parallax)*2)] [-webkit-user-drag:none] [transition:filter_700ms_ease] group-data-[used]:[filter:grayscale(1)_brightness(0.75)] group-data-[instant]:[transition-duration:0ms]\"\n                    src={image}\n                    alt={imageAlt}\n                    draggable={false}\n                    style={reduce ? undefined : { transform: art }}\n                  />\n                  {scrim ? (\n                    <div className=\"absolute inset-0 [background:linear-gradient(to_top,var(--tt-bg)_0%,color-mix(in_srgb,var(--tt-bg)_72%,transparent)_30%,color-mix(in_srgb,var(--tt-bg)_18%,transparent)_58%,transparent_80%)]\" />\n                  ) : null}\n                </div>\n              ) : null}\n              <motion.div\n                className=\"absolute inset-y-0 left-0 [width:calc(var(--tt-w)_-_var(--tt-stub))] [transition:opacity_500ms_ease] group-data-[used]:opacity-55 group-data-[instant]:[transition-duration:0ms] group-data-[orientation=vertical]:bottom-auto group-data-[orientation=vertical]:w-full group-data-[orientation=vertical]:[height:calc(var(--tt-h)_-_var(--tt-stub))]\"\n                style={reduce ? undefined : { transform: ink }}\n              >\n                {children}\n              </motion.div>\n            </div>\n          </div>\n          <svg\n            className=\"pointer-events-none absolute inset-0 h-full w-full overflow-visible [&_path]:fill-none [&_path]:opacity-0 [&_path]:[stroke:var(--tt-bg)] [&_path]:[stroke-linecap:round]\"\n            aria-hidden=\"true\"\n          >\n            {geo.bridges.map((b, i) => (\n              <g key={i}>\n                <path\n                  ref={el => {\n                    fibres.current[i * 2] = el;\n                  }}\n                />\n                <path\n                  ref={el => {\n                    fibres.current[i * 2 + 1] = el;\n                  }}\n                />\n              </g>\n            ))}\n          </svg>\n          <div\n            ref={stubRef}\n            className=\"pointer-events-none absolute inset-0 outline-none will-change-transform\"\n            role=\"button\"\n            tabIndex={disabled || used ? -1 : 0}\n            aria-label={ariaLabel}\n            aria-hidden={used || undefined}\n            aria-disabled={disabled || undefined}\n            onPointerDown={onStubDown}\n            onPointerMove={onStubMove}\n            onPointerUp={onStubUp}\n            onPointerCancel={onStubUp}\n            onLostPointerCapture={onStubUp}\n            onKeyDown={onStubKey}\n            onDragStart={e => e.preventDefault()}\n          >\n            {border ? (\n              <svg\n                className=\"pointer-events-none absolute inset-0 h-full w-full overflow-visible [&>path]:fill-none [&>path]:[stroke:var(--tt-edge)] [&>path]:[stroke-width:var(--tt-edge-w)]\"\n                viewBox={`0 0 ${width} ${height}`}\n                aria-hidden=\"true\"\n              >\n                <path d={geo.stubOutline} />\n              </svg>\n            ) : null}\n            <div\n              className=\"pointer-events-auto absolute inset-0 cursor-grab touch-none [background:var(--tt-stub-bg)] group-data-[grabbing]:cursor-grabbing group-data-[disabled]:cursor-default\"\n              style={{ clipPath: `path('${geo.stub}')` }}\n            >\n              <div className=\"absolute inset-y-0 right-0 [width:var(--tt-stub)] group-data-[orientation=vertical]:top-auto group-data-[orientation=vertical]:left-0 group-data-[orientation=vertical]:w-auto group-data-[orientation=vertical]:[height:var(--tt-stub)]\">\n                {stub}\n              </div>\n            </div>\n          </div>\n        </motion.div>\n      </div>\n      <span className=\"absolute h-px w-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]\" role=\"status\">\n        {used ? 'Used' : ''}\n      </span>\n    </div>\n  );\n};\n\nexport default TearTicket;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}