@akiojin/gwt 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/claude.d.ts +2 -0
  2. package/dist/claude.d.ts.map +1 -1
  3. package/dist/claude.js +49 -2
  4. package/dist/claude.js.map +1 -1
  5. package/dist/cli/ui/components/App.d.ts.map +1 -1
  6. package/dist/cli/ui/components/App.js +68 -68
  7. package/dist/cli/ui/components/App.js.map +1 -1
  8. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  9. package/dist/cli/ui/components/screens/BranchListScreen.js +6 -1
  10. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  11. package/dist/client/assets/index-ChHC-Puh.css +1 -0
  12. package/dist/client/assets/index-PqK9jkug.js +78 -0
  13. package/dist/client/index.html +2 -2
  14. package/dist/config/builtin-tools.d.ts.map +1 -1
  15. package/dist/config/builtin-tools.js +3 -0
  16. package/dist/config/builtin-tools.js.map +1 -1
  17. package/dist/config/tools.d.ts.map +1 -1
  18. package/dist/config/tools.js +10 -1
  19. package/dist/config/tools.js.map +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/launcher.d.ts.map +1 -1
  24. package/dist/launcher.js +15 -0
  25. package/dist/launcher.js.map +1 -1
  26. package/dist/services/aiToolResolver.d.ts.map +1 -1
  27. package/dist/services/aiToolResolver.js +55 -8
  28. package/dist/services/aiToolResolver.js.map +1 -1
  29. package/dist/services/customToolResolver.d.ts.map +1 -1
  30. package/dist/services/customToolResolver.js +22 -17
  31. package/dist/services/customToolResolver.js.map +1 -1
  32. package/dist/utils/webui.js +1 -1
  33. package/dist/web/client/src/components/BranchGraph.d.ts +5 -0
  34. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  35. package/dist/web/client/src/components/BranchGraph.js +35 -108
  36. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  37. package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts +15 -0
  38. package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts.map +1 -0
  39. package/dist/web/client/src/components/graph/BranchDetailPanel.js +57 -0
  40. package/dist/web/client/src/components/graph/BranchDetailPanel.js.map +1 -0
  41. package/dist/web/client/src/components/graph/BranchNode.d.ts +13 -0
  42. package/dist/web/client/src/components/graph/BranchNode.d.ts.map +1 -0
  43. package/dist/web/client/src/components/graph/BranchNode.js +103 -0
  44. package/dist/web/client/src/components/graph/BranchNode.js.map +1 -0
  45. package/dist/web/client/src/components/graph/ClusterNode.d.ts +13 -0
  46. package/dist/web/client/src/components/graph/ClusterNode.d.ts.map +1 -0
  47. package/dist/web/client/src/components/graph/ClusterNode.js +109 -0
  48. package/dist/web/client/src/components/graph/ClusterNode.js.map +1 -0
  49. package/dist/web/client/src/components/graph/SynapticCanvas.d.ts +17 -0
  50. package/dist/web/client/src/components/graph/SynapticCanvas.d.ts.map +1 -0
  51. package/dist/web/client/src/components/graph/SynapticCanvas.js +94 -0
  52. package/dist/web/client/src/components/graph/SynapticCanvas.js.map +1 -0
  53. package/dist/web/client/src/components/graph/SynapticEdge.d.ts +13 -0
  54. package/dist/web/client/src/components/graph/SynapticEdge.d.ts.map +1 -0
  55. package/dist/web/client/src/components/graph/SynapticEdge.js +113 -0
  56. package/dist/web/client/src/components/graph/SynapticEdge.js.map +1 -0
  57. package/dist/web/client/src/components/graph/graphUtils.d.ts +67 -0
  58. package/dist/web/client/src/components/graph/graphUtils.d.ts.map +1 -0
  59. package/dist/web/client/src/components/graph/graphUtils.js +175 -0
  60. package/dist/web/client/src/components/graph/graphUtils.js.map +1 -0
  61. package/dist/web/client/src/components/graph/index.d.ts +10 -0
  62. package/dist/web/client/src/components/graph/index.d.ts.map +1 -0
  63. package/dist/web/client/src/components/graph/index.js +10 -0
  64. package/dist/web/client/src/components/graph/index.js.map +1 -0
  65. package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
  66. package/dist/web/client/src/lib/websocket.js +2 -1
  67. package/dist/web/client/src/lib/websocket.js.map +1 -1
  68. package/dist/web/client/vite.config.js +1 -1
  69. package/dist/web/server/env/importer.d.ts.map +1 -1
  70. package/dist/web/server/env/importer.js +4 -0
  71. package/dist/web/server/env/importer.js.map +1 -1
  72. package/dist/web/server/index.d.ts.map +1 -1
  73. package/dist/web/server/index.js +9 -0
  74. package/dist/web/server/index.js.map +1 -1
  75. package/dist/web/server/pty/manager.d.ts.map +1 -1
  76. package/dist/web/server/pty/manager.js +24 -1
  77. package/dist/web/server/pty/manager.js.map +1 -1
  78. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  79. package/dist/web/server/routes/sessions.js +7 -0
  80. package/dist/web/server/routes/sessions.js.map +1 -1
  81. package/dist/web/server/tray.d.ts +1 -1
  82. package/dist/web/server/tray.d.ts.map +1 -1
  83. package/dist/web/server/tray.js +52 -34
  84. package/dist/web/server/tray.js.map +1 -1
  85. package/dist/web/server/websocket/handler.d.ts.map +1 -1
  86. package/dist/web/server/websocket/handler.js +4 -0
  87. package/dist/web/server/websocket/handler.js.map +1 -1
  88. package/package.json +5 -2
  89. package/src/claude.ts +57 -2
  90. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +104 -0
  91. package/src/cli/ui/components/App.tsx +91 -81
  92. package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -1
  93. package/src/cli/ui/types.ts +1 -1
  94. package/src/config/builtin-tools.ts +3 -0
  95. package/src/config/tools.ts +24 -1
  96. package/src/index.ts +3 -1
  97. package/src/launcher.ts +26 -0
  98. package/src/services/aiToolResolver.ts +75 -9
  99. package/src/services/customToolResolver.ts +32 -17
  100. package/src/utils/webui.ts +1 -1
  101. package/src/web/client/src/components/BranchGraph.tsx +51 -208
  102. package/src/web/client/src/components/graph/BranchDetailPanel.tsx +152 -0
  103. package/src/web/client/src/components/graph/BranchNode.tsx +200 -0
  104. package/src/web/client/src/components/graph/ClusterNode.tsx +211 -0
  105. package/src/web/client/src/components/graph/SynapticCanvas.tsx +171 -0
  106. package/src/web/client/src/components/graph/SynapticEdge.tsx +311 -0
  107. package/src/web/client/src/components/graph/graphUtils.ts +265 -0
  108. package/src/web/client/src/components/graph/index.ts +10 -0
  109. package/src/web/client/src/index.css +314 -29
  110. package/src/web/client/src/lib/websocket.ts +2 -1
  111. package/src/web/client/vite.config.ts +1 -1
  112. package/src/web/server/env/importer.ts +5 -0
  113. package/src/web/server/index.ts +10 -0
  114. package/src/web/server/pty/manager.ts +43 -1
  115. package/src/web/server/routes/sessions.ts +15 -0
  116. package/src/web/server/tray.ts +62 -46
  117. package/src/web/server/websocket/handler.ts +13 -0
  118. package/dist/client/assets/index-DsDNCy5f.css +0 -1
  119. package/dist/client/assets/index-v8smkNOL.js +0 -72
@@ -0,0 +1,152 @@
1
+ /**
2
+ * ブランチ詳細パネル
3
+ *
4
+ * ノードクリック時に右側に表示されるサイドパネル
5
+ */
6
+
7
+ import React from "react";
8
+ import { Link } from "react-router-dom";
9
+ import { X } from "lucide-react";
10
+ import { Badge } from "@/components/ui/badge";
11
+ import { Button } from "@/components/ui/button";
12
+ import {
13
+ Card,
14
+ CardHeader,
15
+ CardContent,
16
+ CardFooter,
17
+ } from "@/components/ui/card";
18
+ import { cn } from "@/lib/utils";
19
+ import type { Branch } from "../../../../../types/api.js";
20
+
21
+ interface BranchDetailPanelProps {
22
+ branch: Branch | null;
23
+ onClose: () => void;
24
+ className?: string;
25
+ }
26
+
27
+ export function BranchDetailPanel({
28
+ branch,
29
+ onClose,
30
+ className,
31
+ }: BranchDetailPanelProps) {
32
+ if (!branch) return null;
33
+
34
+ const hasWorktree = Boolean(branch.worktreePath);
35
+
36
+ return (
37
+ <div
38
+ className={cn(
39
+ "absolute right-0 top-0 h-full w-80 border-l bg-card/95 backdrop-blur-sm",
40
+ "animate-in slide-in-from-right duration-300",
41
+ className,
42
+ )}
43
+ >
44
+ <Card className="h-full rounded-none border-0">
45
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
46
+ <div className="flex items-center gap-2">
47
+ <Badge variant={branch.type === "local" ? "local" : "remote"}>
48
+ {branch.type === "local" ? "Local" : "Remote"}
49
+ </Badge>
50
+ {hasWorktree && <Badge variant="success">Worktree</Badge>}
51
+ </div>
52
+ <Button
53
+ variant="ghost"
54
+ size="icon"
55
+ onClick={onClose}
56
+ className="h-8 w-8"
57
+ >
58
+ <X className="h-4 w-4" />
59
+ </Button>
60
+ </CardHeader>
61
+
62
+ <CardContent className="space-y-4">
63
+ {/* ブランチ名 */}
64
+ <div>
65
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
66
+ Branch Name
67
+ </p>
68
+ <p className="mt-1 break-all font-mono text-sm">{branch.name}</p>
69
+ </div>
70
+
71
+ {/* ベースブランチ */}
72
+ <div>
73
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
74
+ Base Branch
75
+ </p>
76
+ <p className="mt-1 font-mono text-sm">
77
+ {branch.baseBranch ?? "Unknown"}
78
+ </p>
79
+ </div>
80
+
81
+ {/* コミットメッセージ */}
82
+ <div>
83
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
84
+ Last Commit
85
+ </p>
86
+ <p className="mt-1 text-sm text-muted-foreground">
87
+ {branch.commitMessage ?? "No commit message"}
88
+ </p>
89
+ </div>
90
+
91
+ {/* Divergence */}
92
+ {branch.divergence && (
93
+ <div>
94
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
95
+ Divergence
96
+ </p>
97
+ <div className="mt-2 flex flex-wrap gap-2">
98
+ <Badge variant="outline" className="text-xs">
99
+ ↑ {branch.divergence.ahead} ahead
100
+ </Badge>
101
+ <Badge variant="outline" className="text-xs">
102
+ ↓ {branch.divergence.behind} behind
103
+ </Badge>
104
+ <Badge
105
+ variant={branch.divergence.upToDate ? "success" : "warning"}
106
+ className="text-xs"
107
+ >
108
+ {branch.divergence.upToDate ? "Up to date" : "Needs sync"}
109
+ </Badge>
110
+ </div>
111
+ </div>
112
+ )}
113
+
114
+ {/* Worktree パス */}
115
+ {hasWorktree && (
116
+ <div>
117
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
118
+ Worktree Path
119
+ </p>
120
+ <p className="mt-1 break-all font-mono text-xs text-muted-foreground">
121
+ {branch.worktreePath}
122
+ </p>
123
+ </div>
124
+ )}
125
+
126
+ {/* マージステータス */}
127
+ {branch.mergeStatus && (
128
+ <div>
129
+ <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
130
+ Merge Status
131
+ </p>
132
+ <Badge
133
+ variant={
134
+ branch.mergeStatus === "merged" ? "success" : "outline"
135
+ }
136
+ className="mt-1"
137
+ >
138
+ {branch.mergeStatus}
139
+ </Badge>
140
+ </div>
141
+ )}
142
+ </CardContent>
143
+
144
+ <CardFooter className="flex flex-col gap-2">
145
+ <Button asChild className="w-full">
146
+ <Link to={`/${encodeURIComponent(branch.name)}`}>View Details</Link>
147
+ </Button>
148
+ </CardFooter>
149
+ </Card>
150
+ </div>
151
+ );
152
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * ブランチノードコンポーネント
3
+ *
4
+ * ソーマ(神経細胞体)をイメージした生物発光ノード
5
+ * 多層グロー効果と有機的なアニメーションで生命感を表現
6
+ */
7
+
8
+ import React, { memo, useMemo } from "react";
9
+ import { Handle, Position, type NodeProps } from "@xyflow/react";
10
+ import { cn } from "@/lib/utils";
11
+ import { getNodeColor, getNodeSize, type GraphNode } from "./graphUtils";
12
+
13
+ type BranchNodeProps = NodeProps<GraphNode>;
14
+
15
+ export const BranchNode = memo(function BranchNode({
16
+ data,
17
+ selected,
18
+ }: BranchNodeProps) {
19
+ const { branch } = data;
20
+
21
+ if (!branch) return null;
22
+
23
+ const size = getNodeSize(branch);
24
+ const _baseColor = getNodeColor(branch);
25
+ const hasWorktree = Boolean(branch.worktreePath);
26
+ const isMerged = branch.mergeStatus === "merged";
27
+
28
+ // ブランチ名を短縮
29
+ const displayName = useMemo(() => {
30
+ if (branch.name.length > 18) {
31
+ return `${branch.name.slice(0, 15)}...`;
32
+ }
33
+ return branch.name;
34
+ }, [branch.name]);
35
+
36
+ // ノードカラーをHSL値として抽出(動的スタイル用)
37
+ const colorStyle = useMemo(() => {
38
+ if (hasWorktree) {
39
+ return {
40
+ primary: "hsl(160 90% 45%)",
41
+ glow: "hsl(160 90% 45% / 0.4)",
42
+ inner: "hsl(160 90% 50% / 0.2)",
43
+ };
44
+ }
45
+ if (branch.type === "local") {
46
+ return {
47
+ primary: "hsl(280 85% 65%)",
48
+ glow: "hsl(280 85% 65% / 0.4)",
49
+ inner: "hsl(280 85% 70% / 0.2)",
50
+ };
51
+ }
52
+ return {
53
+ primary: "hsl(200 90% 55%)",
54
+ glow: "hsl(200 90% 55% / 0.4)",
55
+ inner: "hsl(200 90% 60% / 0.2)",
56
+ };
57
+ }, [hasWorktree, branch.type]);
58
+
59
+ return (
60
+ <>
61
+ {/* 入力ハンドル - デンドライト接続点 */}
62
+ <Handle
63
+ type="target"
64
+ position={Position.Top}
65
+ className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
66
+ style={{
67
+ borderColor: colorStyle.primary,
68
+ boxShadow: `0 0 8px ${colorStyle.glow}`,
69
+ }}
70
+ />
71
+
72
+ {/* ソーマ本体 */}
73
+ <div
74
+ className={cn(
75
+ "group relative flex cursor-pointer items-center justify-center rounded-full",
76
+ "transition-all duration-500 ease-out",
77
+ hasWorktree && "animate-active-synapse",
78
+ !hasWorktree && "animate-soma-pulse",
79
+ selected && "ring-2 ring-offset-4 ring-offset-background",
80
+ isMerged && "opacity-50",
81
+ )}
82
+ style={{
83
+ width: size,
84
+ height: size,
85
+ background: `radial-gradient(circle at 30% 30%, ${colorStyle.inner}, transparent 60%)`,
86
+ border: `2px solid ${colorStyle.primary}`,
87
+ boxShadow: selected
88
+ ? `0 0 30px ${colorStyle.glow}, inset 0 0 20px ${colorStyle.inner}, 0 0 0 4px ${colorStyle.primary}`
89
+ : undefined,
90
+ }}
91
+ >
92
+ {/* 外殻グロー */}
93
+ <div
94
+ className="absolute -inset-2 rounded-full opacity-30 blur-md"
95
+ style={{ backgroundColor: colorStyle.primary }}
96
+ />
97
+
98
+ {/* 核膜 - 内側のリング */}
99
+ <div
100
+ className="absolute inset-2 rounded-full border opacity-40"
101
+ style={{ borderColor: colorStyle.primary }}
102
+ />
103
+
104
+ {/* 核 - 中心部 */}
105
+ <div
106
+ className="relative z-10 flex items-center justify-center rounded-full shadow-lg"
107
+ style={{
108
+ width: size * 0.45,
109
+ height: size * 0.45,
110
+ background: `linear-gradient(135deg, ${colorStyle.primary}, ${colorStyle.inner})`,
111
+ boxShadow: `0 0 15px ${colorStyle.glow}`,
112
+ }}
113
+ >
114
+ {/* アイコン/タイプ表示 */}
115
+ <span className="text-xs font-bold text-background">
116
+ {branch.type === "local" ? "L" : "R"}
117
+ </span>
118
+ </div>
119
+
120
+ {/* 細胞小器官 - 装飾ドット */}
121
+ {!isMerged && (
122
+ <>
123
+ <div
124
+ className="absolute h-1.5 w-1.5 rounded-full animate-vesicle-release"
125
+ style={{
126
+ backgroundColor: colorStyle.primary,
127
+ top: "20%",
128
+ right: "25%",
129
+ animationDelay: "0s",
130
+ }}
131
+ />
132
+ <div
133
+ className="absolute h-1 w-1 rounded-full animate-vesicle-release"
134
+ style={{
135
+ backgroundColor: colorStyle.primary,
136
+ bottom: "25%",
137
+ left: "20%",
138
+ animationDelay: "0.7s",
139
+ }}
140
+ />
141
+ </>
142
+ )}
143
+
144
+ {/* Worktree活性インジケータ */}
145
+ {hasWorktree && (
146
+ <div
147
+ className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full animate-synapse-spark"
148
+ style={{
149
+ backgroundColor: "hsl(160 90% 45%)",
150
+ boxShadow: "0 0 10px hsl(160 90% 45% / 0.8)",
151
+ }}
152
+ >
153
+ <span className="text-[8px] font-bold text-background">W</span>
154
+ </div>
155
+ )}
156
+
157
+ {/* マージ済みオーバーレイ */}
158
+ {isMerged && (
159
+ <div className="absolute inset-0 flex items-center justify-center rounded-full bg-background/60 backdrop-blur-sm">
160
+ <span className="text-[10px] font-medium text-muted-foreground">
161
+ merged
162
+ </span>
163
+ </div>
164
+ )}
165
+
166
+ {/* ホバーツールチップ */}
167
+ <div
168
+ className={cn(
169
+ "absolute -bottom-12 left-1/2 z-50 -translate-x-1/2",
170
+ "rounded-lg border border-border/50 bg-card/95 px-3 py-1.5 backdrop-blur-md",
171
+ "opacity-0 shadow-xl transition-all duration-300",
172
+ "group-hover:opacity-100 group-hover:-translate-y-1",
173
+ )}
174
+ style={{
175
+ boxShadow: `0 4px 20px hsl(220 30% 0% / 0.5), 0 0 20px ${colorStyle.glow}`,
176
+ }}
177
+ >
178
+ <p className="whitespace-nowrap text-xs font-medium text-foreground">
179
+ {displayName}
180
+ </p>
181
+ <p className="text-[10px] text-muted-foreground">
182
+ {branch.type === "local" ? "Local" : "Remote"}
183
+ {hasWorktree && " • Active"}
184
+ </p>
185
+ </div>
186
+ </div>
187
+
188
+ {/* 出力ハンドル - アクソン接続点 */}
189
+ <Handle
190
+ type="source"
191
+ position={Position.Bottom}
192
+ className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
193
+ style={{
194
+ borderColor: colorStyle.primary,
195
+ boxShadow: `0 0 8px ${colorStyle.glow}`,
196
+ }}
197
+ />
198
+ </>
199
+ );
200
+ });
@@ -0,0 +1,211 @@
1
+ /**
2
+ * クラスタノードコンポーネント
3
+ *
4
+ * ニューロン群(神経細胞集合体)をイメージした有機的ノード
5
+ * 変形する膜と軌道を描く核で複数ブランチのグループを表現
6
+ */
7
+
8
+ import React, { memo, useMemo } from "react";
9
+ import { Handle, Position, type NodeProps } from "@xyflow/react";
10
+ import { cn } from "@/lib/utils";
11
+ import type { GraphNode } from "./graphUtils";
12
+
13
+ type ClusterNodeProps = NodeProps<GraphNode>;
14
+
15
+ export const ClusterNode = memo(function ClusterNode({
16
+ data,
17
+ selected,
18
+ }: ClusterNodeProps) {
19
+ const { clusterSize = 0, expanded } = data;
20
+
21
+ // クラスタサイズに応じてノードサイズを調整
22
+ const baseSize = 70;
23
+ const size = Math.min(baseSize + clusterSize * 5, 120);
24
+
25
+ // 内部の核の数(最大8個)
26
+ const nucleiCount = Math.min(clusterSize, 8);
27
+
28
+ // 核の配置角度を計算
29
+ const nuclei = useMemo(() => {
30
+ return Array.from({ length: nucleiCount }).map((_, i) => {
31
+ const angle = (360 / nucleiCount) * i;
32
+ const delay = i * 0.5;
33
+ const orbitRadius = size * 0.28;
34
+ return { angle, delay, orbitRadius };
35
+ });
36
+ }, [nucleiCount, size]);
37
+
38
+ return (
39
+ <>
40
+ {/* 入力ハンドル */}
41
+ <Handle
42
+ type="target"
43
+ position={Position.Top}
44
+ className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
45
+ style={{
46
+ borderColor: "hsl(185 100% 65%)",
47
+ boxShadow: "0 0 8px hsl(185 100% 65% / 0.4)",
48
+ }}
49
+ />
50
+
51
+ {/* クラスタ本体 */}
52
+ <div
53
+ className={cn(
54
+ "group relative flex cursor-pointer items-center justify-center",
55
+ "transition-all duration-500 ease-out",
56
+ "animate-membrane-morph",
57
+ selected &&
58
+ "ring-2 ring-primary ring-offset-4 ring-offset-background",
59
+ )}
60
+ style={{
61
+ width: size,
62
+ height: size,
63
+ background: expanded
64
+ ? "radial-gradient(circle at 40% 40%, hsl(185 100% 65% / 0.15), hsl(310 85% 60% / 0.08) 50%, transparent 70%)"
65
+ : "radial-gradient(circle at 40% 40%, hsl(220 30% 15% / 0.8), hsl(220 25% 10% / 0.6) 60%, transparent 80%)",
66
+ border: expanded
67
+ ? "2px solid hsl(185 100% 65% / 0.6)"
68
+ : "2px dashed hsl(200 30% 30%)",
69
+ boxShadow: expanded
70
+ ? "0 0 30px hsl(185 100% 65% / 0.3), inset 0 0 30px hsl(185 100% 65% / 0.1)"
71
+ : "0 0 20px hsl(220 30% 0% / 0.5), inset 0 0 20px hsl(220 30% 5% / 0.5)",
72
+ }}
73
+ >
74
+ {/* 外殻グロー */}
75
+ <div
76
+ className={cn(
77
+ "absolute -inset-3 rounded-full blur-lg transition-opacity duration-500",
78
+ expanded ? "opacity-40" : "opacity-20",
79
+ )}
80
+ style={{
81
+ backgroundColor: expanded
82
+ ? "hsl(185 100% 65%)"
83
+ : "hsl(200 30% 40%)",
84
+ borderRadius: "inherit",
85
+ }}
86
+ />
87
+
88
+ {/* 内部膜 */}
89
+ <div
90
+ className="absolute inset-3 rounded-full border border-dashed opacity-30"
91
+ style={{
92
+ borderColor: expanded ? "hsl(185 100% 65%)" : "hsl(200 30% 40%)",
93
+ borderRadius: "inherit",
94
+ }}
95
+ />
96
+
97
+ {/* 軌道を描く核 */}
98
+ <div className="absolute inset-0 flex items-center justify-center">
99
+ {nuclei.map((nucleus, i) => (
100
+ <div
101
+ key={i}
102
+ className="absolute animate-nucleus-orbit"
103
+ style={{
104
+ animationDelay: `${-nucleus.delay}s`,
105
+ animationDuration: `${8 + i * 0.5}s`,
106
+ }}
107
+ >
108
+ <div
109
+ className="h-2.5 w-2.5 rounded-full animate-vesicle-release"
110
+ style={{
111
+ backgroundColor: expanded
112
+ ? `hsl(${185 + i * 15} 80% 60%)`
113
+ : `hsl(${200 + i * 10} 50% 50%)`,
114
+ boxShadow: expanded
115
+ ? `0 0 8px hsl(${185 + i * 15} 80% 60% / 0.8)`
116
+ : `0 0 4px hsl(${200 + i * 10} 50% 50% / 0.5)`,
117
+ animationDelay: `${nucleus.delay * 0.3}s`,
118
+ transform: `translateX(${nucleus.orbitRadius}px)`,
119
+ }}
120
+ />
121
+ </div>
122
+ ))}
123
+ </div>
124
+
125
+ {/* 中心核 - クラスタサイズ表示 */}
126
+ <div
127
+ className={cn(
128
+ "relative z-10 flex items-center justify-center rounded-full",
129
+ "border-2 backdrop-blur-sm transition-all duration-300",
130
+ )}
131
+ style={{
132
+ width: size * 0.4,
133
+ height: size * 0.4,
134
+ backgroundColor: expanded
135
+ ? "hsl(220 30% 8% / 0.9)"
136
+ : "hsl(220 30% 10% / 0.95)",
137
+ borderColor: expanded
138
+ ? "hsl(185 100% 65% / 0.8)"
139
+ : "hsl(200 30% 35%)",
140
+ boxShadow: expanded
141
+ ? "0 0 20px hsl(185 100% 65% / 0.4), inset 0 0 15px hsl(185 100% 65% / 0.1)"
142
+ : "0 0 10px hsl(220 30% 0% / 0.5)",
143
+ }}
144
+ >
145
+ <span
146
+ className={cn(
147
+ "text-sm font-bold transition-colors duration-300",
148
+ expanded ? "text-primary" : "text-muted-foreground",
149
+ )}
150
+ >
151
+ {clusterSize}
152
+ </span>
153
+ </div>
154
+
155
+ {/* 展開/折りたたみインジケータ */}
156
+ <div
157
+ className={cn(
158
+ "absolute -right-2 -top-2 flex h-6 w-6 items-center justify-center rounded-full",
159
+ "border-2 text-xs font-bold transition-all duration-300",
160
+ )}
161
+ style={{
162
+ backgroundColor: expanded
163
+ ? "hsl(185 100% 65%)"
164
+ : "hsl(220 30% 12%)",
165
+ borderColor: expanded ? "hsl(185 100% 70%)" : "hsl(200 30% 25%)",
166
+ color: expanded ? "hsl(220 30% 5%)" : "hsl(185 100% 65%)",
167
+ boxShadow: expanded
168
+ ? "0 0 15px hsl(185 100% 65% / 0.6)"
169
+ : "0 0 8px hsl(220 30% 0% / 0.5)",
170
+ }}
171
+ >
172
+ {expanded ? "−" : "+"}
173
+ </div>
174
+
175
+ {/* ホバーツールチップ */}
176
+ <div
177
+ className={cn(
178
+ "absolute -bottom-14 left-1/2 z-50 -translate-x-1/2",
179
+ "rounded-lg border border-border/50 bg-card/95 px-3 py-2 backdrop-blur-md",
180
+ "opacity-0 shadow-xl transition-all duration-300",
181
+ "group-hover:opacity-100 group-hover:-translate-y-1",
182
+ )}
183
+ style={{
184
+ boxShadow:
185
+ "0 4px 20px hsl(220 30% 0% / 0.5), 0 0 20px hsl(185 100% 65% / 0.2)",
186
+ }}
187
+ >
188
+ <p className="whitespace-nowrap text-xs font-medium text-foreground">
189
+ {clusterSize} branches
190
+ </p>
191
+ <p className="text-[10px] text-muted-foreground">
192
+ Click to {expanded ? "collapse" : "expand"}
193
+ </p>
194
+ </div>
195
+ </div>
196
+
197
+ {/* 出力ハンドル */}
198
+ <Handle
199
+ type="source"
200
+ position={Position.Bottom}
201
+ className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
202
+ style={{
203
+ borderColor: expanded ? "hsl(185 100% 65%)" : "hsl(200 30% 40%)",
204
+ boxShadow: expanded
205
+ ? "0 0 8px hsl(185 100% 65% / 0.4)"
206
+ : "0 0 6px hsl(200 30% 40% / 0.3)",
207
+ }}
208
+ />
209
+ </>
210
+ );
211
+ });