@gokulvenkatareddy/cortex 0.1.7
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/README.md +1295 -0
- package/apps/octogent/.github/workflows/ci.yml +40 -0
- package/apps/octogent/.shims/claude +4 -0
- package/apps/octogent/AGENTS.md +71 -0
- package/apps/octogent/CONTRIBUTING.md +72 -0
- package/apps/octogent/LICENSE +21 -0
- package/apps/octogent/README.md +184 -0
- package/apps/octogent/apps/api/AGENTS.md +32 -0
- package/apps/octogent/apps/api/package.json +19 -0
- package/apps/octogent/apps/api/src/agentStateDetection.ts +181 -0
- package/apps/octogent/apps/api/src/claudeSessionScanner.ts +235 -0
- package/apps/octogent/apps/api/src/claudeSkills.ts +182 -0
- package/apps/octogent/apps/api/src/claudeUsage.ts +922 -0
- package/apps/octogent/apps/api/src/cli.ts +595 -0
- package/apps/octogent/apps/api/src/codeIntelStore.ts +46 -0
- package/apps/octogent/apps/api/src/codexUsage.ts +278 -0
- package/apps/octogent/apps/api/src/createApiServer/codeIntelRoutes.ts +60 -0
- package/apps/octogent/apps/api/src/createApiServer/conversationRoutes.ts +128 -0
- package/apps/octogent/apps/api/src/createApiServer/deckRoutes.ts +873 -0
- package/apps/octogent/apps/api/src/createApiServer/gitParsers.ts +140 -0
- package/apps/octogent/apps/api/src/createApiServer/gitRoutes.ts +214 -0
- package/apps/octogent/apps/api/src/createApiServer/miscRoutes.ts +316 -0
- package/apps/octogent/apps/api/src/createApiServer/monitorParsers.ts +137 -0
- package/apps/octogent/apps/api/src/createApiServer/monitorRoutes.ts +95 -0
- package/apps/octogent/apps/api/src/createApiServer/requestHandler.ts +311 -0
- package/apps/octogent/apps/api/src/createApiServer/requestParsers.ts +25 -0
- package/apps/octogent/apps/api/src/createApiServer/routeHelpers.ts +97 -0
- package/apps/octogent/apps/api/src/createApiServer/security.ts +70 -0
- package/apps/octogent/apps/api/src/createApiServer/terminalParsers.ts +167 -0
- package/apps/octogent/apps/api/src/createApiServer/terminalRoutes.ts +315 -0
- package/apps/octogent/apps/api/src/createApiServer/types.ts +24 -0
- package/apps/octogent/apps/api/src/createApiServer/uiStateParsers.ts +255 -0
- package/apps/octogent/apps/api/src/createApiServer/upgradeHandler.ts +38 -0
- package/apps/octogent/apps/api/src/createApiServer/usageRoutes.ts +84 -0
- package/apps/octogent/apps/api/src/createApiServer.ts +176 -0
- package/apps/octogent/apps/api/src/deck/readDeckTentacles.ts +595 -0
- package/apps/octogent/apps/api/src/githubRepoSummary.ts +397 -0
- package/apps/octogent/apps/api/src/logging.ts +9 -0
- package/apps/octogent/apps/api/src/monitor/defaults.ts +3 -0
- package/apps/octogent/apps/api/src/monitor/index.ts +8 -0
- package/apps/octogent/apps/api/src/monitor/repository.ts +303 -0
- package/apps/octogent/apps/api/src/monitor/service.ts +349 -0
- package/apps/octogent/apps/api/src/monitor/types.ts +120 -0
- package/apps/octogent/apps/api/src/monitor/xProvider.ts +587 -0
- package/apps/octogent/apps/api/src/projectPersistence.ts +377 -0
- package/apps/octogent/apps/api/src/prompts/index.ts +10 -0
- package/apps/octogent/apps/api/src/prompts/promptResolver.ts +145 -0
- package/apps/octogent/apps/api/src/runtimeMetadata.ts +69 -0
- package/apps/octogent/apps/api/src/server.ts +80 -0
- package/apps/octogent/apps/api/src/setupState.ts +80 -0
- package/apps/octogent/apps/api/src/setupStatus.ts +174 -0
- package/apps/octogent/apps/api/src/startupPrerequisites.ts +146 -0
- package/apps/octogent/apps/api/src/terminalRuntime/channelMessaging.ts +87 -0
- package/apps/octogent/apps/api/src/terminalRuntime/claudeTranscript.ts +279 -0
- package/apps/octogent/apps/api/src/terminalRuntime/constants.ts +15 -0
- package/apps/octogent/apps/api/src/terminalRuntime/conversations.ts +492 -0
- package/apps/octogent/apps/api/src/terminalRuntime/gitOperations.ts +341 -0
- package/apps/octogent/apps/api/src/terminalRuntime/hookProcessor.ts +405 -0
- package/apps/octogent/apps/api/src/terminalRuntime/protocol.ts +46 -0
- package/apps/octogent/apps/api/src/terminalRuntime/ptyEnvironment.ts +50 -0
- package/apps/octogent/apps/api/src/terminalRuntime/registry.ts +423 -0
- package/apps/octogent/apps/api/src/terminalRuntime/sessionRuntime.ts +671 -0
- package/apps/octogent/apps/api/src/terminalRuntime/systemClients.ts +432 -0
- package/apps/octogent/apps/api/src/terminalRuntime/types.ts +157 -0
- package/apps/octogent/apps/api/src/terminalRuntime/worktreeManager.ts +135 -0
- package/apps/octogent/apps/api/src/terminalRuntime.ts +567 -0
- package/apps/octogent/apps/api/src/usageUtils.ts +16 -0
- package/apps/octogent/apps/api/src/ws-shim.d.ts +28 -0
- package/apps/octogent/apps/api/tests/agentStateDetection.test.ts +67 -0
- package/apps/octogent/apps/api/tests/claudeUsage.test.ts +583 -0
- package/apps/octogent/apps/api/tests/codexUsage.test.ts +107 -0
- package/apps/octogent/apps/api/tests/createApiServer.test.ts +3207 -0
- package/apps/octogent/apps/api/tests/githubRepoSummary.test.ts +100 -0
- package/apps/octogent/apps/api/tests/logging.test.ts +33 -0
- package/apps/octogent/apps/api/tests/monitorApi.test.ts +467 -0
- package/apps/octogent/apps/api/tests/monitorCore.test.ts +104 -0
- package/apps/octogent/apps/api/tests/promptResolver.test.ts +109 -0
- package/apps/octogent/apps/api/tests/protocol.test.ts +14 -0
- package/apps/octogent/apps/api/tests/sessionRuntime.test.ts +608 -0
- package/apps/octogent/apps/api/tests/startupPrerequisites.test.ts +70 -0
- package/apps/octogent/apps/api/tests/upgradeHandler.test.ts +40 -0
- package/apps/octogent/apps/api/tests/xMonitorProvider.test.ts +109 -0
- package/apps/octogent/apps/api/tsconfig.json +7 -0
- package/apps/octogent/apps/api/vitest.config.ts +7 -0
- package/apps/octogent/apps/web/AGENTS.md +38 -0
- package/apps/octogent/apps/web/index.html +13 -0
- package/apps/octogent/apps/web/package.json +32 -0
- package/apps/octogent/apps/web/public/octopus-favicon.svg +26 -0
- package/apps/octogent/apps/web/src/App.tsx +646 -0
- package/apps/octogent/apps/web/src/app/canvas/types.ts +34 -0
- package/apps/octogent/apps/web/src/app/codeIntelAggregation.ts +278 -0
- package/apps/octogent/apps/web/src/app/constants.ts +28 -0
- package/apps/octogent/apps/web/src/app/conversationNormalizers.ts +135 -0
- package/apps/octogent/apps/web/src/app/formatTimestamp.ts +18 -0
- package/apps/octogent/apps/web/src/app/githubMetrics.ts +76 -0
- package/apps/octogent/apps/web/src/app/githubNormalizers.ts +91 -0
- package/apps/octogent/apps/web/src/app/hooks/useAgentRuntimeStates.ts +18 -0
- package/apps/octogent/apps/web/src/app/hooks/useBackendLivenessPolling.ts +53 -0
- package/apps/octogent/apps/web/src/app/hooks/useCanvasGraphData.ts +449 -0
- package/apps/octogent/apps/web/src/app/hooks/useCanvasTransform.ts +260 -0
- package/apps/octogent/apps/web/src/app/hooks/useClaudeUsagePolling.ts +40 -0
- package/apps/octogent/apps/web/src/app/hooks/useClickOutside.ts +30 -0
- package/apps/octogent/apps/web/src/app/hooks/useCodeIntelRuntime.ts +83 -0
- package/apps/octogent/apps/web/src/app/hooks/useCodexUsagePolling.ts +35 -0
- package/apps/octogent/apps/web/src/app/hooks/useConsoleKeyboardShortcuts.ts +31 -0
- package/apps/octogent/apps/web/src/app/hooks/useConversationsRuntime.ts +377 -0
- package/apps/octogent/apps/web/src/app/hooks/useForceSimulation.ts +319 -0
- package/apps/octogent/apps/web/src/app/hooks/useGitHubPrimaryViewModel.ts +143 -0
- package/apps/octogent/apps/web/src/app/hooks/useGithubSummaryPolling.ts +28 -0
- package/apps/octogent/apps/web/src/app/hooks/useInitialColumnsHydration.ts +64 -0
- package/apps/octogent/apps/web/src/app/hooks/useMonitorRuntime.ts +220 -0
- package/apps/octogent/apps/web/src/app/hooks/usePersistedUiState.ts +536 -0
- package/apps/octogent/apps/web/src/app/hooks/usePollingData.ts +79 -0
- package/apps/octogent/apps/web/src/app/hooks/usePromptLibrary.ts +185 -0
- package/apps/octogent/apps/web/src/app/hooks/useTentacleGitLifecycle.ts +530 -0
- package/apps/octogent/apps/web/src/app/hooks/useTerminalCompletionNotification.ts +94 -0
- package/apps/octogent/apps/web/src/app/hooks/useTerminalMutations.ts +266 -0
- package/apps/octogent/apps/web/src/app/hooks/useTerminalStateReconciliation.ts +23 -0
- package/apps/octogent/apps/web/src/app/hooks/useUsageHeatmapPolling.ts +43 -0
- package/apps/octogent/apps/web/src/app/hooks/useWorkspaceSetup.ts +80 -0
- package/apps/octogent/apps/web/src/app/hotkeys.ts +31 -0
- package/apps/octogent/apps/web/src/app/monitorNormalizers.ts +145 -0
- package/apps/octogent/apps/web/src/app/notificationSounds.ts +164 -0
- package/apps/octogent/apps/web/src/app/terminalRuntimeStateStore.ts +261 -0
- package/apps/octogent/apps/web/src/app/terminalState.ts +21 -0
- package/apps/octogent/apps/web/src/app/types.ts +42 -0
- package/apps/octogent/apps/web/src/app/uiStateNormalizers.ts +113 -0
- package/apps/octogent/apps/web/src/app/usageNormalizers.ts +58 -0
- package/apps/octogent/apps/web/src/components/ActiveAgentsSidebar.tsx +60 -0
- package/apps/octogent/apps/web/src/components/ActivityPrimaryView.tsx +21 -0
- package/apps/octogent/apps/web/src/components/AgentStateBadge.tsx +47 -0
- package/apps/octogent/apps/web/src/components/CanvasPrimaryView.tsx +1532 -0
- package/apps/octogent/apps/web/src/components/ClearAllConversationsDialog.tsx +33 -0
- package/apps/octogent/apps/web/src/components/CodeIntelArcDiagram.tsx +245 -0
- package/apps/octogent/apps/web/src/components/CodeIntelPrimaryView.tsx +104 -0
- package/apps/octogent/apps/web/src/components/CodeIntelTreemap.tsx +138 -0
- package/apps/octogent/apps/web/src/components/ConsolePrimaryNav.tsx +31 -0
- package/apps/octogent/apps/web/src/components/ConversationsPrimaryView.tsx +243 -0
- package/apps/octogent/apps/web/src/components/DeckPrimaryView.tsx +613 -0
- package/apps/octogent/apps/web/src/components/DeleteTentacleDialog.tsx +91 -0
- package/apps/octogent/apps/web/src/components/EmptyOctopus.tsx +715 -0
- package/apps/octogent/apps/web/src/components/GitHubPrimaryView.tsx +494 -0
- package/apps/octogent/apps/web/src/components/MonitorPrimaryView.tsx +475 -0
- package/apps/octogent/apps/web/src/components/PrimaryViewRouter.tsx +99 -0
- package/apps/octogent/apps/web/src/components/PromptsPrimaryView.tsx +243 -0
- package/apps/octogent/apps/web/src/components/RuntimeStatusStrip.tsx +273 -0
- package/apps/octogent/apps/web/src/components/SettingsPrimaryView.tsx +92 -0
- package/apps/octogent/apps/web/src/components/SidebarActionPanel.tsx +124 -0
- package/apps/octogent/apps/web/src/components/SidebarConversationsList.tsx +279 -0
- package/apps/octogent/apps/web/src/components/SidebarPromptsList.tsx +116 -0
- package/apps/octogent/apps/web/src/components/TelemetryTape.tsx +106 -0
- package/apps/octogent/apps/web/src/components/TentacleGitActionsDialog.tsx +341 -0
- package/apps/octogent/apps/web/src/components/Terminal.tsx +524 -0
- package/apps/octogent/apps/web/src/components/TerminalPromptPicker.tsx +140 -0
- package/apps/octogent/apps/web/src/components/UsageHeatmap.tsx +702 -0
- package/apps/octogent/apps/web/src/components/canvas/CanvasTentaclePanel.tsx +485 -0
- package/apps/octogent/apps/web/src/components/canvas/CanvasTerminalColumn.tsx +89 -0
- package/apps/octogent/apps/web/src/components/canvas/DeleteAllTerminalsDialog.tsx +221 -0
- package/apps/octogent/apps/web/src/components/canvas/OctopusNode.tsx +307 -0
- package/apps/octogent/apps/web/src/components/canvas/SessionNode.tsx +185 -0
- package/apps/octogent/apps/web/src/components/deck/ActionCards.tsx +118 -0
- package/apps/octogent/apps/web/src/components/deck/AddTentacleForm.tsx +269 -0
- package/apps/octogent/apps/web/src/components/deck/DeckBottomActions.tsx +56 -0
- package/apps/octogent/apps/web/src/components/deck/TentaclePod.tsx +334 -0
- package/apps/octogent/apps/web/src/components/deck/WorkspaceSetupCard.tsx +105 -0
- package/apps/octogent/apps/web/src/components/deck/octopusVisuals.ts +72 -0
- package/apps/octogent/apps/web/src/components/terminalReplay.ts +62 -0
- package/apps/octogent/apps/web/src/components/terminalWheel.ts +54 -0
- package/apps/octogent/apps/web/src/components/ui/ActionButton.tsx +34 -0
- package/apps/octogent/apps/web/src/components/ui/ConfirmationDialog.tsx +86 -0
- package/apps/octogent/apps/web/src/components/ui/MarkdownContent.tsx +43 -0
- package/apps/octogent/apps/web/src/components/ui/SettingsToggle.tsx +34 -0
- package/apps/octogent/apps/web/src/components/ui/StatusBadge.tsx +24 -0
- package/apps/octogent/apps/web/src/main.tsx +17 -0
- package/apps/octogent/apps/web/src/runtime/HttpTerminalSnapshotReader.ts +87 -0
- package/apps/octogent/apps/web/src/runtime/runtimeEndpoints.ts +412 -0
- package/apps/octogent/apps/web/src/styles/chrome-and-buttons.css +272 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-activity.css +358 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-canvas.css +1843 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-code-intel.css +227 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-conversations.css +705 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-deck.css +1524 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-github.css +541 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-monitor.css +595 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-pixpack.css +81 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-prompts.css +474 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-settings.css +207 -0
- package/apps/octogent/apps/web/src/styles/console-chrome-status-nav.css +441 -0
- package/apps/octogent/apps/web/src/styles/console-overrides-telemetry.css +320 -0
- package/apps/octogent/apps/web/src/styles/console-theme-tokens.css +25 -0
- package/apps/octogent/apps/web/src/styles/cortex-theme.css +412 -0
- package/apps/octogent/apps/web/src/styles/foundation.css +100 -0
- package/apps/octogent/apps/web/src/styles/sidebar-and-scrollbars.css +447 -0
- package/apps/octogent/apps/web/src/styles/terminal-and-status.css +356 -0
- package/apps/octogent/apps/web/src/styles.css +25 -0
- package/apps/octogent/apps/web/src/types/ws.d.ts +23 -0
- package/apps/octogent/apps/web/tests/CanvasPrimaryView.test.tsx +347 -0
- package/apps/octogent/apps/web/tests/HttpTerminalSnapshotReader.test.tsx +54 -0
- package/apps/octogent/apps/web/tests/RuntimeStatusStrip.test.tsx +70 -0
- package/apps/octogent/apps/web/tests/Terminal.test.tsx +87 -0
- package/apps/octogent/apps/web/tests/add-tentacle-form.test.tsx +48 -0
- package/apps/octogent/apps/web/tests/app-github-runtime.test.tsx +162 -0
- package/apps/octogent/apps/web/tests/app-monitor-runtime.test.tsx +657 -0
- package/apps/octogent/apps/web/tests/app-shell-navigation.test.tsx +109 -0
- package/apps/octogent/apps/web/tests/app-swarm-refresh.test.tsx +268 -0
- package/apps/octogent/apps/web/tests/app-ui-state-persistence.test.tsx +116 -0
- package/apps/octogent/apps/web/tests/app-workspace-setup.test.tsx +217 -0
- package/apps/octogent/apps/web/tests/canvas-tentacle-panel.test.tsx +195 -0
- package/apps/octogent/apps/web/tests/delete-all-terminals-dialog.test.tsx +76 -0
- package/apps/octogent/apps/web/tests/githubMetrics.test.tsx +52 -0
- package/apps/octogent/apps/web/tests/hotkeys.test.tsx +44 -0
- package/apps/octogent/apps/web/tests/runtimeEndpoints.test.tsx +240 -0
- package/apps/octogent/apps/web/tests/setup.ts +39 -0
- package/apps/octogent/apps/web/tests/tentacle-pod.test.tsx +62 -0
- package/apps/octogent/apps/web/tests/terminalReplay.test.ts +71 -0
- package/apps/octogent/apps/web/tests/terminalState.test.tsx +49 -0
- package/apps/octogent/apps/web/tests/terminalWheel.test.tsx +51 -0
- package/apps/octogent/apps/web/tests/test-utils/appTestHarness.ts +48 -0
- package/apps/octogent/apps/web/tests/uiPrimitives.test.tsx +31 -0
- package/apps/octogent/apps/web/tests/useAgentRuntimeStates.test.tsx +47 -0
- package/apps/octogent/apps/web/tsconfig.json +8 -0
- package/apps/octogent/apps/web/vite.api.bundle.config.mts +32 -0
- package/apps/octogent/apps/web/vite.config.ts +22 -0
- package/apps/octogent/bin/octogent +3 -0
- package/apps/octogent/biome.json +21 -0
- package/apps/octogent/docs/concepts/mental-model.md +79 -0
- package/apps/octogent/docs/concepts/runtime-and-api.md +60 -0
- package/apps/octogent/docs/concepts/tentacles.md +85 -0
- package/apps/octogent/docs/getting-started/installation.md +54 -0
- package/apps/octogent/docs/getting-started/quickstart.md +79 -0
- package/apps/octogent/docs/guides/inter-agent-messaging.md +43 -0
- package/apps/octogent/docs/guides/orchestrating-child-agents.md +49 -0
- package/apps/octogent/docs/guides/working-with-todos.md +56 -0
- package/apps/octogent/docs/index.md +40 -0
- package/apps/octogent/docs/reference/api.md +103 -0
- package/apps/octogent/docs/reference/cli.md +71 -0
- package/apps/octogent/docs/reference/experimental-features.md +28 -0
- package/apps/octogent/docs/reference/filesystem-layout.md +62 -0
- package/apps/octogent/docs/reference/troubleshooting.md +49 -0
- package/apps/octogent/package.json +35 -0
- package/apps/octogent/packages/core/AGENTS.md +31 -0
- package/apps/octogent/packages/core/package.json +12 -0
- package/apps/octogent/packages/core/src/adapters/InMemoryTerminalSnapshotReader.ts +10 -0
- package/apps/octogent/packages/core/src/application/buildTerminalList.ts +13 -0
- package/apps/octogent/packages/core/src/domain/agentRuntime.ts +18 -0
- package/apps/octogent/packages/core/src/domain/channel.ts +8 -0
- package/apps/octogent/packages/core/src/domain/completionSound.ts +14 -0
- package/apps/octogent/packages/core/src/domain/conversation.ts +48 -0
- package/apps/octogent/packages/core/src/domain/deck.ts +33 -0
- package/apps/octogent/packages/core/src/domain/git.ts +32 -0
- package/apps/octogent/packages/core/src/domain/monitor.ts +62 -0
- package/apps/octogent/packages/core/src/domain/setup.ts +27 -0
- package/apps/octogent/packages/core/src/domain/terminal.ts +17 -0
- package/apps/octogent/packages/core/src/domain/uiState.ts +22 -0
- package/apps/octogent/packages/core/src/domain/usage.ts +60 -0
- package/apps/octogent/packages/core/src/index.ts +15 -0
- package/apps/octogent/packages/core/src/ports/TerminalSnapshotReader.ts +5 -0
- package/apps/octogent/packages/core/src/util/typeCoercion.ts +20 -0
- package/apps/octogent/packages/core/tests/buildTerminalList.test.ts +75 -0
- package/apps/octogent/packages/core/tsconfig.json +7 -0
- package/apps/octogent/packages/core/tsconfig.tsbuildinfo +1 -0
- package/apps/octogent/packages/core/vitest.config.ts +7 -0
- package/apps/octogent/pnpm-lock.yaml +3212 -0
- package/apps/octogent/pnpm-workspace.yaml +3 -0
- package/apps/octogent/prompts/meta-prompt-generator.md +223 -0
- package/apps/octogent/prompts/octoboss-clean-contexts.md +30 -0
- package/apps/octogent/prompts/octoboss-reorganize-tentacles.md +29 -0
- package/apps/octogent/prompts/octoboss-reorganize-todos.md +27 -0
- package/apps/octogent/prompts/sandbox-init.md +3 -0
- package/apps/octogent/prompts/swarm-parent.md +83 -0
- package/apps/octogent/prompts/swarm-worker.md +50 -0
- package/apps/octogent/prompts/tentacle-context-init.md +1 -0
- package/apps/octogent/prompts/tentacle-planner.md +110 -0
- package/apps/octogent/prompts/tentacle-reorganize-todos.md +20 -0
- package/apps/octogent/prompts/tentacle-update-tentacle.md +18 -0
- package/apps/octogent/scripts/build-package.mjs +23 -0
- package/apps/octogent/scripts/dev.mjs +158 -0
- package/apps/octogent/scripts/smoke-public-install.mjs +271 -0
- package/apps/octogent/static/images/octogent-header.png +0 -0
- package/apps/octogent/static/images/preview_1.jpg +0 -0
- package/apps/octogent/static/images/preview_2.jpg +0 -0
- package/apps/octogent/static/images/preview_3.jpg +0 -0
- package/apps/octogent/static/images/preview_4.jpg +0 -0
- package/apps/octogent/static/images/preview_5.jpg +0 -0
- package/apps/octogent/static/images/preview_6.jpg +0 -0
- package/apps/octogent/tsconfig.base.json +16 -0
- package/bin/AGI +3 -0
- package/bin/AGI-install-app +71 -0
- package/bin/AGI-ui +16 -0
- package/bin/AGI-voice +15 -0
- package/bin/AGI-web +16 -0
- package/bin/cortex +109 -0
- package/bin/cortex-octogent +99 -0
- package/bin/import-specifier.mjs +13 -0
- package/bin/import-specifier.test.mjs +13 -0
- package/bin/octo +150 -0
- package/dist/cli.mjs +555650 -0
- package/package.json +157 -0
- package/scripts/setup-wizard.ts +390 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Pixel-art octopus rendered via Canvas 2D.
|
|
5
|
+
* Shape based on classic pixel ghost/invader: outlined dome, square eyes,
|
|
6
|
+
* jagged 3-tooth tentacle bottom.
|
|
7
|
+
*
|
|
8
|
+
* Sprite is 16 × 14 pixels. Canvas height is padded by BOUNCE_PAD rows
|
|
9
|
+
* so bounce/float animations have vertical room without clipping.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SCALE = 14; // CSS pixels per sprite pixel
|
|
13
|
+
const BOUNCE_PAD = 2; // extra canvas rows reserved for vertical animations
|
|
14
|
+
// Extra rows above the sprite for overlays (ZZZ, accessories).
|
|
15
|
+
const ZZZ_PAD = 7; // sleepy ZZZ needs 7 rows
|
|
16
|
+
const ACCESSORY_PAD = 4; // tallest hair (mohawk/curly) reaches row -3
|
|
17
|
+
const ZZZ_COLOR = "#7ec8e3"; // soft sky-blue for the floating z glyphs
|
|
18
|
+
const HAIR_COLOR = "#4a2c0a"; // dark brown
|
|
19
|
+
|
|
20
|
+
const B = "B"; // body (accent fill)
|
|
21
|
+
const O = "O"; // outline (dark)
|
|
22
|
+
const E = "E"; // eye (dark)
|
|
23
|
+
const _ = ""; // transparent
|
|
24
|
+
|
|
25
|
+
// ─── HEAD construction ───────────────────────────────────────────────────────
|
|
26
|
+
// Rows 0-2 and 6-9 are identical across all expressions.
|
|
27
|
+
// Rows 3-5 carry the expression detail; buildHead() assembles the full array.
|
|
28
|
+
|
|
29
|
+
// prettier-ignore
|
|
30
|
+
const HEAD_TOP: string[][] = [
|
|
31
|
+
[_, _, _, _, O, O, O, O, O, O, O, O, _, _, _, _], // 0
|
|
32
|
+
[_, _, _, O, B, B, B, B, B, B, B, B, O, _, _, _], // 1
|
|
33
|
+
[_, _, O, B, B, B, B, B, B, B, B, B, B, O, _, _], // 2
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// Angry variant — outer brow pixel at col 4 (left) and col 11 (right) in row 2.
|
|
37
|
+
// Combined with FACE_ANGRY row 3 inner pixels (col 5 / col 10), this forms a
|
|
38
|
+
// diagonal V-slash brow: outer-high → inner-low on each side.
|
|
39
|
+
// prettier-ignore
|
|
40
|
+
const HEAD_TOP_ANGRY: string[][] = [
|
|
41
|
+
[_, _, _, _, O, O, O, O, O, O, O, O, _, _, _, _], // 0
|
|
42
|
+
[_, _, _, O, B, B, B, B, B, B, B, B, O, _, _, _], // 1
|
|
43
|
+
[_, _, O, B, O, B, B, B, B, B, B, O, B, O, _, _], // 2 cols 4 and 11 → outer brow start
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// prettier-ignore
|
|
47
|
+
const HEAD_BODY: string[][] = [
|
|
48
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 6
|
|
49
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 7
|
|
50
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 8
|
|
51
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 9
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Happy — open mouth: solid black rectangle in rows 7-8, cols 5-10.
|
|
55
|
+
// prettier-ignore
|
|
56
|
+
const HEAD_BODY_HAPPY: string[][] = [
|
|
57
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 6
|
|
58
|
+
[_, O, B, B, B, O, O, O, O, O, O, O, B, B, O, _], // 7 top of open mouth (cols 5-10)
|
|
59
|
+
[_, O, B, B, B, O, O, O, O, O, O, O, B, B, O, _], // 8 bottom of open mouth (cols 5-10)
|
|
60
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 9
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Angry — open mouth, narrower than happy to read as a shout/snarl.
|
|
64
|
+
// prettier-ignore
|
|
65
|
+
const HEAD_BODY_ANGRY: string[][] = [
|
|
66
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 6
|
|
67
|
+
[_, O, B, B, B, B, O, O, O, O, O, B, B, B, O, _], // 7 top of open mouth (cols 6-10)
|
|
68
|
+
[_, O, B, B, B, B, O, O, O, O, O, B, B, B, O, _], // 8 bottom of open mouth (cols 6-10)
|
|
69
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 9
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// Normal — 2×2 square eyes (rows 4-5).
|
|
73
|
+
// prettier-ignore
|
|
74
|
+
const FACE_NORMAL: string[][] = [
|
|
75
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 3
|
|
76
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 4 eyes
|
|
77
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 eyes
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// Happy — upward-curved eyes (^_^ style): bottom row lit, top row clear.
|
|
81
|
+
// The open space above the pupil makes the eye read as curving upward = smile.
|
|
82
|
+
// prettier-ignore
|
|
83
|
+
const FACE_HAPPY: string[][] = [
|
|
84
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 3
|
|
85
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 4 clear = top of eye open
|
|
86
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 bottom row lit = eyes curve up
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
// Sleepy — heavy eyelid (solid outline stripe) with tiny pupils peeking below.
|
|
90
|
+
// prettier-ignore
|
|
91
|
+
const FACE_SLEEPY: string[][] = [
|
|
92
|
+
[_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 3
|
|
93
|
+
[_, O, B, B, O, O, B, B, B, B, O, O, B, B, O, _], // 4 closed eyelid (outline color)
|
|
94
|
+
[_, O, B, B, E, B, B, B, B, B, B, E, B, B, O, _], // 5 tiny pupils peeking
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Angry — brow diagonal continues: outer pixel lands at col 4/11 here too,
|
|
98
|
+
// making a 2-pixel-wide brow that reads clearly as a hard scowl.
|
|
99
|
+
// prettier-ignore
|
|
100
|
+
const FACE_ANGRY: string[][] = [
|
|
101
|
+
[_, O, B, O, O, B, B, B, B, B, B, O, O, B, O, _], // 3 both cols 3-4 left brow and 11-12 right brow
|
|
102
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 4 eyes
|
|
103
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 eyes
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Surprised — eyes extend up into row 3, making them taller (3-row tall eyes).
|
|
107
|
+
// prettier-ignore
|
|
108
|
+
const FACE_SURPRISED: string[][] = [
|
|
109
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 3 eyes start early
|
|
110
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 4 eyes
|
|
111
|
+
[_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 eyes
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
function buildHead(
|
|
115
|
+
face: string[][],
|
|
116
|
+
topRows: string[][] = HEAD_TOP,
|
|
117
|
+
bodyRows: string[][] = HEAD_BODY,
|
|
118
|
+
): string[][] {
|
|
119
|
+
return [...topRows, ...face, ...bodyRows];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Tentacle / tail variants ────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
// Static tentacle split — always drawn.
|
|
125
|
+
// prettier-ignore
|
|
126
|
+
const TENTACLE_TOP: string[][] = [
|
|
127
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 three equal splits
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
// 3-tooth rectangular bottom — neutral (square ghost-style bumps).
|
|
131
|
+
// prettier-ignore
|
|
132
|
+
const TAIL_NEUTRAL: string[][] = [
|
|
133
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11
|
|
134
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 12
|
|
135
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 13 bottom caps
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// Legs bend right — top row stays anchored, lower rows shift 1px right.
|
|
139
|
+
// prettier-ignore
|
|
140
|
+
const TAIL_RIGHT: string[][] = [
|
|
141
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 straight (pivot)
|
|
142
|
+
[_, _, O, B, B, O, _, O, B, B, O, _, O, B, B, O], // 12 bent 1px right
|
|
143
|
+
[_, _, _, O, O, _, _, _, O, O, _, _, _, O, O, _], // 13 caps follow bend
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
// Legs bend left — top row stays anchored, lower rows shift 1px left.
|
|
147
|
+
// prettier-ignore
|
|
148
|
+
const TAIL_LEFT: string[][] = [
|
|
149
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 straight (pivot)
|
|
150
|
+
[O, B, B, O, _, O, B, B, O, _, O, B, B, O, _, _], // 12 bent 1px left
|
|
151
|
+
[_, O, O, _, _, _, O, O, _, _, _, O, O, _, _, _], // 13 caps follow bend
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
// Sway: center → right → center → left → repeat
|
|
155
|
+
const SWAY_FRAMES_TAILS = [TAIL_NEUTRAL, TAIL_RIGHT, TAIL_NEUTRAL, TAIL_LEFT];
|
|
156
|
+
|
|
157
|
+
// Walk-up: all three legs extend and retract in unison.
|
|
158
|
+
// short (1 row + cap) → medium (2 rows + cap) → extended (3 rows + cap) → medium → repeat
|
|
159
|
+
// prettier-ignore
|
|
160
|
+
const WALKUP_0: string[][] = [
|
|
161
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
|
|
162
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 11 all cap
|
|
163
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 12 empty
|
|
164
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
165
|
+
];
|
|
166
|
+
// prettier-ignore
|
|
167
|
+
const WALKUP_1: string[][] = [
|
|
168
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
|
|
169
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 all continue
|
|
170
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 12 all cap
|
|
171
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
172
|
+
];
|
|
173
|
+
// prettier-ignore
|
|
174
|
+
const WALKUP_2: string[][] = [
|
|
175
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
|
|
176
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 all continue
|
|
177
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 12 all continue
|
|
178
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 13 all cap
|
|
179
|
+
];
|
|
180
|
+
// prettier-ignore
|
|
181
|
+
const WALKUP_3: string[][] = [
|
|
182
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
|
|
183
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 all continue
|
|
184
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 12 all cap
|
|
185
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
186
|
+
];
|
|
187
|
+
const WALKUP_FRAMES = [WALKUP_0, WALKUP_1, WALKUP_2, WALKUP_3];
|
|
188
|
+
|
|
189
|
+
// ─── Bounce / float types (needed by walk frames below) ─────────────────────
|
|
190
|
+
|
|
191
|
+
type SpriteFrame = {
|
|
192
|
+
bottom: string[][];
|
|
193
|
+
/** Shift the sprite down by this many pixels (0..BOUNCE_PAD). */
|
|
194
|
+
yOffset?: number;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Walk: lateral stepping — legs bend at a "knee" with feet kicking sideways.
|
|
198
|
+
// Outer legs (L/R) oppose the middle leg direction. Body bobs via yOffset.
|
|
199
|
+
|
|
200
|
+
// Neutral stance — all legs straight down, caps centered.
|
|
201
|
+
// prettier-ignore
|
|
202
|
+
const WALK_S0: string[][] = [
|
|
203
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
|
|
204
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 legs straight
|
|
205
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 12 caps centered
|
|
206
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
// Outer legs kick right, middle kicks left — knee narrows (OBO), cap follows.
|
|
210
|
+
// prettier-ignore
|
|
211
|
+
const WALK_S1: string[][] = [
|
|
212
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
|
|
213
|
+
[_, _, O, B, O, _, O, B, O, _, _, _, O, B, O, _], // 11 bent: L→R, M→L, R→R
|
|
214
|
+
[_, _, _, O, O, _, O, O, _, _, _, _, _, O, O, _], // 12 caps follow bend
|
|
215
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// Outer legs kick left, middle kicks right — mirror of S1.
|
|
219
|
+
// prettier-ignore
|
|
220
|
+
const WALK_S3: string[][] = [
|
|
221
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
|
|
222
|
+
[_, O, B, O, _, _, _, O, B, O, _, O, B, O, _, _], // 11 bent: L→L, M→R, R→L
|
|
223
|
+
[_, O, O, _, _, _, _, _, O, O, _, O, O, _, _, _], // 12 caps follow bend
|
|
224
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const JOG_FRAMES: SpriteFrame[] = [
|
|
228
|
+
{ bottom: WALK_S0, yOffset: 1 }, // neutral (dip)
|
|
229
|
+
{ bottom: WALK_S1, yOffset: 0 }, // step right
|
|
230
|
+
{ bottom: WALK_S0, yOffset: 1 }, // neutral (dip)
|
|
231
|
+
{ bottom: WALK_S3, yOffset: 0 }, // step left
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
// Walk: wave stride — motion ripples across tentacles left → right.
|
|
235
|
+
// Three leg states: neutral (straight), mid (knee bends, foot stays), bent (full step).
|
|
236
|
+
// The stepping leg cycles L → M → R. The leg before the stepper shows the mid state,
|
|
237
|
+
// creating a smooth wave: neutral → mid → bent → neutral.
|
|
238
|
+
|
|
239
|
+
// Frame 0: L=bent, M=neutral, R=mid (trailing from previous cycle)
|
|
240
|
+
// prettier-ignore
|
|
241
|
+
const WALK_WAVE_0: string[][] = [
|
|
242
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 upper legs
|
|
243
|
+
[_, _, _, B, B, _, _, B, B, _, _, _, _, B, B, _], // 11 L bent knee, M center, R mid knee
|
|
244
|
+
[_, _, _, O, B, O, O, B, B, O, _, O, B, B, O, _], // 12 L bent(narrow), M neutral, R mid(full)
|
|
245
|
+
[_, _, _, _, O, O, _, O, O, _, _, _, O, O, _, _], // 13 caps follow
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
// Frame 1: M=bent, L=mid (trailing), R=neutral
|
|
249
|
+
// prettier-ignore
|
|
250
|
+
const WALK_WAVE_1: string[][] = [
|
|
251
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 upper legs
|
|
252
|
+
[_, _, _, B, B, _, _, _, B, B, _, _, B, B, _, _], // 11 L mid knee, M bent knee, R center
|
|
253
|
+
[_, O, B, B, O, _, _, _, O, B, O, O, B, B, O, _], // 12 L mid(full), M bent(narrow), R neutral
|
|
254
|
+
[_, _, O, O, _, _, _, _, _, O, O, _, O, O, _, _], // 13 caps follow
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
// Frame 2: R=bent, M=mid (trailing), L=neutral
|
|
258
|
+
// prettier-ignore
|
|
259
|
+
const WALK_WAVE_2: string[][] = [
|
|
260
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 upper legs
|
|
261
|
+
[_, _, B, B, _, _, _, _, B, B, _, _, _, B, B, _], // 11 L center, M mid knee, R bent knee
|
|
262
|
+
[_, O, B, B, O, _, O, B, B, O, _, _, _, O, B, O], // 12 L neutral, M mid(full), R bent(narrow)
|
|
263
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, _, _, O, O], // 13 caps follow
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
const WALK_FRAMES: SpriteFrame[] = [
|
|
267
|
+
{ bottom: WALK_WAVE_0 },
|
|
268
|
+
{ bottom: WALK_WAVE_1 },
|
|
269
|
+
{ bottom: WALK_WAVE_2 },
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
// ─── Bounce / float animations ───────────────────────────────────────────────
|
|
273
|
+
// Canvas height includes BOUNCE_PAD extra rows; yOffset shifts the sprite down
|
|
274
|
+
// so it can move upward without clipping. Sequence: squat → rise → apex → fall.
|
|
275
|
+
|
|
276
|
+
const BOUNCE_STRAIGHT = [...TENTACLE_TOP, ...TAIL_NEUTRAL];
|
|
277
|
+
|
|
278
|
+
// Crouch — outer legs splay outward (L bends left, R bends right), coiling to jump.
|
|
279
|
+
// prettier-ignore
|
|
280
|
+
const BOUNCE_CROUCH: string[][] = [
|
|
281
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
|
|
282
|
+
[_, O, B, O, _, _, O, B, B, O, _, _, O, B, O, _], // 11 L→left, M straight, R→right
|
|
283
|
+
[_, O, O, _, _, _, _, O, O, _, _, _, _, O, O, _], // 12 caps follow splay
|
|
284
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// Apex — legs tuck short (retracted), airborne.
|
|
288
|
+
// prettier-ignore
|
|
289
|
+
const BOUNCE_TUCKED: string[][] = [
|
|
290
|
+
[_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
|
|
291
|
+
[_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 11 caps only (short legs)
|
|
292
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 12 empty
|
|
293
|
+
[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
const BOUNCE_FRAMES: SpriteFrame[] = [
|
|
297
|
+
{ bottom: BOUNCE_CROUCH, yOffset: 2 }, // crouch (splay + low)
|
|
298
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 1 }, // launch (straighten + rise)
|
|
299
|
+
{ bottom: BOUNCE_TUCKED, yOffset: 0 }, // apex (tucked + highest)
|
|
300
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 1 }, // fall (straight + descend)
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
// Float: slow buoyancy — dwell longer at top and bottom.
|
|
304
|
+
const FLOAT_FRAMES: SpriteFrame[] = [
|
|
305
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 0 },
|
|
306
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 0 },
|
|
307
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 1 },
|
|
308
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 2 },
|
|
309
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 2 },
|
|
310
|
+
{ bottom: BOUNCE_STRAIGHT, yOffset: 1 },
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
// ─── Frame timing ────────────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
const JOG_FRAME_MS = 220;
|
|
316
|
+
const WALK_FRAME_MS = 320;
|
|
317
|
+
const SWAY_FRAME_MS = 350;
|
|
318
|
+
const FLOAT_FRAME_MS = 420;
|
|
319
|
+
|
|
320
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
const SPRITE_W = 16;
|
|
323
|
+
// HEAD_TOP(3) + face(3) + HEAD_BODY(4) + TENTACLE_TOP(1) + TAIL_NEUTRAL(3) = 14
|
|
324
|
+
const SPRITE_H =
|
|
325
|
+
HEAD_TOP.length +
|
|
326
|
+
FACE_NORMAL.length +
|
|
327
|
+
HEAD_BODY.length +
|
|
328
|
+
TENTACLE_TOP.length +
|
|
329
|
+
TAIL_NEUTRAL.length;
|
|
330
|
+
|
|
331
|
+
export type OctopusAnimation = "idle" | "sway" | "walk" | "jog" | "swim-up" | "bounce" | "float";
|
|
332
|
+
// "sleepy" is reserved for idle/inactive tentacles — never assign it randomly on creation.
|
|
333
|
+
export type OctopusExpression = "normal" | "happy" | "sleepy" | "angry" | "surprised";
|
|
334
|
+
export type OctopusAccessory = "none" | "long" | "mohawk" | "side-sweep" | "curly";
|
|
335
|
+
|
|
336
|
+
// ─── Accessories ──────────────────────────────────────────────────────────────
|
|
337
|
+
// Drawn as smooth vector shapes on the canvas (not pixel art) so they look
|
|
338
|
+
// good at any scale. All drawing is relative to the dome center/top.
|
|
339
|
+
|
|
340
|
+
const HEADS: Record<OctopusExpression, string[][]> = {
|
|
341
|
+
normal: buildHead(FACE_NORMAL),
|
|
342
|
+
happy: buildHead(FACE_HAPPY, HEAD_TOP, HEAD_BODY_HAPPY),
|
|
343
|
+
sleepy: buildHead(FACE_SLEEPY),
|
|
344
|
+
angry: buildHead(FACE_ANGRY, HEAD_TOP_ANGRY, HEAD_BODY_ANGRY),
|
|
345
|
+
surprised: buildHead(FACE_SURPRISED),
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// ─── Drawing ─────────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
function drawSprite(
|
|
351
|
+
ctx: CanvasRenderingContext2D,
|
|
352
|
+
accentColor: string,
|
|
353
|
+
frame: SpriteFrame,
|
|
354
|
+
head: string[][],
|
|
355
|
+
scale: number,
|
|
356
|
+
topPad: number,
|
|
357
|
+
) {
|
|
358
|
+
ctx.clearRect(0, 0, SPRITE_W * scale, (topPad + SPRITE_H + BOUNCE_PAD) * scale);
|
|
359
|
+
|
|
360
|
+
const yOff = (frame.yOffset ?? 0) + topPad;
|
|
361
|
+
const layers = [...head, ...frame.bottom];
|
|
362
|
+
for (let y = 0; y < layers.length; y++) {
|
|
363
|
+
const row = layers[y];
|
|
364
|
+
if (!row) continue;
|
|
365
|
+
for (let x = 0; x < row.length; x++) {
|
|
366
|
+
const cell = row[x];
|
|
367
|
+
if (!cell) continue;
|
|
368
|
+
ctx.fillStyle = cell === E || cell === O ? "#000000" : accentColor;
|
|
369
|
+
ctx.fillRect(x * scale, (y + yOff) * scale, scale, scale);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Three 3×5 Z glyphs staggered rising right→left above the sprite.
|
|
375
|
+
// Each Z: top-bar / 3-step diagonal (top-right→bottom-left) / bottom-bar.
|
|
376
|
+
// Phase 0: Z1 only · Phase 1: all three · Phase 2-3: hidden.
|
|
377
|
+
function drawZZZ(ctx: CanvasRenderingContext2D, scale: number, zzzPhase: number) {
|
|
378
|
+
if (zzzPhase >= 3) return; // phases 3-4 are the blank pause
|
|
379
|
+
|
|
380
|
+
ctx.fillStyle = ZZZ_COLOR;
|
|
381
|
+
|
|
382
|
+
// Z1 — lowest, right side. Rows 2-6, cols 13-15.
|
|
383
|
+
// prettier-ignore
|
|
384
|
+
const Z1: Array<[number, number]> = [
|
|
385
|
+
[13, 2],
|
|
386
|
+
[14, 2],
|
|
387
|
+
[15, 2],
|
|
388
|
+
[15, 3],
|
|
389
|
+
[14, 4],
|
|
390
|
+
[13, 5],
|
|
391
|
+
[13, 6],
|
|
392
|
+
[14, 6],
|
|
393
|
+
[15, 6],
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
// Z2 — middle height. Rows 1-5, cols 9-11.
|
|
397
|
+
// prettier-ignore
|
|
398
|
+
const Z2: Array<[number, number]> = [
|
|
399
|
+
[9, 1],
|
|
400
|
+
[10, 1],
|
|
401
|
+
[11, 1],
|
|
402
|
+
[11, 2],
|
|
403
|
+
[10, 3],
|
|
404
|
+
[9, 4],
|
|
405
|
+
[9, 5],
|
|
406
|
+
[10, 5],
|
|
407
|
+
[11, 5],
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
// Z3 — highest, left side. Rows 0-4, cols 5-7.
|
|
411
|
+
// prettier-ignore
|
|
412
|
+
const Z3: Array<[number, number]> = [
|
|
413
|
+
[5, 0],
|
|
414
|
+
[6, 0],
|
|
415
|
+
[7, 0],
|
|
416
|
+
[7, 1],
|
|
417
|
+
[6, 2],
|
|
418
|
+
[5, 3],
|
|
419
|
+
[5, 4],
|
|
420
|
+
[6, 4],
|
|
421
|
+
[7, 4],
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
const pixels = zzzPhase === 0 ? Z1 : zzzPhase === 1 ? [...Z1, ...Z2] : [...Z1, ...Z2, ...Z3];
|
|
425
|
+
for (const [x, y] of pixels) {
|
|
426
|
+
ctx.fillRect(x * scale, y * scale, scale, scale);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Draw accessory as smooth vector shapes on top of the pixel sprite.
|
|
431
|
+
// Dome top-center is at sprite pixel (8, 0) — used as anchor for all accessories.
|
|
432
|
+
function drawAccessory(
|
|
433
|
+
ctx: CanvasRenderingContext2D,
|
|
434
|
+
accessory: OctopusAccessory,
|
|
435
|
+
scale: number,
|
|
436
|
+
yOff: number,
|
|
437
|
+
hColor: string,
|
|
438
|
+
) {
|
|
439
|
+
if (accessory === "none") return;
|
|
440
|
+
|
|
441
|
+
// Dome geometry in canvas pixels
|
|
442
|
+
const domeL = 4 * scale; // dome outline left edge (col 4)
|
|
443
|
+
const domeR = 12 * scale; // dome outline right edge (col 12)
|
|
444
|
+
const domeCX = 8 * scale; // dome center x
|
|
445
|
+
const domeTop = yOff * scale; // dome top y (row 0 of sprite)
|
|
446
|
+
const domeW = domeR - domeL;
|
|
447
|
+
|
|
448
|
+
ctx.save();
|
|
449
|
+
ctx.fillStyle = hColor;
|
|
450
|
+
|
|
451
|
+
switch (accessory) {
|
|
452
|
+
case "long": {
|
|
453
|
+
// Long hair — dome cap, two wide straight strands, zigzag bangs with center part.
|
|
454
|
+
// Shaped like the pixel-art wig: covers top, frames face, strands reach tentacle area.
|
|
455
|
+
const hairTop = domeTop - scale * 1.5;
|
|
456
|
+
const strandEnd = domeTop + scale * 10.5;
|
|
457
|
+
const bangY = domeTop + scale * 2.5; // bang line, just above face/eyes
|
|
458
|
+
// Strand edges — inner edges align with dome outline
|
|
459
|
+
const lOut = scale * 0.5;
|
|
460
|
+
const lIn = domeL; // col 4
|
|
461
|
+
const rIn = domeR; // col 12
|
|
462
|
+
const rOut = scale * 15.5;
|
|
463
|
+
|
|
464
|
+
// Single path for the entire hair shape
|
|
465
|
+
ctx.beginPath();
|
|
466
|
+
// Top center
|
|
467
|
+
ctx.moveTo(domeCX, hairTop);
|
|
468
|
+
// Arc over to left
|
|
469
|
+
ctx.quadraticCurveTo(lOut, hairTop, lOut, domeTop + scale * 2);
|
|
470
|
+
// Left strand straight down
|
|
471
|
+
ctx.lineTo(lOut, strandEnd);
|
|
472
|
+
// Left strand bottom
|
|
473
|
+
ctx.lineTo(lIn, strandEnd);
|
|
474
|
+
// Left inner edge up to bangs
|
|
475
|
+
ctx.lineTo(lIn, bangY);
|
|
476
|
+
// Bangs — zigzag W with center part
|
|
477
|
+
ctx.lineTo(lIn + scale * 1.5, bangY + scale * 1.8);
|
|
478
|
+
ctx.lineTo(domeCX - scale * 0.5, bangY + scale * 0.5);
|
|
479
|
+
ctx.lineTo(domeCX, bangY + scale * 1.2); // center part dip
|
|
480
|
+
ctx.lineTo(domeCX + scale * 0.5, bangY + scale * 0.5);
|
|
481
|
+
ctx.lineTo(rIn - scale * 1.5, bangY + scale * 1.8);
|
|
482
|
+
// Right inner edge from bangs down
|
|
483
|
+
ctx.lineTo(rIn, bangY);
|
|
484
|
+
ctx.lineTo(rIn, strandEnd);
|
|
485
|
+
// Right strand bottom
|
|
486
|
+
ctx.lineTo(rOut, strandEnd);
|
|
487
|
+
// Right strand straight up
|
|
488
|
+
ctx.lineTo(rOut, domeTop + scale * 2);
|
|
489
|
+
// Arc back to top center
|
|
490
|
+
ctx.quadraticCurveTo(rOut, hairTop, domeCX, hairTop);
|
|
491
|
+
ctx.closePath();
|
|
492
|
+
|
|
493
|
+
// Outline first (drawn behind fill via stroke order)
|
|
494
|
+
ctx.strokeStyle = "rgba(0,0,0,0.6)";
|
|
495
|
+
ctx.lineWidth = scale * 0.6;
|
|
496
|
+
ctx.stroke();
|
|
497
|
+
// Fill on top
|
|
498
|
+
ctx.fill();
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case "mohawk": {
|
|
502
|
+
// Spiky ridge along the dome center — three pointed triangles.
|
|
503
|
+
|
|
504
|
+
const baseY = domeTop + scale * 0.3;
|
|
505
|
+
const spikes: Array<[number, number, number]> = [
|
|
506
|
+
[domeCX - domeW * 0.15, domeTop - scale * 2, domeW * 0.2], // left spike
|
|
507
|
+
[domeCX, domeTop - scale * 3.2, domeW * 0.22], // center spike (tallest)
|
|
508
|
+
[domeCX + domeW * 0.18, domeTop - scale * 2.2, domeW * 0.2], // right spike
|
|
509
|
+
];
|
|
510
|
+
for (const [cx, tipY, halfW] of spikes) {
|
|
511
|
+
ctx.beginPath();
|
|
512
|
+
ctx.moveTo(cx - halfW, baseY);
|
|
513
|
+
ctx.lineTo(cx, tipY);
|
|
514
|
+
ctx.lineTo(cx + halfW, baseY);
|
|
515
|
+
ctx.closePath();
|
|
516
|
+
ctx.fill();
|
|
517
|
+
}
|
|
518
|
+
// Base strip connecting the spikes
|
|
519
|
+
ctx.beginPath();
|
|
520
|
+
ctx.ellipse(domeCX, baseY, domeW * 0.35, scale * 0.8, 0, 0, Math.PI * 2);
|
|
521
|
+
ctx.fill();
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
case "side-sweep": {
|
|
525
|
+
// Asymmetric bangs flowing to the left.
|
|
526
|
+
|
|
527
|
+
ctx.beginPath();
|
|
528
|
+
// Start from right side of dome
|
|
529
|
+
ctx.moveTo(domeCX + domeW * 0.2, domeTop + scale * 0.5);
|
|
530
|
+
// Sweep up and over to the left
|
|
531
|
+
ctx.quadraticCurveTo(domeCX, domeTop - scale * 2, domeL - scale * 1.5, domeTop - scale * 0.5);
|
|
532
|
+
// Bang tip curves down
|
|
533
|
+
ctx.quadraticCurveTo(
|
|
534
|
+
domeL - scale * 2,
|
|
535
|
+
domeTop + scale * 1.5,
|
|
536
|
+
domeL - scale * 1,
|
|
537
|
+
domeTop + scale * 3,
|
|
538
|
+
);
|
|
539
|
+
// Curve back along the dome edge
|
|
540
|
+
ctx.quadraticCurveTo(domeL - scale * 0.2, domeTop + scale * 2, domeL, domeTop + scale * 0.5);
|
|
541
|
+
// Follow dome top back to start
|
|
542
|
+
ctx.quadraticCurveTo(
|
|
543
|
+
domeCX,
|
|
544
|
+
domeTop + scale * 0.2,
|
|
545
|
+
domeCX + domeW * 0.2,
|
|
546
|
+
domeTop + scale * 0.5,
|
|
547
|
+
);
|
|
548
|
+
ctx.closePath();
|
|
549
|
+
ctx.fill();
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
case "curly": {
|
|
553
|
+
// Curly poof — many small bumpy circles forming a textured cloud.
|
|
554
|
+
// Smaller radius so individual curls are visible, packed densely.
|
|
555
|
+
const r = domeW * 0.18;
|
|
556
|
+
const centers: Array<[number, number]> = [
|
|
557
|
+
// Bottom row — wide, covers head sides
|
|
558
|
+
[domeCX - domeW * 0.5, domeTop + scale * 1.2],
|
|
559
|
+
[domeCX - domeW * 0.25, domeTop + scale * 1.2],
|
|
560
|
+
[domeCX, domeTop + scale * 1.2],
|
|
561
|
+
[domeCX + domeW * 0.25, domeTop + scale * 1.2],
|
|
562
|
+
[domeCX + domeW * 0.5, domeTop + scale * 1.2],
|
|
563
|
+
// Row 2
|
|
564
|
+
[domeCX - domeW * 0.5, domeTop + scale * 0.3],
|
|
565
|
+
[domeCX - domeW * 0.2, domeTop + scale * 0.3],
|
|
566
|
+
[domeCX + domeW * 0.2, domeTop + scale * 0.3],
|
|
567
|
+
[domeCX + domeW * 0.5, domeTop + scale * 0.3],
|
|
568
|
+
// Row 3
|
|
569
|
+
[domeCX - domeW * 0.4, domeTop - scale * 0.3],
|
|
570
|
+
[domeCX - domeW * 0.12, domeTop - scale * 0.3],
|
|
571
|
+
[domeCX + domeW * 0.12, domeTop - scale * 0.3],
|
|
572
|
+
[domeCX + domeW * 0.4, domeTop - scale * 0.3],
|
|
573
|
+
// Row 3
|
|
574
|
+
[domeCX - domeW * 0.3, domeTop - scale * 1.1],
|
|
575
|
+
[domeCX, domeTop - scale * 1.1],
|
|
576
|
+
[domeCX + domeW * 0.3, domeTop - scale * 1.1],
|
|
577
|
+
// Row 4
|
|
578
|
+
[domeCX - domeW * 0.18, domeTop - scale * 1.8],
|
|
579
|
+
[domeCX + domeW * 0.18, domeTop - scale * 1.8],
|
|
580
|
+
// Top
|
|
581
|
+
[domeCX, domeTop - scale * 2.4],
|
|
582
|
+
];
|
|
583
|
+
for (const [cx, cy] of centers) {
|
|
584
|
+
ctx.beginPath();
|
|
585
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
586
|
+
ctx.fill();
|
|
587
|
+
}
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
ctx.restore();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ─── Animation builder ───────────────────────────────────────────────────────
|
|
596
|
+
|
|
597
|
+
function buildFrameSequence(animation: OctopusAnimation): SpriteFrame[] {
|
|
598
|
+
switch (animation) {
|
|
599
|
+
case "swim-up":
|
|
600
|
+
return WALKUP_FRAMES.map((bottom) => ({ bottom }));
|
|
601
|
+
case "walk":
|
|
602
|
+
return WALK_FRAMES;
|
|
603
|
+
case "jog":
|
|
604
|
+
return JOG_FRAMES;
|
|
605
|
+
case "bounce":
|
|
606
|
+
return BOUNCE_FRAMES;
|
|
607
|
+
case "float":
|
|
608
|
+
return FLOAT_FRAMES;
|
|
609
|
+
default:
|
|
610
|
+
return SWAY_FRAMES_TAILS.map((tail) => ({ bottom: [...TENTACLE_TOP, ...tail] }));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function animationFrameMs(animation: OctopusAnimation): number {
|
|
615
|
+
if (animation === "jog" || animation === "swim-up") return JOG_FRAME_MS;
|
|
616
|
+
if (animation === "walk") return WALK_FRAME_MS;
|
|
617
|
+
if (animation === "float") return FLOAT_FRAME_MS;
|
|
618
|
+
return SWAY_FRAME_MS;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const IDLE_FRAME: SpriteFrame = { bottom: [...TENTACLE_TOP, ...TAIL_NEUTRAL] };
|
|
622
|
+
|
|
623
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
624
|
+
|
|
625
|
+
type OctopusGlyphProps = {
|
|
626
|
+
animation?: OctopusAnimation;
|
|
627
|
+
expression?: OctopusExpression;
|
|
628
|
+
accessory?: OctopusAccessory;
|
|
629
|
+
/** Hair color override. Default: dark brown. */
|
|
630
|
+
hairColor?: string;
|
|
631
|
+
/** Override the pixel scale (CSS px per sprite pixel). Default: 14. */
|
|
632
|
+
scale?: number;
|
|
633
|
+
className?: string;
|
|
634
|
+
color?: string;
|
|
635
|
+
testId?: string;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
export const OctopusGlyph = ({
|
|
639
|
+
animation = "sway",
|
|
640
|
+
expression = "normal",
|
|
641
|
+
accessory = "none",
|
|
642
|
+
hairColor = HAIR_COLOR,
|
|
643
|
+
scale = DEFAULT_SCALE,
|
|
644
|
+
className,
|
|
645
|
+
color,
|
|
646
|
+
testId,
|
|
647
|
+
}: OctopusGlyphProps) => {
|
|
648
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
649
|
+
const frameRef = useRef(0);
|
|
650
|
+
const zzzPhaseRef = useRef(0);
|
|
651
|
+
|
|
652
|
+
// Extra canvas rows above the sprite for overlays (ZZZ, accessories).
|
|
653
|
+
const topPad = Math.max(
|
|
654
|
+
expression === "sleepy" ? ZZZ_PAD : 0,
|
|
655
|
+
accessory !== "none" ? ACCESSORY_PAD : 0,
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
useEffect(() => {
|
|
659
|
+
const canvas = canvasRef.current;
|
|
660
|
+
if (!canvas) return;
|
|
661
|
+
const ctx = canvas.getContext("2d");
|
|
662
|
+
if (!ctx) return;
|
|
663
|
+
|
|
664
|
+
ctx.imageSmoothingEnabled = false;
|
|
665
|
+
|
|
666
|
+
const accentColor =
|
|
667
|
+
color ??
|
|
668
|
+
(getComputedStyle(document.documentElement).getPropertyValue("--accent-primary").trim() ||
|
|
669
|
+
"#d4a017");
|
|
670
|
+
|
|
671
|
+
const head = HEADS[expression];
|
|
672
|
+
|
|
673
|
+
const drawFrame = (frame: SpriteFrame, zzzPhase: number) => {
|
|
674
|
+
drawSprite(ctx, accentColor, frame, head, scale, topPad);
|
|
675
|
+
if (expression === "sleepy") drawZZZ(ctx, scale, zzzPhase);
|
|
676
|
+
const yOff = (frame.yOffset ?? 0) + topPad;
|
|
677
|
+
drawAccessory(ctx, accessory, scale, yOff, hairColor);
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// Idle with no ZZZ: static, no interval.
|
|
681
|
+
if (animation === "idle" && expression !== "sleepy") {
|
|
682
|
+
frameRef.current = 0;
|
|
683
|
+
drawFrame(IDLE_FRAME, 0);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Idle sleepy: sprite is static but ZZZ blinks — use sway timing for ZZZ cycle.
|
|
688
|
+
const frames = animation === "idle" ? null : buildFrameSequence(animation);
|
|
689
|
+
const ms = animation === "idle" ? SWAY_FRAME_MS : animationFrameMs(animation);
|
|
690
|
+
|
|
691
|
+
frameRef.current = 0;
|
|
692
|
+
zzzPhaseRef.current = 0;
|
|
693
|
+
drawFrame(frames?.[0] ?? IDLE_FRAME, 0);
|
|
694
|
+
|
|
695
|
+
const id = setInterval(() => {
|
|
696
|
+
if (frames) {
|
|
697
|
+
frameRef.current = (frameRef.current + 1) % frames.length;
|
|
698
|
+
}
|
|
699
|
+
zzzPhaseRef.current = (zzzPhaseRef.current + 1) % 5;
|
|
700
|
+
drawFrame(frames?.[frameRef.current] ?? IDLE_FRAME, zzzPhaseRef.current);
|
|
701
|
+
}, ms);
|
|
702
|
+
|
|
703
|
+
return () => clearInterval(id);
|
|
704
|
+
}, [animation, expression, accessory, hairColor, color, scale, topPad]);
|
|
705
|
+
|
|
706
|
+
return (
|
|
707
|
+
<canvas
|
|
708
|
+
ref={canvasRef}
|
|
709
|
+
className={className}
|
|
710
|
+
width={SPRITE_W * scale}
|
|
711
|
+
height={(topPad + SPRITE_H + BOUNCE_PAD) * scale}
|
|
712
|
+
data-testid={testId}
|
|
713
|
+
/>
|
|
714
|
+
);
|
|
715
|
+
};
|