@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.
- package/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- 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 • Autonomous Quirks • 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>
|