{"version":3,"sources":["../src/core/keywords.ts","../src/react/WordTunnel.tsx"],"names":["useId","useMemo","useState","useEffect","jsxs","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * 词云隧道 — 经典 3D 星空隧道布局\n *\n * xy：环外随机 + 最小间距拒绝采样\n * z：只沿深度飞，perspective 汇向灭点\n * 词数：1440→80、1920→120、2560→160\n */\n\nexport type TunnelWord = {\n  label: string\n  x: string\n  y: string\n  z0: number\n  z1: number\n  opacity: number\n  duration: number\n  delay: number\n}\n\n/** 默认搞笑词条（可被 props.words 覆盖） */\nexport const DEFAULT_WORDS = [\n  '我本地是好的',\n  '先上线再说',\n  '这不是 bug',\n  '是 feature',\n  '需求又改了',\n  '估计半小时',\n  '今天不加班',\n  '已读不回',\n  '在吗急',\n  '线上炸了',\n  '回滚回滚',\n  '能跑就行',\n  'LGTM',\n  '先 merge',\n  '文档？不存在',\n  '临时方案永久用',\n  '谁写的破代码',\n  '哦是我写的',\n  '只有上帝懂了',\n  '神秘空指针',\n  '依赖地狱',\n  '周五上线快乐',\n  '产品说随便改改',\n  '后端说前端的锅',\n  '前端说后端的锅',\n  '测过了吗',\n  '没测但感觉没问题',\n  '复制粘贴工程师',\n  'Ctrl+C Ctrl+V',\n  '热更新一下',\n  '没事没事',\n  '绝对没问题',\n  '大概也许可能',\n  '重构？下次一定',\n  '注释写给鬼看的',\n  'npm install 三小时',\n  '冲突自己解决',\n  '下班了吗',\n  '有空帮看下',\n  '又是一个小需求',\n] as const\n\nexport const DEFAULTS = {\n  seed: 20250913,\n  perspective: 900,\n  /** 中心空洞半径（越小词越靠近灭点） */\n  innerRadius: 8,\n  /** 外圈采样半径 */\n  outerRadius: 52,\n  /** z 轴最短飞行距离 */\n  travelMin: 480,\n  /** z 轴额外随机飞行距离 */\n  travelRange: 560,\n  /** 淡入完成进度 0–100 */\n  fadeIn: 10,\n  /** 开始淡出进度 0–100（越大越晚消失、越贴中心） */\n  fadeOut: 88,\n} as const\n\nexport type WordTunnelLayout = {\n  innerRadius: number\n  outerRadius: number\n  travelMin: number\n  travelRange: number\n}\n\nfunction mulberry32(seed: number) {\n  return () => {\n    let t = (seed += 0x6d2b79f5)\n    t = Math.imul(t ^ (t >>> 15), t | 1)\n    t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n  }\n}\n\ntype Placed = { px: number; py: number; nearness: number; weight: number }\n\n/** 1440→80、1920→120、2560→160；每约 480px 一档 +40 */\nexport function wordCountForWidth(width: number): number {\n  const steps = Math.round((width - 1440) / 480)\n  return Math.min(280, Math.max(40, 80 + steps * 40))\n}\n\nfunction baseMinDistance(\n  count: number,\n  innerRadius: number,\n  outerRadius: number,\n): number {\n  const area =\n    Math.PI * (outerRadius * 1.12) ** 2 - Math.PI * innerRadius ** 2\n  return Math.min(13, Math.max(5.5, Math.sqrt(Math.max(area, 1) / count) * 0.92))\n}\n\nfunction tooClose(a: Placed, b: Placed, baseMin: number): boolean {\n  const scale = 0.65 + 0.55 * ((a.nearness + b.nearness) / 2)\n  const weight = (a.weight + b.weight) / 2\n  const min = baseMin * scale * weight\n  const dx = a.px - b.px\n  const dy = a.py - b.py\n  return dx * dx + dy * dy < min * min\n}\n\nfunction samplePoint(\n  rnd: () => number,\n  innerRadius: number,\n  outerRadius: number,\n): { px: number; py: number } {\n  const span = Math.max(0.01, outerRadius - innerRadius)\n  for (let tryN = 0; tryN < 32; tryN++) {\n    const angle = rnd() * Math.PI * 2\n    const r = innerRadius + Math.sqrt(rnd()) * span\n    const px = Math.cos(angle) * r * 1.18\n    const py = Math.sin(angle) * r * 0.9\n    if (Math.hypot(px, py) >= innerRadius) return { px, py }\n  }\n  return { px: innerRadius * 1.2, py: 0 }\n}\n\nfunction labelWeight(label: string): number {\n  const len = [...label].length\n  return Math.min(1.35, Math.max(0.9, 0.85 + len * 0.035))\n}\n\nfunction clampPct(n: number): number {\n  return Math.min(99, Math.max(1, Math.round(n)))\n}\n\n/** 生成可配置淡入淡出的 keyframes CSS */\nexport function buildConvergeKeyframes(\n  name: string,\n  fadeIn: number = DEFAULTS.fadeIn,\n  fadeOut: number = DEFAULTS.fadeOut,\n): string {\n  let inn = clampPct(fadeIn)\n  let out = clampPct(fadeOut)\n  if (out <= inn) out = Math.min(99, inn + 1)\n\n  return `@keyframes ${name} {\n  0% {\n    opacity: 0;\n    transform: translate3d(\n      calc(-50% + var(--fx-wt-x)),\n      calc(-50% + var(--fx-wt-y)),\n      var(--fx-wt-z0)\n    );\n  }\n  ${inn}% {\n    opacity: var(--fx-wt-op);\n  }\n  ${out}% {\n    opacity: calc(var(--fx-wt-op) * 0.7);\n  }\n  100% {\n    opacity: 0;\n    transform: translate3d(\n      calc(-50% + var(--fx-wt-x)),\n      calc(-50% + var(--fx-wt-y)),\n      var(--fx-wt-z1)\n    );\n  }\n}`\n}\n\nexport type BuildWordsOptions = {\n  count?: number\n  seed?: number\n  words?: readonly string[]\n  innerRadius?: number\n  outerRadius?: number\n  travelMin?: number\n  travelRange?: number\n}\n\nexport function buildWords(options: BuildWordsOptions = {}): TunnelWord[] {\n  const words = options.words?.length ? options.words : DEFAULT_WORDS\n  const count = options.count ?? 80\n  const seed = options.seed ?? DEFAULTS.seed\n  const innerRadius = options.innerRadius ?? DEFAULTS.innerRadius\n  const outerRadius = Math.max(\n    innerRadius + 1,\n    options.outerRadius ?? DEFAULTS.outerRadius,\n  )\n  const travelMin = options.travelMin ?? DEFAULTS.travelMin\n  const travelRange = Math.max(0, options.travelRange ?? DEFAULTS.travelRange)\n\n  const rnd = mulberry32(seed + count * 997)\n  const list: TunnelWord[] = []\n  const placed: Placed[] = []\n  const baseMin = baseMinDistance(count, innerRadius, outerRadius)\n\n  for (let i = 0; i < count; i++) {\n    const label = words[Math.floor(rnd() * words.length)]!\n    const weight = labelWeight(label)\n\n    const z0 = Math.round(-80 + rnd() * 280)\n    const nearness = Math.min(1, Math.max(0, (z0 + 80) / 280))\n\n    let px = 0\n    let py = 0\n    let ok = false\n\n    for (let relax = 0; relax < 6 && !ok; relax++) {\n      const minFactor = 1 - relax * 0.12\n      const tries = 40 + relax * 20\n      for (let t = 0; t < tries; t++) {\n        const p = samplePoint(rnd, innerRadius, outerRadius)\n        const candidate: Placed = { px: p.px, py: p.py, nearness, weight }\n        if (\n          placed.every((o) => !tooClose(candidate, o, baseMin * minFactor))\n        ) {\n          px = p.px\n          py = p.py\n          ok = true\n          break\n        }\n      }\n    }\n\n    if (!ok) {\n      const p = samplePoint(rnd, innerRadius, outerRadius)\n      px = p.px\n      py = p.py\n    }\n\n    placed.push({ px, py, nearness, weight })\n\n    const travel = travelMin + rnd() * travelRange\n    const z1 = Math.round(z0 - travel)\n    const opacity =\n      Math.round((0.18 + nearness * 0.72 + rnd() * 0.08) * 100) / 100\n    const duration =\n      Math.round((5.5 + (1 - nearness) * 6 + rnd() * 5) * 100) / 100\n    const delay = Math.round(-rnd() * duration * 100) / 100\n\n    list.push({\n      label,\n      x: `${Math.round(px * 100) / 100}vw`,\n      y: `${Math.round(py * 100) / 100}vh`,\n      z0,\n      z1,\n      opacity,\n      duration,\n      delay,\n    })\n  }\n\n  return list\n}\n","'use client'\n\nimport {\n  useEffect,\n  useId,\n  useMemo,\n  useState,\n  type CSSProperties,\n  type ReactNode,\n} from 'react'\nimport {\n  DEFAULTS,\n  buildConvergeKeyframes,\n  buildWords,\n  wordCountForWidth,\n  type TunnelWord,\n} from '../core/keywords'\nimport '../core/styles.css'\n\nexport type WordTunnelProps = {\n  /** 词条列表；不传则用内置搞笑默认词。 */\n  words?: readonly string[]\n  /** 固定词数；不传则按视口宽度自适应。 */\n  count?: number\n  /** 布局随机种子：同值可复现，换值换分布。默认 `20250913`。 */\n  seed?: number\n  /** CSS perspective，默认 900。 */\n  perspective?: number\n  /** 中心空洞半径，越小词越靠近灭点。默认 8。 */\n  innerRadius?: number\n  /** 外圈采样半径。默认 52。 */\n  outerRadius?: number\n  /** z 轴最短飞行距离，越大消隐越靠近中心。默认 480。 */\n  travelMin?: number\n  /** z 轴额外随机飞行距离。默认 560。 */\n  travelRange?: number\n  /** 淡入完成进度 0–100。默认 10。 */\n  fadeIn?: number\n  /** 开始淡出进度 0–100，越大越晚消失。默认 88。 */\n  fadeOut?: number\n  /** 文字颜色。 */\n  color?: string\n  /** 字号，默认 24px。 */\n  fontSize?: number | string\n  className?: string\n  style?: CSSProperties\n  children?: ReactNode\n  ariaHidden?: boolean\n}\n\nexport function WordTunnel({\n  words,\n  count,\n  seed = DEFAULTS.seed,\n  perspective = DEFAULTS.perspective,\n  innerRadius = DEFAULTS.innerRadius,\n  outerRadius = DEFAULTS.outerRadius,\n  travelMin = DEFAULTS.travelMin,\n  travelRange = DEFAULTS.travelRange,\n  fadeIn = DEFAULTS.fadeIn,\n  fadeOut = DEFAULTS.fadeOut,\n  color,\n  fontSize,\n  className,\n  style,\n  children,\n  ariaHidden = true,\n}: WordTunnelProps) {\n  const uid = useId().replace(/:/g, '')\n  const animName = `fx-wt-converge-${uid}`\n\n  const layout = useMemo(\n    () => ({\n      seed,\n      words,\n      innerRadius,\n      outerRadius,\n      travelMin,\n      travelRange,\n    }),\n    [seed, words, innerRadius, outerRadius, travelMin, travelRange],\n  )\n\n  const [items, setItems] = useState<TunnelWord[]>(() =>\n    buildWords({\n      count: count ?? 80,\n      ...layout,\n    }),\n  )\n\n  useEffect(() => {\n    function rebuild() {\n      const nextCount = count ?? wordCountForWidth(window.innerWidth)\n      setItems(buildWords({ count: nextCount, ...layout }))\n    }\n\n    rebuild()\n\n    if (count != null) return\n\n    let timer: ReturnType<typeof setTimeout> | undefined\n    function onResize() {\n      if (timer) clearTimeout(timer)\n      timer = setTimeout(rebuild, 150)\n    }\n\n    window.addEventListener('resize', onResize)\n    return () => {\n      if (timer) clearTimeout(timer)\n      window.removeEventListener('resize', onResize)\n    }\n  }, [count, layout])\n\n  const keyframesCss = useMemo(\n    () => buildConvergeKeyframes(animName, fadeIn, fadeOut),\n    [animName, fadeIn, fadeOut],\n  )\n\n  const rootStyle: CSSProperties = {\n    ...style,\n    ['--fx-wt-perspective' as string]: `${perspective}px`,\n    ['--fx-wt-anim' as string]: animName,\n    ...(color ? { ['--fx-wt-color' as string]: color } : null),\n    ...(fontSize != null\n      ? {\n          ['--fx-wt-font-size' as string]:\n            typeof fontSize === 'number' ? `${fontSize}px` : fontSize,\n        }\n      : null),\n  }\n\n  return (\n    <div className={['fx-wt', className].filter(Boolean).join(' ')} style={rootStyle}>\n      <style>{keyframesCss}</style>\n      <div className=\"fx-wt__stage\" aria-hidden={ariaHidden}>\n        <div className=\"fx-wt__space\">\n          {items.map((item, i) => (\n            <span\n              key={`${items.length}-${i}-${item.label}`}\n              className=\"fx-wt__word\"\n              style={{\n                ['--fx-wt-x' as string]: item.x,\n                ['--fx-wt-y' as string]: item.y,\n                ['--fx-wt-z0' as string]: `${item.z0}px`,\n                ['--fx-wt-z1' as string]: `${item.z1}px`,\n                ['--fx-wt-op' as string]: String(item.opacity),\n                animationDuration: `${item.duration}s`,\n                animationDelay: `${item.delay}s`,\n              }}\n            >\n              {item.label}\n            </span>\n          ))}\n        </div>\n      </div>\n      {children != null ? <div className=\"fx-wt__content\">{children}</div> : null}\n    </div>\n  )\n}\n\nexport default WordTunnel\n"]}