@mazees/mark 5.0.0

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 (227) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +209 -0
  3. package/bin/mark.js +27 -0
  4. package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
  5. package/out/renderer/assets/index-G2fADWO-.js +4768 -0
  6. package/out/renderer/assets/index-f3mVse3c.css +1 -0
  7. package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
  8. package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
  9. package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
  10. package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
  11. package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
  12. package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
  13. package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
  14. package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
  15. package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
  16. package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  17. package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  18. package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  19. package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  20. package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  21. package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  22. package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  23. package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  24. package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
  25. package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
  26. package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
  27. package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
  28. package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
  29. package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
  30. package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
  31. package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
  32. package/out/renderer/favicon.ico +0 -0
  33. package/out/renderer/icon-256.png +0 -0
  34. package/out/renderer/icon.svg +1 -0
  35. package/out/renderer/index.html +25 -0
  36. package/out/renderer/manifest.json +25 -0
  37. package/out/renderer/robot-simulator.html +1640 -0
  38. package/package.json +106 -0
  39. package/src/cli/index.js +332 -0
  40. package/src/cli/monitor.js +413 -0
  41. package/src/cli/theme.js +347 -0
  42. package/src/launcher/MarkTray.cs +227 -0
  43. package/src/main/browser-agent.js +1132 -0
  44. package/src/main/git-service.js +90 -0
  45. package/src/main/google/google-calendar.js +69 -0
  46. package/src/main/google/google-drive.js +263 -0
  47. package/src/main/google/google-gmail.js +131 -0
  48. package/src/main/google/google-service.js +241 -0
  49. package/src/main/node-tools.js +23 -0
  50. package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
  51. package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
  52. package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
  53. package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
  54. package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
  55. package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
  56. package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
  57. package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
  58. package/src/main/plugins/plugin-loader.js +306 -0
  59. package/src/main/skills/skill-manager.js +265 -0
  60. package/src/main/syntax-validator.js +455 -0
  61. package/src/main/task-daemon.js +117 -0
  62. package/src/main/telegram/telegram-service.js +835 -0
  63. package/src/main/tools/browser-tools.js +516 -0
  64. package/src/main/tools/file-tools.js +1126 -0
  65. package/src/main/tools/git-tools.js +79 -0
  66. package/src/main/tools/google-tools.js +566 -0
  67. package/src/main/tools/index.js +36 -0
  68. package/src/main/tools/media-tools.js +42 -0
  69. package/src/main/tools/system-tools.js +295 -0
  70. package/src/main/tools/task-tools.js +73 -0
  71. package/src/main/tools/telegram-tools.js +101 -0
  72. package/src/renderer/index.html +24 -0
  73. package/src/renderer/public/favicon.ico +0 -0
  74. package/src/renderer/public/icon-256.png +0 -0
  75. package/src/renderer/public/icon.svg +1 -0
  76. package/src/renderer/public/manifest.json +25 -0
  77. package/src/renderer/public/robot-simulator.html +1640 -0
  78. package/src/renderer/src/App.jsx +304 -0
  79. package/src/renderer/src/api/ai/awareness.js +294 -0
  80. package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
  81. package/src/renderer/src/api/ai/contextCompactor.js +129 -0
  82. package/src/renderer/src/api/ai/contextManager.js +1130 -0
  83. package/src/renderer/src/api/ai/core.js +167 -0
  84. package/src/renderer/src/api/ai/index.js +4 -0
  85. package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
  86. package/src/renderer/src/api/ai/persona.js +115 -0
  87. package/src/renderer/src/api/ai/planning.js +317 -0
  88. package/src/renderer/src/api/ai/relationship.js +177 -0
  89. package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
  90. package/src/renderer/src/api/ai/tools.js +167 -0
  91. package/src/renderer/src/api/ai/utils.js +359 -0
  92. package/src/renderer/src/api/db.js +1013 -0
  93. package/src/renderer/src/api/oramaStore.js +592 -0
  94. package/src/renderer/src/api/ragPipeline.js +87 -0
  95. package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
  96. package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
  97. package/src/renderer/src/api/subagent/subagentStore.js +173 -0
  98. package/src/renderer/src/api/taskExecutor.js +18 -0
  99. package/src/renderer/src/api/taskStore.js +315 -0
  100. package/src/renderer/src/api/tools/core-tools.js +707 -0
  101. package/src/renderer/src/api/tools/index.js +151 -0
  102. package/src/renderer/src/api/turnPairMigrator.js +200 -0
  103. package/src/renderer/src/api/vectorMemory.js +136 -0
  104. package/src/renderer/src/api/wakeWord.js +101 -0
  105. package/src/renderer/src/api/web-bridge.js +885 -0
  106. package/src/renderer/src/api/webSpeech.js +178 -0
  107. package/src/renderer/src/api/workspaceRag.js +76 -0
  108. package/src/renderer/src/assets/electron.svg +10 -0
  109. package/src/renderer/src/assets/icon.svg +1 -0
  110. package/src/renderer/src/assets/main.css +589 -0
  111. package/src/renderer/src/assets/music-cover.png +0 -0
  112. package/src/renderer/src/assets/wavy-lines.svg +25 -0
  113. package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
  114. package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
  115. package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
  116. package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
  117. package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
  118. package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
  119. package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
  120. package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
  121. package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
  122. package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
  123. package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
  124. package/src/renderer/src/components/Chat/index.js +11 -0
  125. package/src/renderer/src/components/ChatList.jsx +434 -0
  126. package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
  127. package/src/renderer/src/components/Versions.jsx +15 -0
  128. package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
  129. package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
  130. package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
  131. package/src/renderer/src/components/config/CameraSection.jsx +100 -0
  132. package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
  133. package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
  134. package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
  135. package/src/renderer/src/components/config/index.js +5 -0
  136. package/src/renderer/src/components/core/Avatar.jsx +2197 -0
  137. package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
  138. package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
  139. package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
  140. package/src/renderer/src/components/core/HoloCard.jsx +64 -0
  141. package/src/renderer/src/components/core/InputBar.jsx +1346 -0
  142. package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
  143. package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
  144. package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
  145. package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
  146. package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
  147. package/src/renderer/src/components/core/native-skills.js +43 -0
  148. package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
  149. package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
  150. package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
  151. package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
  152. package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
  153. package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
  154. package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
  155. package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
  156. package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
  157. package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
  158. package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
  159. package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
  160. package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
  161. package/src/renderer/src/contexts/ChatContext.jsx +61 -0
  162. package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
  163. package/src/renderer/src/data/faqData.js +202 -0
  164. package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
  165. package/src/renderer/src/hooks/agent/index.js +4 -0
  166. package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
  167. package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
  168. package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
  169. package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
  170. package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
  171. package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
  172. package/src/renderer/src/hooks/useAwareness.js +289 -0
  173. package/src/renderer/src/hooks/useChatArchiver.js +52 -0
  174. package/src/renderer/src/hooks/useConfirm.jsx +68 -0
  175. package/src/renderer/src/hooks/useMarkAgent.js +454 -0
  176. package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
  177. package/src/renderer/src/hooks/useVAD.js +511 -0
  178. package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
  179. package/src/renderer/src/main.jsx +12 -0
  180. package/src/renderer/src/pages/ChatStudio.jsx +922 -0
  181. package/src/renderer/src/pages/Configuration.jsx +360 -0
  182. package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
  183. package/src/renderer/src/pages/Guidebook.jsx +917 -0
  184. package/src/renderer/src/pages/Knowledge.jsx +241 -0
  185. package/src/renderer/src/pages/MarkHome.jsx +491 -0
  186. package/src/renderer/src/pages/NeuralCore.jsx +786 -0
  187. package/src/renderer/src/pages/Plugins.jsx +630 -0
  188. package/src/renderer/src/pages/SkillEditor.jsx +380 -0
  189. package/src/renderer/src/pages/Skills.jsx +219 -0
  190. package/src/renderer/src/pages/Subagents.jsx +342 -0
  191. package/src/renderer/src/pages/TelegramBot.jsx +343 -0
  192. package/src/renderer/src/utils/chatContentParser.js +203 -0
  193. package/src/renderer/src/utils/toolClusters.js +201 -0
  194. package/src/server/bin/sha3_wasm_bg.wasm +0 -0
  195. package/src/server/config-manager.js +57 -0
  196. package/src/server/index.js +195 -0
  197. package/src/server/launcher.js +87 -0
  198. package/src/server/memory/db-store.js +758 -0
  199. package/src/server/memory/orama-store.js +177 -0
  200. package/src/server/memory/vector-engine.js +109 -0
  201. package/src/server/routes/ai.routes.js +280 -0
  202. package/src/server/routes/awareness.routes.js +62 -0
  203. package/src/server/routes/chat.routes.js +61 -0
  204. package/src/server/routes/config.routes.js +41 -0
  205. package/src/server/routes/index.js +35 -0
  206. package/src/server/routes/integrations.routes.js +116 -0
  207. package/src/server/routes/memory.routes.js +256 -0
  208. package/src/server/routes/plugins.routes.js +72 -0
  209. package/src/server/routes/skills.routes.js +139 -0
  210. package/src/server/routes/subagents.routes.js +70 -0
  211. package/src/server/routes/tasks.routes.js +175 -0
  212. package/src/server/routes/workspace.routes.js +127 -0
  213. package/src/server/services/ai/ai-utils.js +161 -0
  214. package/src/server/services/ai/openai-provider.js +757 -0
  215. package/src/server/services/ai/web-provider.js +729 -0
  216. package/src/server/services/ai-bridge.js +46 -0
  217. package/src/server/services/deepseek-web.js +956 -0
  218. package/src/server/services/gemini-web.js +170 -0
  219. package/src/server/services/notification-service.js +91 -0
  220. package/src/server/services/updater.js +125 -0
  221. package/src/server/tools/awareness-tracker.js +292 -0
  222. package/src/server/tools/group-tools.js +1164 -0
  223. package/src/server/tools/media-tools.js +244 -0
  224. package/src/server/tools/pc-agent.js +327 -0
  225. package/src/server/tools/screen-service.js +51 -0
  226. package/src/server/ws-hub.js +168 -0
  227. package/src/shared/parsers/mark-tag-parser.js +275 -0
@@ -0,0 +1,1640 @@
1
+ <!doctype html>
2
+ <html lang="id" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>MARK 2.5D Cyber-Droid Simulator - Lifelike Living Companion</title>
7
+ <script src="https://www.gstatic.com/antigravity/web/dev/tailwindcss.min.js"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Poppins:wght@400;500;600;700&display=swap"
12
+ rel="stylesheet"
13
+ />
14
+ <style>
15
+ :root {
16
+ --color-primary: #1fb854;
17
+ --color-neutral: #19362d;
18
+ --color-base-100: #0f1715;
19
+ --color-base-200: #13221b;
20
+ --color-base-300: #19362d;
21
+ --color-base-content: #cac9c9;
22
+ --font-sans: 'Poppins', system-ui, -apple-system, sans-serif;
23
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
24
+ --current-accent: #1fb854;
25
+ }
26
+
27
+ body {
28
+ font-family: var(--font-sans);
29
+ background-color: var(--color-base-100);
30
+ color: var(--color-base-content);
31
+ }
32
+
33
+ .font-mono {
34
+ font-family: var(--font-mono);
35
+ }
36
+
37
+ @keyframes scanline {
38
+ 0% {
39
+ transform: translateY(-100%);
40
+ }
41
+ 100% {
42
+ transform: translateY(160%);
43
+ }
44
+ }
45
+ @keyframes glitchShake {
46
+ 0% {
47
+ transform: translate(0, 0);
48
+ }
49
+ 25% {
50
+ transform: translate(-3px, 2px);
51
+ }
52
+ 50% {
53
+ transform: translate(3px, -2px);
54
+ }
55
+ 75% {
56
+ transform: translate(-2px, -1px);
57
+ }
58
+ 100% {
59
+ transform: translate(0, 0);
60
+ }
61
+ }
62
+ @keyframes forestPulse {
63
+ 0%,
64
+ 100% {
65
+ box-shadow:
66
+ 0 0 25px rgba(31, 184, 84, 0.15),
67
+ 0 0 50px rgba(31, 184, 84, 0.05);
68
+ }
69
+ 50% {
70
+ box-shadow:
71
+ 0 0 45px rgba(31, 184, 84, 0.3),
72
+ 0 0 90px rgba(31, 184, 84, 0.12);
73
+ }
74
+ }
75
+ @keyframes coreHeartbeat {
76
+ 0%,
77
+ 100% {
78
+ transform: scale(1);
79
+ opacity: 0.9;
80
+ }
81
+ 15% {
82
+ transform: scale(1.2);
83
+ opacity: 1;
84
+ }
85
+ 30% {
86
+ transform: scale(1.05);
87
+ opacity: 0.92;
88
+ }
89
+ 45% {
90
+ transform: scale(1.14);
91
+ opacity: 0.98;
92
+ }
93
+ 60% {
94
+ transform: scale(1);
95
+ opacity: 0.9;
96
+ }
97
+ }
98
+
99
+ .preserve-3d {
100
+ transform-style: preserve-3d;
101
+ }
102
+ .forest-glow {
103
+ animation: forestPulse 3.5s ease-in-out infinite;
104
+ }
105
+ .core-beat {
106
+ animation: coreHeartbeat 2.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
107
+ }
108
+ .animate-glitch {
109
+ animation: glitchShake 0.16s linear infinite;
110
+ }
111
+
112
+ /* Smooth Squash & Stretch Transitions for Eyes */
113
+ .eye-mesh {
114
+ transition: transform 120ms cubic-bezier(0.2, 0.8, 0.2, 1);
115
+ transform-origin: center center;
116
+ }
117
+
118
+ input[type='range'] {
119
+ accent-color: var(--current-accent, #1fb854);
120
+ }
121
+ </style>
122
+ </head>
123
+ <body class="min-h-screen flex flex-col select-none overflow-x-hidden bg-[#0f1715]">
124
+ <!-- Header Bar matching MARK Top Navigation -->
125
+ <header
126
+ class="w-full border-b border-[#19362d] bg-[#0f1715]/90 backdrop-blur-xl px-6 py-3.5 flex items-center justify-between z-30"
127
+ >
128
+ <div class="flex items-center gap-3">
129
+ <div
130
+ class="w-2.5 h-2.5 rounded-full bg-[#1fb854] shadow-[0_0_12px_#1fb854] animate-pulse"
131
+ ></div>
132
+ <div>
133
+ <h1
134
+ class="text-xs font-mono font-bold tracking-wider uppercase text-white flex items-center gap-2"
135
+ >
136
+ MARK 2.5D DROID
137
+ <span
138
+ class="text-[9px] px-2 py-0.5 rounded bg-[#19362d] text-[#1fb854] border border-[#1fb854]/30 font-mono font-semibold"
139
+ >LIFELIKE COMPANION</span
140
+ >
141
+ </h1>
142
+ <p class="text-[10px] text-[#cac9c9]/60 font-mono">
143
+ Elastic Curved Eyes &bull; Autonomous Quirks &bull; Clean Head Shell
144
+ </p>
145
+ </div>
146
+ </div>
147
+ <div class="flex items-center gap-3 text-xs font-mono">
148
+ <div
149
+ class="flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-[#13221b] border border-[#19362d]"
150
+ >
151
+ <span class="text-[#cac9c9]/50">FPS:</span>
152
+ <span id="fpsCounter" class="text-[#1fb854] font-bold">60</span>
153
+ </div>
154
+ <div
155
+ class="flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-[#13221b] border border-[#19362d]"
156
+ >
157
+ <span class="text-[#cac9c9]/50">AKSI:</span>
158
+ <span id="quirkIndicator" class="text-[#1fb854] font-bold uppercase">BERNAPAS</span>
159
+ </div>
160
+ </div>
161
+ </header>
162
+
163
+ <!-- Main Workspace -->
164
+ <main
165
+ class="flex-1 flex flex-col lg:flex-row items-stretch justify-between p-4 lg:p-6 gap-6 relative max-w-7xl mx-auto w-full"
166
+ >
167
+ <!-- LEFT: Character Stage -->
168
+ <section
169
+ class="flex-1 min-h-[500px] lg:min-h-[620px] bg-[#0a120e] border border-[#19362d] rounded-2xl relative flex flex-col items-center justify-center overflow-hidden backdrop-blur-xl shadow-2xl group forest-glow"
170
+ id="stageContainer"
171
+ >
172
+ <!-- Background Subtle Radar Rings -->
173
+ <div
174
+ class="absolute inset-0 pointer-events-none opacity-20 bg-[radial-gradient(#1fb854_1px,transparent_1px)] [background-size:24px_24px]"
175
+ ></div>
176
+ <div
177
+ class="absolute w-[500px] h-[500px] rounded-full border border-[#1fb854]/10 pointer-events-none"
178
+ ></div>
179
+ <div
180
+ class="absolute w-[360px] h-[360px] rounded-full border border-dashed border-[#1fb854]/15 pointer-events-none animate-[spin_100s_linear_infinite]"
181
+ ></div>
182
+
183
+ <!-- Stage Telemetry Label in Top Left -->
184
+ <div
185
+ class="absolute top-4 left-4 z-20 flex flex-col gap-1.5 font-mono text-[11px] pointer-events-none bg-[#0f1715]/85 border border-[#19362d] px-3 py-2.5 rounded-xl backdrop-blur-md shadow-lg"
186
+ >
187
+ <div class="flex items-center gap-2">
188
+ <span class="text-[#cac9c9]/50">STATUS:</span>
189
+ <span id="statusLabel" class="text-[#1fb854] font-bold uppercase tracking-wider"
190
+ >IDLE</span
191
+ >
192
+ </div>
193
+ <div class="flex items-center gap-2">
194
+ <span class="text-[#cac9c9]/50">MOOD:</span>
195
+ <span id="moodLabel" class="text-[#1fb854] font-bold uppercase tracking-wider"
196
+ >NEUTRAL</span
197
+ >
198
+ </div>
199
+ <div class="flex items-center gap-2">
200
+ <span class="text-[#cac9c9]/50">GESTUR:</span>
201
+ <span id="actionLabel" class="text-[#1fb854] font-mono text-[10px]">MENATAP DEPAN</span>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- Top Right Instruction -->
206
+ <div
207
+ class="absolute top-4 right-4 z-20 text-[10px] font-mono text-[#cac9c9]/50 pointer-events-none text-right bg-[#0f1715]/85 border border-[#19362d] px-3 py-2 rounded-xl backdrop-blur-md shadow-lg"
208
+ >
209
+ LIFELIKE AUTONOMOUS RIG<br />KLIK AVATAR UNTUK REAKSI POKE
210
+ </div>
211
+
212
+ <!-- 3D Perspective Viewport (Explicit 320px by 440px Stage) -->
213
+ <div
214
+ class="relative w-[320px] h-[440px] [perspective:1000px] z-10 select-none"
215
+ id="avatarInteractiveArea"
216
+ >
217
+ <!-- Robot Root Pivot (Handles organic breathing & floating) -->
218
+ <div id="robotRoot" class="relative w-full h-full preserve-3d cursor-pointer">
219
+ <!-- ── 1. UNIFIED 3D HEAD ASSEMBLY (CLEAN: NO MARK-OS TEXT) ── -->
220
+ <!-- Throat pivot joint (130px 165px) so the neck NEVER tears or detaches -->
221
+ <div
222
+ id="robotHead"
223
+ class="absolute left-[30px] top-[18px] w-[260px] h-[180px] preserve-3d will-change-transform"
224
+ style="transform-origin: 130px 165px"
225
+ >
226
+ <!-- 1a. Background Helmet Shell & Antennas (Single unified SVG with 3D gradients, clean forehead) -->
227
+ <svg
228
+ viewBox="0 0 260 180"
229
+ class="absolute inset-0 w-full h-full pointer-events-none drop-shadow-[0_16px_36px_rgba(0,0,0,0.9)]"
230
+ style="overflow: visible"
231
+ >
232
+ <defs>
233
+ <!-- 3D Volumetric Spherical Helmet Gradient -->
234
+ <radialGradient id="helmetGrad" cx="38%" cy="32%" r="68%">
235
+ <stop offset="0%" stop-color="#243f30" />
236
+ <stop offset="45%" stop-color="#14241c" />
237
+ <stop offset="100%" stop-color="#080e0a" />
238
+ </radialGradient>
239
+ <linearGradient id="helmetRim" x1="0" y1="0" x2="0" y2="1">
240
+ <stop offset="0%" stop-color="#2a4738" />
241
+ <stop offset="100%" stop-color="#14241c" />
242
+ </linearGradient>
243
+ <filter id="neon-glow" x="-20%" y="-20%" width="140%" height="140%">
244
+ <feGaussianBlur stdDeviation="3" result="blur" />
245
+ <feComposite in="SourceGraphic" in2="blur" operator="over" />
246
+ </filter>
247
+ </defs>
248
+
249
+ <!-- Left Wing Antenna -->
250
+ <g id="antennaLeft" class="origin-[55px_90px]">
251
+ <path
252
+ d="M 55 90 L 16 40 L 22 24 L 46 48 Z"
253
+ fill="#13221b"
254
+ stroke="#19362d"
255
+ stroke-width="2.5"
256
+ />
257
+ <circle
258
+ id="antBulbLeft"
259
+ cx="19"
260
+ cy="32"
261
+ r="7"
262
+ fill="#1fb854"
263
+ class="mood-fill"
264
+ filter="url(#neon-glow)"
265
+ />
266
+ <circle cx="19" cy="32" r="2.5" fill="#ffffff" opacity="0.95" />
267
+ </g>
268
+
269
+ <!-- Right Wing Antenna -->
270
+ <g id="antennaRight" class="origin-[205px_90px]">
271
+ <path
272
+ d="M 205 90 L 244 40 L 238 24 L 214 48 Z"
273
+ fill="#13221b"
274
+ stroke="#19362d"
275
+ stroke-width="2.5"
276
+ />
277
+ <circle
278
+ id="antBulbRight"
279
+ cx="241"
280
+ cy="32"
281
+ r="7"
282
+ fill="#1fb854"
283
+ class="mood-fill"
284
+ filter="url(#neon-glow)"
285
+ />
286
+ <circle cx="241" cy="32" r="2.5" fill="#ffffff" opacity="0.95" />
287
+ </g>
288
+
289
+ <!-- Main Helmet Curved 3D Head Shell (Sleek, Clean, No badges) -->
290
+ <ellipse
291
+ cx="130"
292
+ cy="95"
293
+ rx="78"
294
+ ry="70"
295
+ fill="url(#helmetGrad)"
296
+ stroke="url(#helmetRim)"
297
+ stroke-width="3"
298
+ />
299
+
300
+ <!-- Chin Bevel (Hugs neck seamlessly) -->
301
+ <path
302
+ d="M 104 156 Q 130 166 156 156 L 150 164 Q 130 170 110 164 Z"
303
+ fill="#080e0a"
304
+ />
305
+
306
+ <!-- Earphone Side Pods -->
307
+ <rect
308
+ x="47"
309
+ y="78"
310
+ width="10"
311
+ height="34"
312
+ rx="5"
313
+ fill="#19362d"
314
+ stroke="#254a3e"
315
+ stroke-width="2"
316
+ />
317
+ <rect
318
+ x="203"
319
+ y="78"
320
+ width="10"
321
+ height="34"
322
+ rx="5"
323
+ fill="#19362d"
324
+ stroke="#254a3e"
325
+ stroke-width="2"
326
+ />
327
+
328
+ <!-- Subtle Forehead Ambient Light Sensor Dot (Clean, Minimalist) -->
329
+ <circle cx="130" cy="36" r="3" fill="#1fb854" class="mood-fill" opacity="0.75" />
330
+ </svg>
331
+
332
+ <!-- 1b. Deep Curved 3D Glass Visor Screen -->
333
+ <div
334
+ id="visorScreen"
335
+ class="absolute left-[56px] top-[40px] w-[148px] h-[108px] rounded-[34px] bg-gradient-to-b from-[#0a140f] to-[#040806] border-2 border-[#19362d] overflow-hidden shadow-[inset_0_4px_22px_rgba(0,0,0,0.98)] preserve-3d flex items-center justify-center pointer-events-none"
336
+ style="transform: translateZ(16px)"
337
+ >
338
+ <!-- SUBTLE MONITOR DESKTOP WIREFRAME REFLECTION (Active during Awareness / Melihat Desktop) -->
339
+ <div
340
+ id="desktopScreenReflection"
341
+ class="absolute inset-0 pointer-events-none opacity-0 transition-opacity duration-500 flex flex-col p-3 z-0"
342
+ >
343
+ <div
344
+ class="w-full h-3 border-b border-[#1fb854]/30 flex items-center justify-between px-1 pb-1"
345
+ >
346
+ <div class="flex gap-1">
347
+ <div class="w-1.5 h-1.5 rounded-full bg-[#1fb854]/50"></div>
348
+ <div class="w-1.5 h-1.5 rounded-full bg-[#1fb854]/30"></div>
349
+ </div>
350
+ <div class="w-6 h-1 rounded-full bg-[#1fb854]/25"></div>
351
+ </div>
352
+ <div class="flex-1 flex flex-col gap-1 pt-1.5 px-0.5 opacity-40">
353
+ <div class="w-3/4 h-1 rounded bg-[#1fb854]/40"></div>
354
+ <div class="w-1/2 h-1 rounded bg-[#1fb854]/30"></div>
355
+ <div class="w-5/6 h-1 rounded bg-[#1fb854]/40"></div>
356
+ <div class="w-2/3 h-1 rounded bg-[#1fb854]/30"></div>
357
+ <div class="w-4/5 h-1 rounded bg-[#1fb854]/40"></div>
358
+ </div>
359
+ <div
360
+ class="absolute inset-x-0 top-0 h-4 bg-gradient-to-b from-[#1fb854]/15 to-transparent pointer-events-none animate-[scanline_2.5s_linear_infinite]"
361
+ ></div>
362
+ </div>
363
+
364
+ <!-- 3D Curved Glass Specular Highlight (Natural soft reflection) -->
365
+ <div
366
+ class="absolute -top-10 -left-10 w-56 h-30 bg-gradient-to-b from-white/12 via-white/3 to-transparent rotate-[-22deg] pointer-events-none rounded-full blur-[1px]"
367
+ ></div>
368
+
369
+ <!-- 1c. LED Cyber Face (Elastic Squash & Stretch Eyes + Animated Mouth) -->
370
+ <div
371
+ id="cyberFace"
372
+ class="relative w-full h-full flex flex-col items-center justify-center z-10 preserve-3d"
373
+ style="transform: translateZ(10px)"
374
+ >
375
+ <!-- Eyes Container -->
376
+ <div
377
+ id="eyesContainer"
378
+ class="flex items-center justify-center gap-7 will-change-transform"
379
+ >
380
+ <div
381
+ class="w-10 h-10 flex items-center justify-center eye-mesh"
382
+ id="leftEyeBox"
383
+ ></div>
384
+ <div
385
+ class="w-10 h-10 flex items-center justify-center eye-mesh"
386
+ id="rightEyeBox"
387
+ ></div>
388
+ </div>
389
+
390
+ <!-- Mouth / Audio Lip-Flap -->
391
+ <div
392
+ id="mouthContainer"
393
+ class="h-5 flex items-center justify-center mt-1 transition-all duration-75"
394
+ ></div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+
399
+ <!-- ── 2. BODY CHASSIS & INTEGRATED NECK COLLAR (Centered: X: 95px, Y: 172px) ── -->
400
+ <div
401
+ id="robotBody"
402
+ class="absolute left-[95px] top-[172px] w-[130px] h-[126px] preserve-3d will-change-transform"
403
+ style="transform-origin: 65px 12px; transform: translateZ(-5px)"
404
+ >
405
+ <svg
406
+ viewBox="0 0 130 126"
407
+ class="w-full h-full drop-shadow-[0_12px_30px_rgba(0,0,0,0.85)]"
408
+ >
409
+ <defs>
410
+ <radialGradient id="bodyGrad" cx="50%" cy="40%" r="65%">
411
+ <stop offset="0%" stop-color="#1a2e23" />
412
+ <stop offset="60%" stop-color="#13221b" />
413
+ <stop offset="100%" stop-color="#080e0a" />
414
+ </radialGradient>
415
+ </defs>
416
+
417
+ <!-- Integrated Cyber Neck Socket -->
418
+ <ellipse
419
+ cx="65"
420
+ cy="14"
421
+ rx="26"
422
+ ry="9"
423
+ fill="#0d1611"
424
+ stroke="#19362d"
425
+ stroke-width="2.5"
426
+ />
427
+ <ellipse cx="65" cy="14" rx="16" ry="5" fill="#080e0a" />
428
+
429
+ <!-- Body Outer Shell (Volumetric 3D gradient) -->
430
+ <path
431
+ d="M 32 30 L 98 30 C 114 30, 120 50, 114 90 C 108 113, 88 120, 65 120 C 42 120, 22 113, 16 90 C 10 50, 16 30, 32 30 Z"
432
+ fill="url(#bodyGrad)"
433
+ stroke="#19362d"
434
+ stroke-width="3"
435
+ />
436
+
437
+ <!-- Cyber Armor Plates -->
438
+ <path
439
+ d="M 30 45 L 56 45 L 52 80 L 28 75 Z"
440
+ fill="#19362d"
441
+ stroke="#254a3e"
442
+ stroke-width="1.5"
443
+ />
444
+ <path
445
+ d="M 100 45 L 74 45 L 78 80 L 102 75 Z"
446
+ fill="#19362d"
447
+ stroke="#254a3e"
448
+ stroke-width="1.5"
449
+ />
450
+
451
+ <!-- Central Arc Reactor with Heartbeat Pulse -->
452
+ <circle cx="65" cy="70" r="14" fill="#09120e" stroke="#19362d" stroke-width="2.5" />
453
+ <g class="core-beat origin-[65px_70px]">
454
+ <circle cx="65" cy="70" r="9" fill="#1fb854" class="mood-fill" />
455
+ <circle cx="65" cy="70" r="4.5" fill="#ffffff" opacity="0.95" />
456
+ </g>
457
+
458
+ <!-- Power Telemetry Bars in Forest Green -->
459
+ <rect
460
+ x="50"
461
+ y="95"
462
+ width="6"
463
+ height="3"
464
+ rx="1.5"
465
+ fill="#1fb854"
466
+ class="mood-fill"
467
+ />
468
+ <rect
469
+ x="59"
470
+ y="95"
471
+ width="6"
472
+ height="3"
473
+ rx="1.5"
474
+ fill="#1fb854"
475
+ class="mood-fill"
476
+ />
477
+ <rect
478
+ x="68"
479
+ y="95"
480
+ width="6"
481
+ height="3"
482
+ rx="1.5"
483
+ fill="#1fb854"
484
+ class="mood-fill"
485
+ />
486
+ <rect
487
+ x="77"
488
+ y="95"
489
+ width="6"
490
+ height="3"
491
+ rx="1.5"
492
+ fill="#1fb854"
493
+ class="mood-fill"
494
+ />
495
+
496
+ <!-- Bottom Thruster Vent Nozzle -->
497
+ <path d="M 50 117 L 80 117 L 74 125 L 56 125 Z" fill="#0d1712" />
498
+ </svg>
499
+ </div>
500
+
501
+ <!-- ── 3. FLOATING HANDS (Left: X: 36px, Right: X: 240px, Y: 215px) ── -->
502
+ <div
503
+ id="handLeft"
504
+ class="absolute left-[36px] top-[215px] w-[44px] h-[52px] preserve-3d will-change-transform"
505
+ style="transform: translateZ(18px)"
506
+ >
507
+ <svg
508
+ viewBox="0 0 50 60"
509
+ class="w-full h-full drop-shadow-[0_6px_14px_rgba(0,0,0,0.65)]"
510
+ >
511
+ <rect
512
+ x="10"
513
+ y="8"
514
+ width="30"
515
+ height="34"
516
+ rx="15"
517
+ fill="#13221b"
518
+ stroke="#19362d"
519
+ stroke-width="2.5"
520
+ />
521
+ <ellipse cx="25" cy="25" rx="8" ry="10" fill="#0c1611" />
522
+ <circle cx="20" cy="46" r="3.5" fill="#1fb854" class="mood-fill" />
523
+ <circle cx="30" cy="46" r="3.5" fill="#1fb854" class="mood-fill" />
524
+ <circle cx="8" cy="26" r="3" fill="#1fb854" class="mood-fill" />
525
+ </svg>
526
+ </div>
527
+
528
+ <div
529
+ id="handRight"
530
+ class="absolute left-[240px] top-[215px] w-[44px] h-[52px] preserve-3d will-change-transform"
531
+ style="transform: translateZ(18px)"
532
+ >
533
+ <svg
534
+ viewBox="0 0 50 60"
535
+ class="w-full h-full drop-shadow-[0_6px_14px_rgba(0,0,0,0.65)]"
536
+ >
537
+ <rect
538
+ x="10"
539
+ y="8"
540
+ width="30"
541
+ height="34"
542
+ rx="15"
543
+ fill="#13221b"
544
+ stroke="#19362d"
545
+ stroke-width="2.5"
546
+ />
547
+ <ellipse cx="25" cy="25" rx="8" ry="10" fill="#0c1611" />
548
+ <circle cx="20" cy="46" r="3.5" fill="#1fb854" class="mood-fill" />
549
+ <circle cx="30" cy="46" r="3.5" fill="#1fb854" class="mood-fill" />
550
+ <circle cx="42" cy="26" r="3" fill="#1fb854" class="mood-fill" />
551
+ </svg>
552
+ </div>
553
+
554
+ <!-- ── 4. THRUSTER PARTICLES & SHADOW AT BOTTOM ── -->
555
+ <div
556
+ id="thrusterGlow"
557
+ class="absolute left-[118px] top-[305px] w-[84px] h-[22px] rounded-full bg-[#1fb854]/40 blur-[10px] pointer-events-none"
558
+ style="transform: translateZ(-35px)"
559
+ ></div>
560
+ <div
561
+ id="groundShadow"
562
+ class="absolute left-[90px] top-[360px] w-[140px] h-[26px] rounded-full bg-black/75 blur-[8px] pointer-events-none transition-transform duration-75"
563
+ style="transform: translateZ(-60px) rotateX(75deg)"
564
+ ></div>
565
+ </div>
566
+ </div>
567
+
568
+ <!-- Bottom Hint -->
569
+ <div class="absolute bottom-3 text-center text-[#cac9c9]/60 text-xs font-mono">
570
+ Karakter Hidup Mandiri (Natural Micro-Gestures & Quirks)
571
+ </div>
572
+ </section>
573
+
574
+ <!-- RIGHT: Control Panel & Live Inspector -->
575
+ <aside class="w-full lg:w-96 flex flex-col gap-4 font-mono">
576
+ <!-- 1. Awareness Engine Control Center (Melihat Layar) -->
577
+ <div
578
+ class="bg-[#13221b]/95 border border-[#1fb854]/50 rounded-2xl p-4 backdrop-blur-xl shadow-[0_0_20px_rgba(31,184,84,0.15)]"
579
+ >
580
+ <div class="flex items-center justify-between pb-2 mb-3 border-b border-[#19362d]">
581
+ <h2
582
+ class="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-2"
583
+ >
584
+ <span
585
+ class="w-2.5 h-2.5 rounded-full bg-[#1fb854] shadow-[0_0_8px_#1fb854] animate-ping"
586
+ ></span>
587
+ Simulasi Melihat Desktop
588
+ </h2>
589
+ <span class="text-[10px] px-2 py-0.5 rounded bg-[#19362d] text-[#1fb854] font-bold"
590
+ >AWARENESS</span
591
+ >
592
+ </div>
593
+
594
+ <div class="flex flex-col gap-2.5 text-xs">
595
+ <p class="text-[11px] text-[#cac9c9]/70 leading-relaxed">
596
+ Mark menunduk memperhatikan desktop dengan pantulan layar halus di kaca visor dan mata
597
+ membaca baris kode/jendela yang sedang aktif.
598
+ </p>
599
+
600
+ <!-- Trigger Seeing Desktop Button -->
601
+ <button
602
+ onclick="triggerSeeingDesktop()"
603
+ id="btnTriggerAwareness"
604
+ class="w-full py-2.5 px-3 rounded-xl bg-[#1fb854] hover:bg-[#1aa049] text-black font-bold transition-all shadow-[0_0_15px_rgba(31,184,84,0.3)] flex items-center justify-center gap-2"
605
+ >
606
+ <span class="w-2 h-2 rounded-full bg-black"></span>
607
+ Lihat Layar Desktop
608
+ </button>
609
+
610
+ <!-- Awareness Scenarios Quick Buttons -->
611
+ <div class="grid grid-cols-2 gap-1.5 pt-1 text-[10px]">
612
+ <button
613
+ onclick="triggerScenario('vscode')"
614
+ class="p-2 rounded-lg bg-[#0f1715]/70 hover:bg-[#19362d] border border-[#19362d] text-left text-[#cac9c9]/80 truncate"
615
+ >
616
+ [ Lihat Sesi VS Code ]
617
+ </button>
618
+ <button
619
+ onclick="triggerScenario('music')"
620
+ class="p-2 rounded-lg bg-[#0f1715]/70 hover:bg-[#19362d] border border-[#19362d] text-left text-[#cac9c9]/80 truncate"
621
+ >
622
+ [ Lihat Lagu Musik ]
623
+ </button>
624
+ <button
625
+ onclick="triggerScenario('late_night')"
626
+ class="p-2 rounded-lg bg-[#0f1715]/70 hover:bg-[#19362d] border border-[#19362d] text-left text-[#cac9c9]/80 truncate"
627
+ >
628
+ [ Lihat Jam Malam ]
629
+ </button>
630
+ <button
631
+ onclick="triggerScenario('idle_user')"
632
+ class="p-2 rounded-lg bg-[#0f1715]/70 hover:bg-[#19362d] border border-[#19362d] text-left text-[#cac9c9]/80 truncate"
633
+ >
634
+ [ Lihat Layar Idle ]
635
+ </button>
636
+ </div>
637
+ </div>
638
+ </div>
639
+
640
+ <!-- 2. Mood Picker Card -->
641
+ <div
642
+ class="bg-[#13221b]/90 border border-[#19362d] rounded-2xl p-4 backdrop-blur-xl shadow-xl"
643
+ >
644
+ <div class="flex items-center justify-between pb-2 mb-3 border-b border-[#19362d]">
645
+ <h2
646
+ class="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-2"
647
+ >
648
+ <span class="w-2 h-2 rounded-full bg-[#1fb854]"></span>
649
+ 10 Dynamic Moods
650
+ </h2>
651
+ <span class="text-[10px] text-[#1fb854]">Persona</span>
652
+ </div>
653
+
654
+ <div class="grid grid-cols-2 gap-2 text-xs" id="moodButtonsGrid">
655
+ <!-- Populated by JS -->
656
+ </div>
657
+ </div>
658
+
659
+ <!-- 3. Status & Actions Card -->
660
+ <div
661
+ class="bg-[#13221b]/90 border border-[#19362d] rounded-2xl p-4 backdrop-blur-xl shadow-xl"
662
+ >
663
+ <div class="flex items-center justify-between pb-2 mb-3 border-b border-[#19362d]">
664
+ <h2
665
+ class="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-2"
666
+ >
667
+ <span class="w-2 h-2 rounded-full bg-[#1fb854]"></span>
668
+ Robot State & Context
669
+ </h2>
670
+ <span class="text-[10px] text-[#cac9c9]/50">States</span>
671
+ </div>
672
+
673
+ <div class="grid grid-cols-3 gap-2 text-xs">
674
+ <button
675
+ onclick="setStatus('idle')"
676
+ class="status-btn px-2.5 py-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all"
677
+ data-status="idle"
678
+ >
679
+ <div class="font-bold text-[11px] text-white">IDLE</div>
680
+ <div class="text-[9px] text-[#cac9c9]/50">Alive quirks</div>
681
+ </button>
682
+ <button
683
+ onclick="triggerSeeingDesktop()"
684
+ class="status-btn px-2.5 py-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all"
685
+ data-status="awareness"
686
+ >
687
+ <div class="font-bold text-[11px] text-[#1fb854]">LIHAT LAYAR</div>
688
+ <div class="text-[9px] text-[#1fb854]/70">Desktop view</div>
689
+ </button>
690
+ <button
691
+ onclick="setStatus('thinking')"
692
+ class="status-btn px-2.5 py-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all"
693
+ data-status="thinking"
694
+ >
695
+ <div class="font-bold text-[11px] text-cyan-400">THINKING</div>
696
+ <div class="text-[9px] text-[#cac9c9]/50">Chin ponder</div>
697
+ </button>
698
+ <button
699
+ onclick="setStatus('listening')"
700
+ class="status-btn px-2.5 py-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all"
701
+ data-status="listening"
702
+ >
703
+ <div class="font-bold text-[11px] text-amber-300">LISTEN</div>
704
+ <div class="text-[9px] text-[#cac9c9]/50">Focus lean</div>
705
+ </button>
706
+ <button
707
+ onclick="setStatus('speaking')"
708
+ class="status-btn px-2.5 py-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all"
709
+ data-status="speaking"
710
+ >
711
+ <div class="font-bold text-[11px] text-[#1fb854]">SPEAKING</div>
712
+ <div class="text-[9px] text-[#cac9c9]/50">Hand gesture</div>
713
+ </button>
714
+ <button
715
+ onclick="setStatus('sleeping')"
716
+ class="status-btn px-2.5 py-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all"
717
+ data-status="sleeping"
718
+ >
719
+ <div class="font-bold text-[11px] text-indigo-300">SLEEP</div>
720
+ <div class="text-[9px] text-[#cac9c9]/50">Deep rest</div>
721
+ </button>
722
+ </div>
723
+ </div>
724
+
725
+ <!-- 4. Speaking & Audio Lip-Flap Simulator -->
726
+ <div
727
+ class="bg-[#13221b]/90 border border-[#19362d] rounded-2xl p-4 backdrop-blur-xl shadow-xl"
728
+ >
729
+ <div class="flex items-center justify-between pb-2 mb-3 border-b border-[#19362d]">
730
+ <h2
731
+ class="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-2"
732
+ >
733
+ <span class="w-2 h-2 rounded-full bg-[#1fb854]"></span>
734
+ Voice Lip-Flap & Gestures
735
+ </h2>
736
+ <span class="text-[10px] text-[#1fb854]">Lip Sync</span>
737
+ </div>
738
+
739
+ <div class="flex flex-col gap-3 text-xs">
740
+ <div>
741
+ <div class="flex justify-between text-[11px] mb-1">
742
+ <span class="text-[#cac9c9]/60">Voice Intensity:</span>
743
+ <span id="intensityVal" class="text-[#1fb854] font-bold">0%</span>
744
+ </div>
745
+ <input
746
+ type="range"
747
+ id="intensitySlider"
748
+ min="0"
749
+ max="100"
750
+ value="0"
751
+ class="w-full cursor-pointer"
752
+ oninput="handleIntensityChange(this.value)"
753
+ />
754
+ </div>
755
+
756
+ <button
757
+ id="autoSpeakBtn"
758
+ onclick="toggleAutoSpeech()"
759
+ class="w-full py-2 px-3 rounded-xl bg-[#1fb854] hover:bg-[#1aa049] text-black font-bold transition-all text-center shadow-[0_0_15px_rgba(31,184,84,0.3)]"
760
+ >
761
+ Mulai Simulasi Suara Otomatis
762
+ </button>
763
+ </div>
764
+ </div>
765
+ </aside>
766
+ </main>
767
+
768
+ <script>
769
+ // ── MOOD DICTIONARY (MATCHING MARK PERSONA) ──
770
+ const MOODS = {
771
+ neutral: { name: 'Neutral', hex: '#1fb854', desc: 'Tenang & siap sedia' },
772
+ joy: { name: 'Joy', hex: '#facc15', desc: 'Gembira & tersenyum' },
773
+ sadness: { name: 'Sadness', hex: '#3b82f6', desc: 'Sedih berair mata' },
774
+ anger: { name: 'Anger', hex: '#ef4444', desc: 'Marah & sengit' },
775
+ fear: { name: 'Fear', hex: '#a855f7', desc: 'Takut & waspada' },
776
+ disgust: { name: 'Disgust', hex: '#84cc16', desc: 'Sinis & mengelak' },
777
+ anxiety: { name: 'Anxiety', hex: '#f97316', desc: 'Cemas pusing swirly' },
778
+ envy: { name: 'Envy', hex: '#14b8a6', desc: 'Iri & melirik tajam' },
779
+ embarrassment: { name: 'Embarrassment', hex: '#ec4899', desc: 'Malu merona' },
780
+ ennui: { name: 'Ennui', hex: '#6b7280', desc: 'Bosan & malas' }
781
+ }
782
+
783
+ let currentMood = 'neutral'
784
+ let currentStatus = 'idle'
785
+ let voiceIntensity = 0
786
+ let isAutoSpeaking = false
787
+ let autoSpeakTimer = null
788
+ let isBlinking = false
789
+ let isSeeingDesktop = false
790
+ let desktopTimeout = null
791
+
792
+ // ── PHYSICS SPRING-DAMPER ROTATION STATE ──
793
+ let targetRotX = 0
794
+ let targetRotY = 0
795
+ let targetRotZ = 0
796
+ let rotX = 0
797
+ let rotY = 0
798
+ let rotZ = 0
799
+ let velRotX = 0
800
+ let velRotY = 0
801
+ let velRotZ = 0
802
+
803
+ // Hands Floating Dynamics
804
+ let handLeftY = 0
805
+ let handLeftX = 0
806
+ let handRightY = 0
807
+ let handRightX = 0
808
+
809
+ // Antenna Elastic Dynamics
810
+ let antLeftRot = 0
811
+ let antRightRot = 0
812
+ let antVelLeft = 0
813
+ let antVelRight = 0
814
+
815
+ // Eye Saccades & Elastic Squash/Stretch
816
+ let eyeSaccadeX = 0
817
+ let eyeSaccadeY = 0
818
+ let targetSaccadeX = 0
819
+ let targetSaccadeY = 0
820
+ let eyeScaleX = 1
821
+ let eyeScaleY = 1
822
+
823
+ // ── AUTONOMOUS IDLE QUIRKS (RANDOM COMBINATION: TILT, NOD, STRETCH, GLANCE) ──
824
+ let lastQuirkTime = performance.now()
825
+ let nextQuirkInterval = 2200
826
+ let currentQuirk = 'BREATHING'
827
+ let quirkPitchOffset = 0
828
+ let quirkRollOffset = 0
829
+ let quirkAltitudeOffset = 0
830
+
831
+ // Populate Mood Grid
832
+ const moodGrid = document.getElementById('moodButtonsGrid')
833
+ Object.entries(MOODS).forEach(([key, val]) => {
834
+ const btn = document.createElement('button')
835
+ btn.className =
836
+ 'mood-btn p-2 rounded-xl border border-[#19362d] bg-[#0f1715]/60 hover:bg-[#19362d]/50 text-left transition-all flex items-center gap-2'
837
+ btn.setAttribute('data-mood', key)
838
+ btn.onclick = () => setMood(key)
839
+ btn.innerHTML = `
840
+ <span class="w-3 h-3 rounded-full shrink-0 shadow-sm" style="background-color: ${val.hex}"></span>
841
+ <div class="overflow-hidden">
842
+ <div class="font-bold text-[11px] text-white truncate">${val.name}</div>
843
+ <div class="text-[9px] text-[#cac9c9]/50 truncate">${val.desc}</div>
844
+ </div>
845
+ `
846
+ moodGrid.appendChild(btn)
847
+ })
848
+
849
+ function setMood(moodKey) {
850
+ if (!MOODS[moodKey]) return
851
+ currentMood = moodKey
852
+ const moodData = MOODS[moodKey]
853
+
854
+ document.getElementById('moodLabel').textContent = moodData.name.toUpperCase()
855
+ document.getElementById('moodLabel').style.color = moodData.hex
856
+
857
+ document.querySelectorAll('.mood-btn').forEach((b) => {
858
+ const isCurrent = b.getAttribute('data-mood') === moodKey
859
+ b.style.borderColor = isCurrent ? moodData.hex : '#19362d'
860
+ b.style.backgroundColor = isCurrent ? `${moodData.hex}22` : 'rgba(15, 23, 21, 0.6)'
861
+ })
862
+
863
+ document.documentElement.style.setProperty('--current-accent', moodData.hex)
864
+ document.querySelectorAll('.mood-fill').forEach((el) => {
865
+ el.setAttribute('fill', moodData.hex)
866
+ })
867
+
868
+ const thruster = document.getElementById('thrusterGlow')
869
+ if (thruster) {
870
+ thruster.style.backgroundColor = moodData.hex
871
+ }
872
+
873
+ // Elastic bounce on eye scale when mood changes
874
+ eyeScaleX = 1.25
875
+ eyeScaleY = 0.85
876
+ setTimeout(() => {
877
+ eyeScaleX = 1
878
+ eyeScaleY = 1
879
+ updateEyeMesh()
880
+ }, 140)
881
+
882
+ antVelLeft = -10
883
+ antVelRight = 10
884
+ renderFace()
885
+ }
886
+
887
+ function setStatus(statusKey) {
888
+ currentStatus = statusKey
889
+ document.getElementById('statusLabel').textContent = statusKey.toUpperCase()
890
+
891
+ document.querySelectorAll('.status-btn').forEach((b) => {
892
+ const isCurrent = b.getAttribute('data-status') === statusKey
893
+ b.style.borderColor = isCurrent ? '#1fb854' : '#19362d'
894
+ b.style.backgroundColor = isCurrent ? 'rgba(31,184,84,0.2)' : 'rgba(15, 23, 21, 0.6)'
895
+ })
896
+
897
+ const root = document.getElementById('robotRoot')
898
+ const reflection = document.getElementById('desktopScreenReflection')
899
+ const bulbL = document.getElementById('antBulbLeft')
900
+ const bulbR = document.getElementById('antBulbRight')
901
+
902
+ root.classList.remove('animate-glitch')
903
+
904
+ if (statusKey === 'awareness' || isSeeingDesktop) {
905
+ if (reflection) reflection.style.opacity = '1'
906
+ if (bulbL) bulbL.classList.add('animate-pulse')
907
+ if (bulbR) bulbR.classList.add('animate-pulse')
908
+ } else {
909
+ if (reflection) reflection.style.opacity = '0'
910
+ if (bulbL) bulbL.classList.remove('animate-pulse')
911
+ if (bulbR) bulbR.classList.remove('animate-pulse')
912
+ }
913
+
914
+ if (statusKey === 'thinking') {
915
+ antVelLeft = -14
916
+ antVelRight = 14
917
+ } else if (statusKey === 'listening') {
918
+ antVelLeft = 14
919
+ antVelRight = -14
920
+ } else if (statusKey === 'speaking') {
921
+ targetRotX = 0
922
+ targetRotY = 0
923
+ targetRotZ = 0
924
+ targetSaccadeX = 0
925
+ targetSaccadeY = 0
926
+ eyeSaccadeX = 0
927
+ eyeSaccadeY = 0
928
+ quirkPitchOffset = 0
929
+ quirkRollOffset = 0
930
+ quirkAltitudeOffset = 0
931
+ const actionLbl = document.getElementById('actionLabel')
932
+ if (actionLbl) actionLbl.textContent = 'MENATAP DEPAN'
933
+ const quirkInd = document.getElementById('quirkIndicator')
934
+ if (quirkInd) quirkInd.textContent = 'BERBICARA'
935
+ } else if (statusKey === 'error') {
936
+ root.classList.add('animate-glitch')
937
+ }
938
+
939
+ renderFace()
940
+ }
941
+
942
+ // ── AWARENESS SCENARIOS (MELIHAT DESKTOP) ──
943
+ const AWARENESS_SCENARIOS = {
944
+ vscode: { mood: 'neutral' },
945
+ music: { mood: 'joy' },
946
+ late_night: { mood: 'anxiety' },
947
+ idle_user: { mood: 'neutral' }
948
+ }
949
+
950
+ function triggerSeeingDesktop(targetMood = null) {
951
+ clearTimeout(desktopTimeout)
952
+ isSeeingDesktop = true
953
+
954
+ setStatus('awareness')
955
+ if (targetMood) setMood(targetMood)
956
+
957
+ antVelLeft = -14
958
+ antVelRight = 14
959
+
960
+ // Eye reading scan: looks left then smoothly sweeps right
961
+ targetSaccadeX = -6
962
+ targetSaccadeY = 2
963
+ setTimeout(() => {
964
+ targetSaccadeX = 6
965
+ }, 900)
966
+ setTimeout(() => {
967
+ targetSaccadeX = 0
968
+ targetSaccadeY = 0
969
+ }, 2200)
970
+
971
+ desktopTimeout = setTimeout(() => {
972
+ isSeeingDesktop = false
973
+ setStatus('idle')
974
+ }, 4500)
975
+ }
976
+
977
+ function triggerScenario(key) {
978
+ const scen = AWARENESS_SCENARIOS[key]
979
+ if (!scen) return
980
+ triggerSeeingDesktop(scen.mood)
981
+ }
982
+
983
+ // ── PROCEDURAL CURVED MINIMALIST EYES WITH SQUASH & STRETCH ──
984
+ function updateEyeMesh() {
985
+ const leftEye = document.getElementById('leftEyeBox')
986
+ const rightEye = document.getElementById('rightEyeBox')
987
+ if (leftEye) leftEye.style.transform = `scale(${eyeScaleX}, ${eyeScaleY})`
988
+ if (rightEye) rightEye.style.transform = `scale(${eyeScaleX}, ${eyeScaleY})`
989
+ }
990
+
991
+ function renderFace() {
992
+ const leftEye = document.getElementById('leftEyeBox')
993
+ const rightEye = document.getElementById('rightEyeBox')
994
+ const mouth = document.getElementById('mouthContainer')
995
+ const color = currentStatus === 'error' ? '#ef4444' : MOODS[currentMood].hex
996
+
997
+ // 1. SLEEPING STATE (Closed flat rounded curve)
998
+ if (currentStatus === 'sleeping') {
999
+ const sleepEye = `
1000
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1001
+ <path d="M 8 22 Q 20 25 32 22" fill="none" stroke="${color}" stroke-width="4.5" stroke-linecap="round"/>
1002
+ </svg>
1003
+ `
1004
+ leftEye.innerHTML = sleepEye
1005
+ rightEye.innerHTML = sleepEye
1006
+ mouth.innerHTML = `
1007
+ <svg viewBox="0 0 60 20" class="w-12 h-5">
1008
+ <path d="M 22 10 Q 30 14 38 10" fill="none" stroke="${color}" stroke-width="2.5" stroke-linecap="round"/>
1009
+ </svg>
1010
+ `
1011
+ return
1012
+ }
1013
+
1014
+ // 2. ERROR STATE (Sharp digital crosses)
1015
+ if (currentStatus === 'error') {
1016
+ const errEye = `
1017
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1018
+ <line x1="10" y1="10" x2="30" y2="30" stroke="${color}" stroke-width="4.5" stroke-linecap="round"/>
1019
+ <line x1="30" y1="10" x2="10" y2="30" stroke="${color}" stroke-width="4.5" stroke-linecap="round"/>
1020
+ </svg>
1021
+ `
1022
+ leftEye.innerHTML = errEye
1023
+ rightEye.innerHTML = errEye
1024
+ mouth.innerHTML = `
1025
+ <svg viewBox="0 0 60 20" class="w-14 h-5">
1026
+ <path d="M 15 10 L 22 6 L 30 14 L 38 6 L 45 10" fill="none" stroke="${color}" stroke-width="3" stroke-linecap="round"/>
1027
+ </svg>
1028
+ `
1029
+ return
1030
+ }
1031
+
1032
+ // 3. READING DESKTOP EYES (Focused eyes curved slightly downward)
1033
+ if (isSeeingDesktop || currentStatus === 'awareness') {
1034
+ const readingEye = `
1035
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1036
+ <path d="M 11 15 C 11 9, 29 9, 29 15 C 29 27, 11 27, 11 15 Z" fill="${color}"/>
1037
+ <circle cx="21" cy="18" r="3" fill="#ffffff"/>
1038
+ </svg>
1039
+ `
1040
+ leftEye.innerHTML = readingEye
1041
+ rightEye.innerHTML = readingEye
1042
+ mouth.innerHTML = `
1043
+ <svg viewBox="0 0 60 20" class="w-8 h-3">
1044
+ <line x1="25" y1="10" x2="35" y2="10" stroke="${color}" stroke-width="2" stroke-linecap="round"/>
1045
+ </svg>
1046
+ `
1047
+ return
1048
+ }
1049
+
1050
+ // 4. ELASTIC BLINKING STATE (Squash down to thin sleek slit)
1051
+ if (isBlinking) {
1052
+ const blinkEye = `
1053
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1054
+ <line x1="8" y1="20" x2="32" y2="20" stroke="${color}" stroke-width="4" stroke-linecap="round"/>
1055
+ </svg>
1056
+ `
1057
+ leftEye.innerHTML = blinkEye
1058
+ rightEye.innerHTML = blinkEye
1059
+ } else if (currentStatus === 'speaking' || voiceIntensity > 0.05) {
1060
+ // 5a. SPEAKING STATE: Eyes locked forward with dead-center pupil
1061
+ const speakPupilR = 3.8 + Math.min(1.0, voiceIntensity * 1.5)
1062
+ const speakEye = `
1063
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1064
+ <!-- Outer Curved Capsule Eye centered at X:20, Y:17 -->
1065
+ <path d="M 12 13 C 12 6.5, 28 6.5, 28 13 C 28 27.5, 12 27.5, 12 13 Z" fill="${color}"/>
1066
+ <!-- Dead-center pupil looking straight forward at user (X: 20, Y: 17) -->
1067
+ <circle cx="20" cy="17" r="${speakPupilR}" fill="#ffffff" opacity="0.98"/>
1068
+ </svg>
1069
+ `
1070
+ leftEye.innerHTML = speakEye
1071
+ rightEye.innerHTML = speakEye
1072
+ } else {
1073
+ // 5b. MOOD SPECIFIC CURVED DIGITAL EYES
1074
+ let leftEyeSvg = ''
1075
+ let rightEyeSvg = ''
1076
+
1077
+ switch (currentMood) {
1078
+ case 'joy':
1079
+ // Cute inverted happy curve arches ^ ^
1080
+ leftEyeSvg = `
1081
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1082
+ <path d="M 8 26 Q 20 8 32 26" fill="none" stroke="${color}" stroke-width="5" stroke-linecap="round"/>
1083
+ </svg>
1084
+ `
1085
+ rightEyeSvg = leftEyeSvg
1086
+ break
1087
+
1088
+ case 'anger':
1089
+ // Angled sharp brows \ /
1090
+ leftEyeSvg = `
1091
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1092
+ <path d="M 6 12 L 34 22 L 30 30 L 6 18 Z" fill="${color}"/>
1093
+ </svg>
1094
+ `
1095
+ rightEyeSvg = `
1096
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1097
+ <path d="M 34 12 L 6 22 L 10 30 L 34 18 Z" fill="${color}"/>
1098
+ </svg>
1099
+ `
1100
+ break
1101
+
1102
+ case 'sadness':
1103
+ // Drooping curve arches with teardrop T T
1104
+ leftEyeSvg = `
1105
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1106
+ <path d="M 8 16 Q 20 28 32 16" fill="none" stroke="${color}" stroke-width="4.5" stroke-linecap="round"/>
1107
+ <circle cx="20" cy="30" r="3.5" fill="#38bdf8" class="animate-bounce"/>
1108
+ </svg>
1109
+ `
1110
+ rightEyeSvg = `
1111
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1112
+ <path d="M 8 16 Q 20 28 32 16" fill="none" stroke="${color}" stroke-width="4.5" stroke-linecap="round"/>
1113
+ <circle cx="20" cy="30" r="3.5" fill="#38bdf8" class="animate-bounce"/>
1114
+ </svg>
1115
+ `
1116
+ break
1117
+
1118
+ case 'fear':
1119
+ // Wide circular trembling pupils
1120
+ leftEyeSvg = `
1121
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1122
+ <circle cx="20" cy="20" r="13" fill="none" stroke="${color}" stroke-width="4"/>
1123
+ <circle cx="20" cy="20" r="4.5" fill="${color}"/>
1124
+ </svg>
1125
+ `
1126
+ rightEyeSvg = leftEyeSvg
1127
+ break
1128
+
1129
+ case 'anxiety':
1130
+ // Spiral dizzy spinning eyes
1131
+ leftEyeSvg = `
1132
+ <svg viewBox="0 0 40 40" class="w-full h-full animate-[spin_4s_linear_infinite]">
1133
+ <path d="M 20 20 A 4 4 0 0 1 24 20 A 8 8 0 0 1 16 20 A 12 12 0 0 1 28 20" fill="none" stroke="${color}" stroke-width="3.5" stroke-linecap="round"/>
1134
+ </svg>
1135
+ `
1136
+ rightEyeSvg = leftEyeSvg
1137
+ break
1138
+
1139
+ case 'envy':
1140
+ // Sly feline slit eyes
1141
+ leftEyeSvg = `
1142
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1143
+ <path d="M 6 18 Q 20 12 34 20 Q 20 28 6 18 Z" fill="${color}" opacity="0.4"/>
1144
+ <line x1="18" y1="12" x2="22" y2="28" stroke="${color}" stroke-width="4" stroke-linecap="round"/>
1145
+ </svg>
1146
+ `
1147
+ rightEyeSvg = `
1148
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1149
+ <path d="M 6 20 Q 20 12 34 18 Q 20 28 6 20 Z" fill="${color}" opacity="0.4"/>
1150
+ <line x1="22" y1="12" x2="18" y2="28" stroke="${color}" stroke-width="4" stroke-linecap="round"/>
1151
+ </svg>
1152
+ `
1153
+ break
1154
+
1155
+ case 'embarrassment':
1156
+ // Shy closed squint > <
1157
+ leftEyeSvg = `
1158
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1159
+ <path d="M 12 14 L 28 22 L 12 30" fill="none" stroke="${color}" stroke-width="4" stroke-linecap="round"/>
1160
+ </svg>
1161
+ `
1162
+ rightEyeSvg = `
1163
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1164
+ <path d="M 28 14 L 12 22 L 28 30" fill="none" stroke="${color}" stroke-width="4" stroke-linecap="round"/>
1165
+ </svg>
1166
+ `
1167
+ break
1168
+
1169
+ case 'ennui':
1170
+ // Deadpan straight sleepy lids
1171
+ leftEyeSvg = `
1172
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1173
+ <line x1="8" y1="18" x2="32" y2="18" stroke="${color}" stroke-width="4.5" stroke-linecap="round"/>
1174
+ <circle cx="20" cy="24" r="5" fill="${color}"/>
1175
+ </svg>
1176
+ `
1177
+ rightEyeSvg = leftEyeSvg
1178
+ break
1179
+
1180
+ case 'disgust':
1181
+ // Asymmetrical squint
1182
+ leftEyeSvg = `
1183
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1184
+ <line x1="10" y1="20" x2="30" y2="16" stroke="${color}" stroke-width="4" stroke-linecap="round"/>
1185
+ </svg>
1186
+ `
1187
+ rightEyeSvg = `
1188
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1189
+ <circle cx="20" cy="20" r="10" fill="none" stroke="${color}" stroke-width="3.5"/>
1190
+ <circle cx="20" cy="20" r="4" fill="${color}"/>
1191
+ </svg>
1192
+ `
1193
+ break
1194
+
1195
+ default: // Neutral: Modern Minimalist Curved Stadium Eye with Centered Pupil
1196
+ leftEyeSvg = `
1197
+ <svg viewBox="0 0 40 40" class="w-full h-full">
1198
+ <!-- Outer Curved Oval centered at X:20, Y:17 -->
1199
+ <path d="M 12 13 C 12 6.5, 28 6.5, 28 13 C 28 27.5, 12 27.5, 12 13 Z" fill="${color}"/>
1200
+ <!-- Centered Pupil Core right in the middle (X: 20, Y: 17) -->
1201
+ <circle cx="20" cy="17" r="3.6" fill="#ffffff" opacity="0.95"/>
1202
+ </svg>
1203
+ `
1204
+ rightEyeSvg = leftEyeSvg
1205
+ break
1206
+ }
1207
+
1208
+ leftEye.innerHTML = leftEyeSvg
1209
+ rightEye.innerHTML = rightEyeSvg
1210
+ }
1211
+
1212
+ // 6. MOUTH / LIP-FLAP
1213
+ if (currentStatus === 'speaking' || voiceIntensity > 0.05) {
1214
+ const scale = Math.max(0.2, voiceIntensity)
1215
+ const openH = Math.round(5 + scale * 16)
1216
+ const openW = Math.round(12 + scale * 20)
1217
+
1218
+ mouth.innerHTML = `
1219
+ <svg viewBox="0 0 60 30" class="w-14 h-6">
1220
+ <ellipse cx="30" cy="15" rx="${openW / 2}" ry="${openH / 2}" fill="${color}" opacity="0.3"/>
1221
+ <ellipse cx="30" cy="15" rx="${Math.max(2, openW / 2 - 2)}" ry="${Math.max(2, openH / 2 - 2)}" fill="#ffffff"/>
1222
+ </svg>
1223
+ `
1224
+ } else if (currentStatus === 'thinking') {
1225
+ mouth.innerHTML = `
1226
+ <div class="flex items-center gap-1.5 py-1">
1227
+ <span class="w-1.5 h-1.5 rounded-full bg-[#1fb854] animate-ping"></span>
1228
+ <span class="w-1.5 h-1.5 rounded-full bg-[#1fb854] animate-ping [animation-delay:0.2s]"></span>
1229
+ <span class="w-1.5 h-1.5 rounded-full bg-[#1fb854] animate-ping [animation-delay:0.4s]"></span>
1230
+ </div>
1231
+ `
1232
+ } else {
1233
+ let mouthSvg = ''
1234
+ switch (currentMood) {
1235
+ case 'joy':
1236
+ mouthSvg = `<path d="M 20 8 Q 30 20 40 8" fill="none" stroke="${color}" stroke-width="3" stroke-linecap="round"/>`
1237
+ break
1238
+ case 'anger':
1239
+ mouthSvg = `<path d="M 20 18 Q 30 10 40 18" fill="none" stroke="${color}" stroke-width="3" stroke-linecap="round"/>`
1240
+ break
1241
+ case 'sadness':
1242
+ mouthSvg = `<path d="M 22 18 Q 30 10 38 18" fill="none" stroke="${color}" stroke-width="2.5" stroke-linecap="round"/>`
1243
+ break
1244
+ case 'anxiety':
1245
+ mouthSvg = `<path d="M 20 14 Q 25 10 30 14 Q 35 18 40 14" fill="none" stroke="${color}" stroke-width="2.5" stroke-linecap="round"/>`
1246
+ break
1247
+ case 'envy':
1248
+ mouthSvg = `<path d="M 24 16 Q 32 18 40 12" fill="none" stroke="${color}" stroke-width="2.5" stroke-linecap="round"/>`
1249
+ break
1250
+ default:
1251
+ mouthSvg = `<line x1="24" y1="14" x2="36" y2="14" stroke="${color}" stroke-width="2.5" stroke-linecap="round"/>`
1252
+ break
1253
+ }
1254
+ mouth.innerHTML = `
1255
+ <svg viewBox="0 0 60 26" class="w-12 h-4">
1256
+ ${mouthSvg}
1257
+ </svg>
1258
+ `
1259
+ }
1260
+ }
1261
+
1262
+ // Natural Elastic Blinking Loop (Squash down, stretch back)
1263
+ function triggerBlink() {
1264
+ if (currentStatus === 'sleeping') {
1265
+ setTimeout(triggerBlink, 3000)
1266
+ return
1267
+ }
1268
+ isBlinking = true
1269
+ // Squash Y, widen X
1270
+ eyeScaleX = 1.15
1271
+ eyeScaleY = 0.15
1272
+ updateEyeMesh()
1273
+ renderFace()
1274
+
1275
+ setTimeout(() => {
1276
+ isBlinking = false
1277
+ // Overshoot stretch
1278
+ eyeScaleX = 0.95
1279
+ eyeScaleY = 1.1
1280
+ updateEyeMesh()
1281
+ renderFace()
1282
+
1283
+ setTimeout(() => {
1284
+ eyeScaleX = 1
1285
+ eyeScaleY = 1
1286
+ updateEyeMesh()
1287
+ }, 80)
1288
+
1289
+ const doubleBlink = Math.random() < 0.2
1290
+ if (doubleBlink) {
1291
+ setTimeout(() => {
1292
+ isBlinking = true
1293
+ eyeScaleY = 0.15
1294
+ updateEyeMesh()
1295
+ renderFace()
1296
+ setTimeout(() => {
1297
+ isBlinking = false
1298
+ eyeScaleY = 1
1299
+ updateEyeMesh()
1300
+ renderFace()
1301
+ }, 90)
1302
+ }, 110)
1303
+ }
1304
+
1305
+ const nextDelay = 2600 + Math.random() * 3200
1306
+ setTimeout(triggerBlink, nextDelay)
1307
+ }, 120)
1308
+ }
1309
+ setTimeout(triggerBlink, 1500)
1310
+
1311
+ // ── POKE REACTION ──
1312
+ const avatarArea = document.getElementById('avatarInteractiveArea')
1313
+ const robotRoot = document.getElementById('robotRoot')
1314
+ const robotHead = document.getElementById('robotHead')
1315
+ const robotBody = document.getElementById('robotBody')
1316
+ const handLeftEl = document.getElementById('handLeft')
1317
+ const handRightEl = document.getElementById('handRight')
1318
+ const antLeftEl = document.getElementById('antennaLeft')
1319
+ const antRightEl = document.getElementById('antennaRight')
1320
+ const groundShadow = document.getElementById('groundShadow')
1321
+ const eyesContainer = document.getElementById('eyesContainer')
1322
+ const actionLabel = document.getElementById('actionLabel')
1323
+ const quirkIndicator = document.getElementById('quirkIndicator')
1324
+
1325
+ let pokeVelocityY = 0
1326
+ avatarArea.addEventListener('click', () => {
1327
+ pokeVelocityY = -26
1328
+ velRotY += Math.random() > 0.5 ? 14 : -14
1329
+ antVelLeft = -28
1330
+ antVelRight = 28
1331
+
1332
+ triggerBlink()
1333
+ })
1334
+
1335
+ // ── AUTONOMOUS IDLE QUIRKS (RANDOM COMBINATION NATURAL SYSTEM) ──
1336
+ function updateAutonomousQuirks(now) {
1337
+ if (isSeeingDesktop || currentStatus === 'speaking') {
1338
+ targetRotY = 0
1339
+ targetRotZ = 0
1340
+ targetSaccadeX = 0
1341
+ targetSaccadeY = 0
1342
+ quirkPitchOffset = 0
1343
+ quirkRollOffset = 0
1344
+ quirkAltitudeOffset = 0
1345
+ return
1346
+ }
1347
+
1348
+ if (now - lastQuirkTime >= nextQuirkInterval) {
1349
+ lastQuirkTime = now
1350
+ nextQuirkInterval = 2000 + Math.random() * 1200 // Fast dynamic 2s rhythm
1351
+
1352
+ const roll = Math.random()
1353
+
1354
+ // 1. Curious Head Tilt (Memiringkan kepala penasaran)
1355
+ if (roll < 0.3) {
1356
+ currentQuirk = 'TILT PENASARAN'
1357
+ quirkRollOffset = Math.random() > 0.5 ? 10 : -10
1358
+ quirkPitchOffset = -4
1359
+ targetRotY = (Math.random() - 0.5) * 8
1360
+ targetSaccadeX = quirkRollOffset * 0.4
1361
+ actionLabel.textContent = 'KEPALA MIRING'
1362
+ }
1363
+ // 2. Subtle Listening Nod (Mengangguk kecil menyimak)
1364
+ else if (roll < 0.55) {
1365
+ currentQuirk = 'MENGANGGUK KECIL'
1366
+ quirkPitchOffset = -9
1367
+ quirkRollOffset = 0
1368
+ setTimeout(() => {
1369
+ quirkPitchOffset = 0
1370
+ }, 350)
1371
+ actionLabel.textContent = 'MENGANGGUK'
1372
+ }
1373
+ // 3. Floating Stretch Breath (Helaan napas apung)
1374
+ else if (roll < 0.75) {
1375
+ currentQuirk = 'PEREGANGAN APUNG'
1376
+ quirkAltitudeOffset = -12
1377
+ quirkPitchOffset = 3
1378
+ setTimeout(() => {
1379
+ quirkAltitudeOffset = 0
1380
+ quirkPitchOffset = 0
1381
+ }, 700)
1382
+ actionLabel.textContent = 'HELAAN NAPAS'
1383
+ }
1384
+ // 4. Casual Look Around (Menoleh santai)
1385
+ else {
1386
+ currentQuirk = 'MENOLEH SANTAI'
1387
+ targetRotY = Math.random() > 0.5 ? 12 : -12
1388
+ quirkPitchOffset = -2
1389
+ quirkRollOffset = (Math.random() - 0.5) * 4
1390
+ targetSaccadeX = targetRotY * 0.35
1391
+ actionLabel.textContent = targetRotY > 0 ? 'MENOLEH KANAN' : 'MENOLEH KIRI'
1392
+ }
1393
+
1394
+ quirkIndicator.textContent = currentQuirk
1395
+ }
1396
+ }
1397
+
1398
+ // ── MAIN HIGH-PRECISION PHYSICS TICKER ──
1399
+ let frameCount = 0
1400
+ let fpsTimer = performance.now()
1401
+ let prevTime = performance.now()
1402
+ let pokeOffsetY = 0
1403
+
1404
+ function updateFrame(now) {
1405
+ const dt = Math.min(0.05, (now - prevTime) / 1000)
1406
+ prevTime = now
1407
+ const t = now * 0.001
1408
+
1409
+ // 1. Autonomous Quirks
1410
+ updateAutonomousQuirks(now)
1411
+
1412
+ // 2. Eye Saccades (Lerp) - locked forward during speaking
1413
+ if (currentStatus === 'speaking') {
1414
+ targetSaccadeX = 0
1415
+ targetSaccadeY = 0
1416
+ }
1417
+ eyeSaccadeX += (targetSaccadeX - eyeSaccadeX) * (currentStatus === 'speaking' ? 0.35 : 0.16)
1418
+ eyeSaccadeY += (targetSaccadeY - eyeSaccadeY) * (currentStatus === 'speaking' ? 0.35 : 0.16)
1419
+
1420
+ // 3. Multi-Harmonic Organic Breathing & Sway
1421
+ let breathSpeed = 1.6
1422
+ let breathDepth = 8
1423
+ let swayDepth = 4
1424
+ let rollDepth = 1.2
1425
+
1426
+ if (currentStatus === 'sleeping') {
1427
+ breathSpeed = 0.8
1428
+ breathDepth = 4.5
1429
+ swayDepth = 1.2
1430
+ } else if (currentStatus === 'speaking') {
1431
+ breathSpeed = 2.2
1432
+ breathDepth = 7
1433
+ swayDepth = 0 // Pure straight forward, no horizontal swaying
1434
+ rollDepth = 0 // Strictly zero roll
1435
+ } else if (isSeeingDesktop) {
1436
+ breathSpeed = 1.8
1437
+ breathDepth = 5
1438
+ } else if (currentStatus === 'thinking') {
1439
+ breathSpeed = 1.2
1440
+ }
1441
+
1442
+ const harmonicY =
1443
+ Math.sin(t * breathSpeed) * breathDepth +
1444
+ Math.sin(t * breathSpeed * 1.8) * (breathDepth * 0.3)
1445
+ const harmonicX = Math.cos(t * breathSpeed * 0.65) * swayDepth
1446
+ const harmonicRoll = Math.sin(t * breathSpeed * 0.5) * rollDepth
1447
+
1448
+ const breathScaleY = 1 + harmonicY * 0.0016
1449
+ const breathScaleX = 1 - harmonicY * 0.0008
1450
+
1451
+ pokeOffsetY += pokeVelocityY * dt * 30
1452
+ pokeVelocityY += (0 - pokeOffsetY) * 0.25 - pokeVelocityY * 0.2
1453
+
1454
+ // 4. Spring-Damper for Head Rotation (Pitch, Yaw, Roll)
1455
+ let addPitch = currentStatus === 'sleeping' ? 14 : currentStatus === 'listening' ? -6 : 0
1456
+ let addRoll = currentStatus === 'thinking' ? 7 : currentStatus === 'joy' ? 3 : 0
1457
+ let desktopForwardZ = 0
1458
+ let desktopDownwardY = 0
1459
+
1460
+ if (isSeeingDesktop) {
1461
+ addPitch = -16
1462
+ desktopForwardZ = 35
1463
+ desktopDownwardY = -12
1464
+ actionLabel.textContent = 'MEMBACA MONITOR'
1465
+ } else if (currentStatus === 'speaking') {
1466
+ // Natural speech cadence nod: strictly forward pitch only, no yaw/roll
1467
+ const speakNod = Math.sin(t * 7.5) * (voiceIntensity * 2.2)
1468
+ addPitch = -1.5 + speakNod
1469
+ actionLabel.textContent = 'MENATAP DEPAN'
1470
+ quirkIndicator.textContent = 'BERBICARA'
1471
+ }
1472
+
1473
+ let effectiveTargetX = targetRotX + quirkPitchOffset + addPitch
1474
+ let effectiveTargetY = targetRotY
1475
+ let effectiveTargetZ = targetRotZ + quirkRollOffset + addRoll
1476
+
1477
+ if (currentStatus === 'speaking') {
1478
+ effectiveTargetX = addPitch
1479
+ effectiveTargetY = 0 // Strictly face forward (no turning left/right)
1480
+ effectiveTargetZ = 0 // Strictly upright (no side tilting)
1481
+ } else if (isSeeingDesktop) {
1482
+ effectiveTargetX = -14 + addPitch
1483
+ effectiveTargetY = Math.sin(t * 1.8) * 4
1484
+ effectiveTargetZ = 0
1485
+ }
1486
+
1487
+ // Spring damper calculation
1488
+ velRotX += ((effectiveTargetX - rotX) * 140 - velRotX * 16) * dt
1489
+ velRotY += ((effectiveTargetY - rotY) * 140 - velRotY * 16) * dt
1490
+ velRotZ += ((effectiveTargetZ - rotZ) * 140 - velRotZ * 16) * dt
1491
+ rotX += velRotX * dt
1492
+ rotY += velRotY * dt
1493
+ rotZ += velRotZ * dt
1494
+
1495
+ // 5. Apply Rigid Compound Transforms around Throat Pivot
1496
+ const totalY = harmonicY + pokeOffsetY + desktopDownwardY + quirkAltitudeOffset
1497
+ robotRoot.style.transform = `translate3d(${harmonicX}px, ${totalY}px, ${desktopForwardZ}px) scale3d(${breathScaleX}, ${breathScaleY}, 1)`
1498
+
1499
+ // Head: Rotates smoothly with pitch, yaw, and harmonic roll around throat
1500
+ robotHead.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg) rotateZ(${rotZ + harmonicRoll}deg)`
1501
+
1502
+ // Torso: Follows directly beneath the throat socket without tearing
1503
+ const torsoRotX = rotX * 0.35
1504
+ const torsoRotY = currentStatus === 'speaking' ? 0 : rotY * 0.35
1505
+ robotBody.style.transform = `translateZ(-5px) rotateX(${torsoRotX}deg) rotateY(${torsoRotY}deg)`
1506
+
1507
+ if (groundShadow) {
1508
+ const shadowScale = Math.max(0.65, 1 - totalY * 0.012)
1509
+ const shadowOpacity = Math.max(0.3, 0.75 + totalY * 0.012)
1510
+ groundShadow.style.transform = `translateZ(-60px) rotateX(75deg) scale(${shadowScale})`
1511
+ groundShadow.style.opacity = shadowOpacity
1512
+ }
1513
+
1514
+ // 6. Antenna Spring Damping
1515
+ const antTargetLeft =
1516
+ currentStatus === 'speaking'
1517
+ ? Math.sin(t * 6) * (voiceIntensity * 6)
1518
+ : -velRotY * 0.35 +
1519
+ (currentStatus === 'thinking' ? -15 : currentStatus === 'listening' ? 14 : 0)
1520
+ const antTargetRight =
1521
+ currentStatus === 'speaking'
1522
+ ? -Math.sin(t * 6) * (voiceIntensity * 6)
1523
+ : -velRotY * 0.35 +
1524
+ (currentStatus === 'thinking' ? 15 : currentStatus === 'listening' ? -14 : 0)
1525
+
1526
+ antVelLeft += (antTargetLeft - antLeftRot) * 200 * dt - antVelLeft * 14 * dt
1527
+ antVelRight += (antTargetRight - antRightRot) * 200 * dt - antVelRight * 14 * dt
1528
+ antLeftRot += antVelLeft * dt
1529
+ antRightRot += antVelRight * dt
1530
+
1531
+ if (antLeftEl) antLeftEl.style.transform = `rotate(${antLeftRot}deg)`
1532
+ if (antRightEl) antRightEl.style.transform = `rotate(${antRightRot}deg)`
1533
+
1534
+ // 7. Hand Physics
1535
+ let targetHandLY = Math.sin(t * breathSpeed * 0.9) * 5
1536
+ let targetHandLX = Math.cos(t * breathSpeed * 0.9) * 2.5
1537
+ let targetHandRY = Math.sin(t * breathSpeed * 0.9 + 1.2) * 5
1538
+ let targetHandRX = Math.cos(t * breathSpeed * 0.9 + 1.2) * 2.5
1539
+
1540
+ if (isSeeingDesktop) {
1541
+ targetHandLY = -40
1542
+ targetHandLX = 12
1543
+ targetHandRY = -40
1544
+ targetHandRX = -12
1545
+ } else if (currentStatus === 'thinking') {
1546
+ targetHandLY = -34
1547
+ targetHandLX = 16
1548
+ } else if (currentStatus === 'speaking') {
1549
+ const voicePulse = Math.sin(t * 8) * (voiceIntensity * 12)
1550
+ targetHandLY = -10 + voicePulse
1551
+ targetHandLX = -4
1552
+ targetHandRY = -10 - voicePulse
1553
+ targetHandRX = 4
1554
+ } else if (currentStatus === 'sleeping') {
1555
+ targetHandLY = 18
1556
+ targetHandRY = 18
1557
+ } else if (currentMood === 'joy') {
1558
+ targetHandLY -= 8
1559
+ targetHandRY -= 8
1560
+ }
1561
+
1562
+ handLeftY += (targetHandLY - handLeftY) * 0.16
1563
+ handLeftX += (targetHandLX - handLeftX) * 0.16
1564
+ handRightY += (targetHandRY - handRightY) * 0.16
1565
+ handRightX += (targetHandRX - handRightX) * 0.16
1566
+
1567
+ if (handLeftEl) {
1568
+ handLeftEl.style.transform = `translate3d(${handLeftX}px, ${handLeftY}px, ${isSeeingDesktop ? 30 : 18}px) rotateZ(${-rotY * 0.2}deg)`
1569
+ }
1570
+ if (handRightEl) {
1571
+ handRightEl.style.transform = `translate3d(${handRightX}px, ${handRightY}px, ${isSeeingDesktop ? 30 : 18}px) rotateZ(${-rotY * 0.2}deg)`
1572
+ }
1573
+
1574
+ // 8. Eyes Parallax - firmly focused forward on user when speaking
1575
+ if (eyesContainer) {
1576
+ const finalEyeX = currentStatus === 'speaking' ? 0 : rotY * 0.35 + eyeSaccadeX
1577
+ const finalEyeY = currentStatus === 'speaking' ? 0 : -rotX * 0.25 + eyeSaccadeY
1578
+ eyesContainer.style.transform = `translate3d(${finalEyeX}px, ${finalEyeY}px, 10px)`
1579
+ }
1580
+
1581
+ // FPS Monitor
1582
+ frameCount++
1583
+ if (now - fpsTimer >= 1000) {
1584
+ document.getElementById('fpsCounter').textContent = frameCount
1585
+ frameCount = 0
1586
+ fpsTimer = now
1587
+ }
1588
+
1589
+ requestAnimationFrame(updateFrame)
1590
+ }
1591
+ requestAnimationFrame(updateFrame)
1592
+
1593
+ // ── SPEECH SIMULATION CONTROLS ──
1594
+ function handleIntensityChange(val) {
1595
+ voiceIntensity = val / 100
1596
+ document.getElementById('intensityVal').textContent = `${Math.round(val)}%`
1597
+ if (voiceIntensity > 0.05 && currentStatus !== 'speaking' && !isAutoSpeaking) {
1598
+ setStatus('speaking')
1599
+ } else if (voiceIntensity <= 0.05 && currentStatus === 'speaking' && !isAutoSpeaking) {
1600
+ setStatus('idle')
1601
+ }
1602
+ renderFace()
1603
+ }
1604
+
1605
+ function toggleAutoSpeech() {
1606
+ isAutoSpeaking = !isAutoSpeaking
1607
+ const btn = document.getElementById('autoSpeakBtn')
1608
+ const slider = document.getElementById('intensitySlider')
1609
+
1610
+ if (isAutoSpeaking) {
1611
+ btn.textContent = 'Berhenti Simulasi Suara'
1612
+ btn.className =
1613
+ 'w-full py-2 px-3 rounded-xl bg-rose-500 hover:bg-rose-600 text-white font-bold transition-all text-center shadow-[0_0_15px_rgba(244,63,94,0.3)]'
1614
+ setStatus('speaking')
1615
+
1616
+ let phase = 0
1617
+ autoSpeakTimer = setInterval(() => {
1618
+ phase += 0.35
1619
+ const rawInt = Math.sin(phase) * 0.4 + Math.sin(phase * 2.3) * 0.3 + Math.random() * 0.3
1620
+ const intensity = Math.max(0.1, Math.min(0.95, rawInt))
1621
+ handleIntensityChange(intensity * 100)
1622
+ slider.value = intensity * 100
1623
+ }, 90)
1624
+ } else {
1625
+ btn.textContent = 'Mulai Simulasi Suara Otomatis'
1626
+ btn.className =
1627
+ 'w-full py-2 px-3 rounded-xl bg-[#1fb854] hover:bg-[#1aa049] text-black font-bold transition-all text-center shadow-[0_0_15px_rgba(31,184,84,0.3)]'
1628
+ clearInterval(autoSpeakTimer)
1629
+ handleIntensityChange(0)
1630
+ slider.value = 0
1631
+ setStatus('idle')
1632
+ }
1633
+ }
1634
+
1635
+ // Initial render in Forest Theme
1636
+ setMood('neutral')
1637
+ setStatus('idle')
1638
+ </script>
1639
+ </body>
1640
+ </html>