codex-partner 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. app.py +5530 -0
  2. codex_partner/__init__.py +4 -0
  3. codex_partner/__main__.py +6 -0
  4. codex_partner/app_server.py +150 -0
  5. codex_partner/cli.py +35 -0
  6. codex_partner/commands.py +77 -0
  7. codex_partner/database.py +206 -0
  8. codex_partner/discovery.py +136 -0
  9. codex_partner/schemas.py +133 -0
  10. codex_partner-0.0.1.dist-info/METADATA +101 -0
  11. codex_partner-0.0.1.dist-info/RECORD +244 -0
  12. codex_partner-0.0.1.dist-info/WHEEL +5 -0
  13. codex_partner-0.0.1.dist-info/entry_points.txt +2 -0
  14. codex_partner-0.0.1.dist-info/top_level.txt +3 -0
  15. static/__init__.py +1 -0
  16. static/app.js +505 -0
  17. static/chat-worker.js +157 -0
  18. static/conversation.js +894 -0
  19. static/core.js +880 -0
  20. static/editor.js +142 -0
  21. static/index.html +71 -0
  22. static/mascot-dance.css +100 -0
  23. static/mascot-dance.js +46 -0
  24. static/settings.js +335 -0
  25. static/styles.css +690 -0
  26. static/vendor/ace/LICENSE +24 -0
  27. static/vendor/ace/ace.js +23 -0
  28. static/vendor/ace/ext-modelist.js +8 -0
  29. static/vendor/ace/mode-abap.js +8 -0
  30. static/vendor/ace/mode-abc.js +8 -0
  31. static/vendor/ace/mode-actionscript.js +8 -0
  32. static/vendor/ace/mode-ada.js +8 -0
  33. static/vendor/ace/mode-alda.js +8 -0
  34. static/vendor/ace/mode-apache_conf.js +8 -0
  35. static/vendor/ace/mode-apex.js +8 -0
  36. static/vendor/ace/mode-applescript.js +8 -0
  37. static/vendor/ace/mode-aql.js +8 -0
  38. static/vendor/ace/mode-asciidoc.js +8 -0
  39. static/vendor/ace/mode-asl.js +8 -0
  40. static/vendor/ace/mode-assembly_arm32.js +8 -0
  41. static/vendor/ace/mode-assembly_x86.js +8 -0
  42. static/vendor/ace/mode-astro.js +8 -0
  43. static/vendor/ace/mode-autohotkey.js +8 -0
  44. static/vendor/ace/mode-basic.js +8 -0
  45. static/vendor/ace/mode-batchfile.js +8 -0
  46. static/vendor/ace/mode-bibtex.js +8 -0
  47. static/vendor/ace/mode-c9search.js +8 -0
  48. static/vendor/ace/mode-c_cpp.js +8 -0
  49. static/vendor/ace/mode-cedar.js +8 -0
  50. static/vendor/ace/mode-cedarschema.js +8 -0
  51. static/vendor/ace/mode-cirru.js +8 -0
  52. static/vendor/ace/mode-clojure.js +8 -0
  53. static/vendor/ace/mode-clue.js +8 -0
  54. static/vendor/ace/mode-cobol.js +8 -0
  55. static/vendor/ace/mode-coffee.js +8 -0
  56. static/vendor/ace/mode-coldfusion.js +8 -0
  57. static/vendor/ace/mode-crystal.js +8 -0
  58. static/vendor/ace/mode-csharp.js +8 -0
  59. static/vendor/ace/mode-csound_document.js +8 -0
  60. static/vendor/ace/mode-csound_orchestra.js +8 -0
  61. static/vendor/ace/mode-csound_score.js +8 -0
  62. static/vendor/ace/mode-csp.js +8 -0
  63. static/vendor/ace/mode-css.js +8 -0
  64. static/vendor/ace/mode-csv.js +8 -0
  65. static/vendor/ace/mode-curly.js +8 -0
  66. static/vendor/ace/mode-cuttlefish.js +8 -0
  67. static/vendor/ace/mode-d.js +8 -0
  68. static/vendor/ace/mode-dart.js +8 -0
  69. static/vendor/ace/mode-diff.js +8 -0
  70. static/vendor/ace/mode-django.js +8 -0
  71. static/vendor/ace/mode-dockerfile.js +8 -0
  72. static/vendor/ace/mode-dot.js +8 -0
  73. static/vendor/ace/mode-drools.js +8 -0
  74. static/vendor/ace/mode-edifact.js +8 -0
  75. static/vendor/ace/mode-eiffel.js +8 -0
  76. static/vendor/ace/mode-ejs.js +8 -0
  77. static/vendor/ace/mode-elixir.js +8 -0
  78. static/vendor/ace/mode-elm.js +8 -0
  79. static/vendor/ace/mode-erlang.js +8 -0
  80. static/vendor/ace/mode-flix.js +8 -0
  81. static/vendor/ace/mode-forth.js +8 -0
  82. static/vendor/ace/mode-fortran.js +8 -0
  83. static/vendor/ace/mode-fsharp.js +8 -0
  84. static/vendor/ace/mode-fsl.js +8 -0
  85. static/vendor/ace/mode-ftl.js +8 -0
  86. static/vendor/ace/mode-gcode.js +8 -0
  87. static/vendor/ace/mode-gherkin.js +8 -0
  88. static/vendor/ace/mode-gitignore.js +8 -0
  89. static/vendor/ace/mode-glsl.js +8 -0
  90. static/vendor/ace/mode-gobstones.js +8 -0
  91. static/vendor/ace/mode-golang.js +8 -0
  92. static/vendor/ace/mode-graphqlschema.js +8 -0
  93. static/vendor/ace/mode-groovy.js +8 -0
  94. static/vendor/ace/mode-groq.js +8 -0
  95. static/vendor/ace/mode-haml.js +8 -0
  96. static/vendor/ace/mode-handlebars.js +8 -0
  97. static/vendor/ace/mode-haskell.js +8 -0
  98. static/vendor/ace/mode-haskell_cabal.js +8 -0
  99. static/vendor/ace/mode-haxe.js +8 -0
  100. static/vendor/ace/mode-hjson.js +8 -0
  101. static/vendor/ace/mode-html.js +8 -0
  102. static/vendor/ace/mode-html_elixir.js +8 -0
  103. static/vendor/ace/mode-html_ruby.js +8 -0
  104. static/vendor/ace/mode-ini.js +8 -0
  105. static/vendor/ace/mode-io.js +8 -0
  106. static/vendor/ace/mode-ion.js +8 -0
  107. static/vendor/ace/mode-jack.js +8 -0
  108. static/vendor/ace/mode-jade.js +8 -0
  109. static/vendor/ace/mode-java.js +8 -0
  110. static/vendor/ace/mode-javascript.js +8 -0
  111. static/vendor/ace/mode-jexl.js +8 -0
  112. static/vendor/ace/mode-json.js +8 -0
  113. static/vendor/ace/mode-json5.js +8 -0
  114. static/vendor/ace/mode-jsoniq.js +8 -0
  115. static/vendor/ace/mode-jsp.js +8 -0
  116. static/vendor/ace/mode-jssm.js +8 -0
  117. static/vendor/ace/mode-jsx.js +8 -0
  118. static/vendor/ace/mode-julia.js +8 -0
  119. static/vendor/ace/mode-kotlin.js +8 -0
  120. static/vendor/ace/mode-latex.js +8 -0
  121. static/vendor/ace/mode-latte.js +8 -0
  122. static/vendor/ace/mode-less.js +8 -0
  123. static/vendor/ace/mode-liquid.js +8 -0
  124. static/vendor/ace/mode-lisp.js +8 -0
  125. static/vendor/ace/mode-livescript.js +8 -0
  126. static/vendor/ace/mode-logiql.js +8 -0
  127. static/vendor/ace/mode-logtalk.js +8 -0
  128. static/vendor/ace/mode-lsl.js +8 -0
  129. static/vendor/ace/mode-lua.js +8 -0
  130. static/vendor/ace/mode-luapage.js +8 -0
  131. static/vendor/ace/mode-lucene.js +8 -0
  132. static/vendor/ace/mode-makefile.js +8 -0
  133. static/vendor/ace/mode-mariadb.js +8 -0
  134. static/vendor/ace/mode-markdown.js +8 -0
  135. static/vendor/ace/mode-mask.js +8 -0
  136. static/vendor/ace/mode-matlab.js +8 -0
  137. static/vendor/ace/mode-maze.js +8 -0
  138. static/vendor/ace/mode-mediawiki.js +8 -0
  139. static/vendor/ace/mode-mel.js +8 -0
  140. static/vendor/ace/mode-mips.js +8 -0
  141. static/vendor/ace/mode-mixal.js +8 -0
  142. static/vendor/ace/mode-mushcode.js +8 -0
  143. static/vendor/ace/mode-mysql.js +8 -0
  144. static/vendor/ace/mode-nasal.js +8 -0
  145. static/vendor/ace/mode-nginx.js +8 -0
  146. static/vendor/ace/mode-nim.js +8 -0
  147. static/vendor/ace/mode-nix.js +8 -0
  148. static/vendor/ace/mode-nsis.js +8 -0
  149. static/vendor/ace/mode-nunjucks.js +8 -0
  150. static/vendor/ace/mode-objectivec.js +8 -0
  151. static/vendor/ace/mode-ocaml.js +8 -0
  152. static/vendor/ace/mode-odin.js +8 -0
  153. static/vendor/ace/mode-partiql.js +8 -0
  154. static/vendor/ace/mode-pascal.js +8 -0
  155. static/vendor/ace/mode-perl.js +8 -0
  156. static/vendor/ace/mode-pgsql.js +8 -0
  157. static/vendor/ace/mode-php.js +8 -0
  158. static/vendor/ace/mode-php_laravel_blade.js +8 -0
  159. static/vendor/ace/mode-pig.js +8 -0
  160. static/vendor/ace/mode-plain_text.js +8 -0
  161. static/vendor/ace/mode-plsql.js +8 -0
  162. static/vendor/ace/mode-powershell.js +8 -0
  163. static/vendor/ace/mode-praat.js +8 -0
  164. static/vendor/ace/mode-prisma.js +8 -0
  165. static/vendor/ace/mode-prolog.js +8 -0
  166. static/vendor/ace/mode-properties.js +8 -0
  167. static/vendor/ace/mode-protobuf.js +8 -0
  168. static/vendor/ace/mode-prql.js +8 -0
  169. static/vendor/ace/mode-puppet.js +8 -0
  170. static/vendor/ace/mode-python.js +8 -0
  171. static/vendor/ace/mode-qml.js +8 -0
  172. static/vendor/ace/mode-r.js +8 -0
  173. static/vendor/ace/mode-raku.js +8 -0
  174. static/vendor/ace/mode-razor.js +8 -0
  175. static/vendor/ace/mode-rdoc.js +8 -0
  176. static/vendor/ace/mode-red.js +8 -0
  177. static/vendor/ace/mode-redshift.js +8 -0
  178. static/vendor/ace/mode-rhtml.js +8 -0
  179. static/vendor/ace/mode-robot.js +8 -0
  180. static/vendor/ace/mode-rst.js +8 -0
  181. static/vendor/ace/mode-ruby.js +8 -0
  182. static/vendor/ace/mode-rust.js +8 -0
  183. static/vendor/ace/mode-sac.js +8 -0
  184. static/vendor/ace/mode-sass.js +8 -0
  185. static/vendor/ace/mode-scad.js +8 -0
  186. static/vendor/ace/mode-scala.js +8 -0
  187. static/vendor/ace/mode-scheme.js +8 -0
  188. static/vendor/ace/mode-scrypt.js +8 -0
  189. static/vendor/ace/mode-scss.js +8 -0
  190. static/vendor/ace/mode-sh.js +8 -0
  191. static/vendor/ace/mode-sjs.js +8 -0
  192. static/vendor/ace/mode-slim.js +8 -0
  193. static/vendor/ace/mode-smarty.js +8 -0
  194. static/vendor/ace/mode-smithy.js +8 -0
  195. static/vendor/ace/mode-snippets.js +8 -0
  196. static/vendor/ace/mode-soy_template.js +8 -0
  197. static/vendor/ace/mode-space.js +8 -0
  198. static/vendor/ace/mode-sparql.js +8 -0
  199. static/vendor/ace/mode-sql.js +8 -0
  200. static/vendor/ace/mode-sqlserver.js +8 -0
  201. static/vendor/ace/mode-stylus.js +8 -0
  202. static/vendor/ace/mode-svg.js +8 -0
  203. static/vendor/ace/mode-swift.js +8 -0
  204. static/vendor/ace/mode-tcl.js +8 -0
  205. static/vendor/ace/mode-terraform.js +8 -0
  206. static/vendor/ace/mode-tex.js +8 -0
  207. static/vendor/ace/mode-text.js +8 -0
  208. static/vendor/ace/mode-textile.js +8 -0
  209. static/vendor/ace/mode-toml.js +8 -0
  210. static/vendor/ace/mode-tsv.js +8 -0
  211. static/vendor/ace/mode-tsx.js +8 -0
  212. static/vendor/ace/mode-turtle.js +8 -0
  213. static/vendor/ace/mode-twig.js +8 -0
  214. static/vendor/ace/mode-typescript.js +8 -0
  215. static/vendor/ace/mode-vala.js +8 -0
  216. static/vendor/ace/mode-vbscript.js +8 -0
  217. static/vendor/ace/mode-velocity.js +8 -0
  218. static/vendor/ace/mode-verilog.js +8 -0
  219. static/vendor/ace/mode-vhdl.js +8 -0
  220. static/vendor/ace/mode-visualforce.js +8 -0
  221. static/vendor/ace/mode-vue.js +8 -0
  222. static/vendor/ace/mode-wollok.js +8 -0
  223. static/vendor/ace/mode-xml.js +8 -0
  224. static/vendor/ace/mode-xquery.js +8 -0
  225. static/vendor/ace/mode-yaml.js +8 -0
  226. static/vendor/ace/mode-zeek.js +8 -0
  227. static/vendor/ace/mode-zig.js +8 -0
  228. static/vendor/ace/theme-tomorrow_night_eighties.js +8 -0
  229. static/vendor/i18next/LICENSE +21 -0
  230. static/vendor/i18next/i18next.min.js +1 -0
  231. static/vendor/toastui-editor/LICENSE +21 -0
  232. static/vendor/toastui-editor/toastui-editor-dark.css +474 -0
  233. static/vendor/toastui-editor/toastui-editor.css +1597 -0
  234. static/vendor/toastui-editor/toastui-editor.js +24 -0
  235. static/vendor/toastui-editor/zh-cn.js +7 -0
  236. static/vendor/xterm/LICENSE +21 -0
  237. static/vendor/xterm/addons/fit.LICENSE +19 -0
  238. static/vendor/xterm/addons/fit.js +2 -0
  239. static/vendor/xterm/addons/unicode11.LICENSE +19 -0
  240. static/vendor/xterm/addons/unicode11.js +2 -0
  241. static/vendor/xterm/addons/web-links.LICENSE +19 -0
  242. static/vendor/xterm/addons/web-links.js +2 -0
  243. static/vendor/xterm/xterm.css +285 -0
  244. static/vendor/xterm/xterm.js +2 -0
static/editor.js ADDED
@@ -0,0 +1,142 @@
1
+ // Shared Ace adapter for every editable text document in Codex Partner.
2
+ function textEditorMarkup(id) {
3
+ return `<section class="editor-workspace" data-editor-workspace><header class="editor-toolbar"><div class="editor-toolbar-group"><button type="button" data-editor-action="undo" title="${esc(uiLabel("undo"))}" aria-label="${esc(uiLabel("undo"))}">↶</button><button type="button" data-editor-action="redo" title="${esc(uiLabel("redo"))}" aria-label="${esc(uiLabel("redo"))}">↷</button></div><div class="editor-toolbar-group" data-markdown-tools hidden><button type="button" data-editor-action="bold" title="${esc(uiLabel("bold"))}"><strong>B</strong></button><button type="button" data-editor-action="italic" title="${esc(uiLabel("italic"))}"><em>I</em></button><button type="button" data-editor-action="heading" title="${esc(uiLabel("heading"))}">H</button><button type="button" data-editor-action="link" title="${esc(uiLabel("link"))}">↗</button><button type="button" data-editor-action="code" title="${esc(uiLabel("inlineCode"))}">&lt;/&gt;</button><button type="button" data-editor-action="list" title="${esc(uiLabel("bulletList"))}">≡</button></div><button type="button" class="editor-wrap-button" data-editor-action="wrap" title="${esc(uiLabel("wrapLines"))}" aria-pressed="false">↵</button><div class="editor-view-switch" data-markdown-tools hidden><button type="button" class="active" data-editor-view="edit">${uiLabel("editView")}</button><button type="button" data-editor-view="split">${uiLabel("splitView")}</button><button type="button" data-editor-view="preview">${uiLabel("preview")}</button></div><span class="editor-toolbar-spacer"></span><span class="editor-mode" data-editor-mode></span><span class="editor-cursor" data-editor-cursor></span></header><div class="editor-stage" data-editor-stage data-view="edit"><div id="${esc(id)}" class="unified-editor"></div><div class="editor-preview memory-viewer" data-editor-preview hidden></div></div></section>`;
4
+ }
5
+
6
+ function detectEditorMode(filename = "", content = "") {
7
+ const modelist = window.ace?.require("ace/ext/modelist");
8
+ const detected = modelist?.getModeForPath(String(filename || ""))?.mode || "ace/mode/text";
9
+ if (detected !== "ace/mode/text") return detected;
10
+
11
+ const value = String(content || "");
12
+ const trimmed = value.trimStart();
13
+ if (/^[\[{]/.test(trimmed)) {
14
+ try { JSON.parse(trimmed); return "ace/mode/json"; } catch (_) {}
15
+ }
16
+ if (/^#!.*\bpython\b/m.test(value) || /^\s*(?:def|class|from|import)\s+[\w.]/m.test(value)) return "ace/mode/python";
17
+ if (/^#!.*\b(?:ba|z|k)?sh\b/m.test(value)) return "ace/mode/sh";
18
+ if (/^\s*(?:<!doctype\s+html|<html\b)/i.test(value)) return "ace/mode/html";
19
+ if (/^\s*<\?xml\b/i.test(value)) return "ace/mode/xml";
20
+ if (/^(?:---\s*$|[\w.-]+:\s*\S)/m.test(value)) return "ace/mode/yaml";
21
+ return "ace/mode/text";
22
+ }
23
+
24
+ function replaceEditorSelection(editor, prefix, suffix = "", placeholder = "") {
25
+ const range = editor.getSelectionRange();
26
+ const selected = editor.session.getTextRange(range);
27
+ editor.session.replace(range, `${prefix}${selected || placeholder}${suffix}`);
28
+ editor.focus();
29
+ }
30
+
31
+ function applyMarkdownAction(editor, action) {
32
+ if (action === "bold") return replaceEditorSelection(editor, "**", "**", uiLabel("bold"));
33
+ if (action === "italic") return replaceEditorSelection(editor, "_", "_", uiLabel("italic"));
34
+ if (action === "heading") return replaceEditorSelection(editor, "## ", "", uiLabel("heading"));
35
+ if (action === "link") return replaceEditorSelection(editor, "[", "](https://)", uiLabel("link"));
36
+ if (action === "code") return replaceEditorSelection(editor, "`", "`", "code");
37
+ if (action === "list") {
38
+ const range = editor.getSelectionRange();
39
+ const selected = editor.session.getTextRange(range) || uiLabel("bulletList");
40
+ editor.session.replace(range, selected.split("\n").map(line => `- ${line}`).join("\n"));
41
+ editor.focus();
42
+ }
43
+ }
44
+
45
+ function mountTextEditor(selector, options = {}) {
46
+ if (!window.ace) throw Error(uiLabel("editorUnavailable"));
47
+ const element = typeof selector === "string" ? $(selector) : selector;
48
+ if (!element) throw Error(uiLabel("editorUnavailable"));
49
+
50
+ window.ace.config.set("basePath", "/vendor/ace");
51
+ const editor = window.ace.edit(element);
52
+ const mode = detectEditorMode(options.filename, options.value);
53
+ editor.setTheme("ace/theme/tomorrow_night_eighties");
54
+ editor.session.setMode(mode);
55
+ editor.session.setUseWorker(false);
56
+ editor.session.setUseWrapMode(["ace/mode/markdown", "ace/mode/text"].includes(mode));
57
+ editor.setOptions({
58
+ fontSize: "13px",
59
+ readOnly: Boolean(options.readOnly),
60
+ showPrintMargin: false,
61
+ showFoldWidgets: true,
62
+ tabSize: 2,
63
+ useSoftTabs: true,
64
+ wrapBehavioursEnabled: true,
65
+ });
66
+ editor.setValue(String(options.value || ""), -1);
67
+ if (options.placeholder) editor.setOption("placeholder", options.placeholder);
68
+ editor.renderer.setScrollMargin(10, 10);
69
+ editor.container.dataset.mode = mode.replace("ace/mode/", "");
70
+ const workspace = editor.container.closest("[data-editor-workspace]");
71
+ const cursor = workspace?.querySelector("[data-editor-cursor]");
72
+ const modeLabel = workspace?.querySelector("[data-editor-mode]");
73
+ const wrapButton = workspace?.querySelector('[data-editor-action="wrap"]');
74
+ const markdown = mode === "ace/mode/markdown";
75
+ if (modeLabel) modeLabel.textContent = mode.replace("ace/mode/", "").replaceAll("_", " ").toUpperCase();
76
+ workspace?.querySelectorAll("[data-markdown-tools]").forEach(node => { node.hidden = !markdown; });
77
+ if (wrapButton) {
78
+ wrapButton.classList.toggle("active", editor.session.getUseWrapMode());
79
+ wrapButton.setAttribute("aria-pressed", String(editor.session.getUseWrapMode()));
80
+ }
81
+ const updateCursor = () => {
82
+ const position = editor.getCursorPosition();
83
+ if (cursor) cursor.textContent = uiLabel("lineColumn", { line: position.row + 1, column: position.column + 1 });
84
+ };
85
+ editor.selection.on("changeCursor", updateCursor);
86
+ updateCursor();
87
+ if (options.readOnly) {
88
+ workspace?.querySelectorAll('[data-editor-action]:not([data-editor-action="wrap"])').forEach(button => { button.disabled = true; });
89
+ }
90
+
91
+ let previewViewer = null;
92
+ const renderPreview = () => {
93
+ if (!markdown || !workspace) return;
94
+ const preview = workspace.querySelector("[data-editor-preview]");
95
+ if (!previewViewer) {
96
+ previewViewer = window.toastui.Editor.factory({
97
+ el: preview, viewer: true, initialValue: editor.getValue(), usageStatistics: false,
98
+ ...(document.documentElement.dataset.theme === "light" ? {} : { theme: "dark" }),
99
+ });
100
+ markdownComponents.push(previewViewer);
101
+ } else previewViewer.setMarkdown(editor.getValue());
102
+ };
103
+ let previewTimer = null;
104
+ editor.session.on("change", () => {
105
+ if (workspace?.querySelector("[data-editor-stage]")?.dataset.view === "edit") return;
106
+ clearTimeout(previewTimer);
107
+ previewTimer = setTimeout(renderPreview, 120);
108
+ });
109
+ workspace?.addEventListener("click", event => {
110
+ const actionButton = event.target.closest("[data-editor-action]");
111
+ if (actionButton) {
112
+ const action = actionButton.dataset.editorAction;
113
+ if (action === "undo") editor.undo();
114
+ else if (action === "redo") editor.redo();
115
+ else if (action === "wrap") {
116
+ const enabled = !editor.session.getUseWrapMode();
117
+ editor.session.setUseWrapMode(enabled);
118
+ actionButton.classList.toggle("active", enabled);
119
+ actionButton.setAttribute("aria-pressed", String(enabled));
120
+ } else if (markdown) applyMarkdownAction(editor, action);
121
+ editor.focus();
122
+ return;
123
+ }
124
+ const viewButton = event.target.closest("[data-editor-view]");
125
+ if (!viewButton) return;
126
+ const view = viewButton.dataset.editorView;
127
+ workspace.querySelector("[data-editor-stage]").dataset.view = view;
128
+ workspace.querySelectorAll("[data-editor-view]").forEach(button => button.classList.toggle("active", button === viewButton));
129
+ const preview = workspace.querySelector("[data-editor-preview]");
130
+ preview.hidden = view === "edit";
131
+ if (view !== "edit") renderPreview();
132
+ requestAnimationFrame(() => editor.resize());
133
+ });
134
+ editor.commands.addCommand({
135
+ name: "saveDocument",
136
+ bindKey: { win: "Ctrl-S", mac: "Command-S" },
137
+ exec: () => editor.container.closest("form")?.requestSubmit(),
138
+ });
139
+ markdownComponents.push(editor);
140
+ requestAnimationFrame(() => { editor.resize(); if (!options.readOnly) editor.focus(); });
141
+ return editor;
142
+ }
static/index.html ADDED
@@ -0,0 +1,71 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Codex Partner</title>
7
+ <link rel="stylesheet" href="/styles.css?v=20260816-game-sprites" />
8
+ <link rel="stylesheet" href="/mascot-dance.css?v=20260816-game-sprites" />
9
+ <link rel="stylesheet" href="/vendor/toastui-editor/toastui-editor.css" />
10
+ <link rel="stylesheet" href="/vendor/toastui-editor/toastui-editor-dark.css" />
11
+ <link rel="stylesheet" href="/vendor/xterm/xterm.css" />
12
+ </head>
13
+ <body>
14
+ <header class="topbar">
15
+ <div class="brand"><button id="theme-toggle" class="theme-toggle" type="button" title="切换废土终端主题" aria-label="切换废土终端主题"><span class="mascot mascot-mini" role="img" aria-hidden="true"><i></i><b></b><em></em></span></button><span>Codex Partner</span></div>
16
+ <div class="shortcut-hints" aria-label="快捷键">
17
+ <span class="shortcut-hint" title="单按反引号键 (`) 显示/隐藏终端"><kbd class="key-backquote" aria-label="反引号键"><span aria-hidden="true">~</span><span aria-hidden="true">`</span></kbd><span class="shortcut-label">终端</span></span>
18
+ <span class="shortcut-hint" title="下一个会话"><kbd class="combo-key" aria-label="Shift N"><span aria-hidden="true">⇧</span> N</kbd><span class="shortcut-label">下一个会话</span></span>
19
+ <span class="shortcut-hint" title="上一个会话"><kbd class="combo-key" aria-label="Shift P"><span aria-hidden="true">⇧</span> P</kbd><span class="shortcut-label">上一个会话</span></span>
20
+ <span class="shortcut-hint" title="回到最顶端会话"><kbd class="combo-key" aria-label="Shift C"><span aria-hidden="true">⇧</span> C</kbd><span class="shortcut-label">首个会话</span></span>
21
+ </div>
22
+ <div class="top-actions"><label class="language-picker"><span class="sr-only">界面语言</span><select id="language-select" aria-label="界面语言"><option value="zh">中文</option><option value="en">English</option><option value="fr">Français</option><option value="ja">日本語</option><option value="ko">한국어</option></select></label><button id="new-task" class="new-session" title="新建会话">+ <span>新会话</span></button><button id="open-terminal" class="icon-btn" title="显示/隐藏远程终端(单按反引号键)">&gt;_</button></div>
23
+ </header>
24
+ <button id="connection-status" class="connection-popover connecting" type="button" title="查看当前服务器、Provider 和实时指标" aria-label="查看当前服务器、Provider 和实时指标"><span class="connection-signal" aria-hidden="true"><i></i></span><span class="connection-copy"><small id="connection-server-label">服务器</small><strong id="connection-status-label">连接中</strong></span><span class="connection-runtime"><small id="connection-provider-label">当前 API · 自动选择</small><small id="connection-metrics-label">TTFT -- · TPOT --</small></span></button>
25
+ <div id="codex-install-notice" class="codex-install-notice" role="alert" hidden><span class="codex-install-mark" aria-hidden="true">!</span><span class="codex-install-copy"><strong>未找到 Codex CLI</strong><small id="codex-install-command">正在检查安装环境</small></span><button id="install-codex" class="primary" type="button">一键安装</button></div>
26
+ <main class="workspace-app">
27
+ <aside class="session-sidebar" aria-label="Codex 会话列表">
28
+ <div class="sidebar-top"><div><h1>会话</h1></div><button id="sync-native" class="icon-action" title="同步本机 Codex">⟳</button></div>
29
+ <div class="session-search"><span>⌕</span><input id="session-search" placeholder="搜索会话" /></div>
30
+ <div class="session-filters"><button class="filter active" data-filter="all">全部</button><button class="filter" data-filter="running">进行中</button><button class="filter" data-filter="available">待处理</button><button class="filter trash-filter" data-panel="trash" title="打开回收站">回收站</button></div>
31
+ <div id="running-summary" class="running-summary" hidden></div>
32
+ <div id="task-list" class="session-list"><div class="empty">加载会话中…</div></div>
33
+ <div class="sidebar-bottom"><button class="sidebar-link" data-panel="memories">⌁ <span>记忆</span></button><button class="sidebar-link" data-panel="skills">◇ <span>Skills</span></button><span id="sidebar-stats">0 个会话</span></div>
34
+ </aside>
35
+ <section class="conversation-shell">
36
+ <div id="empty-conversation" class="empty-conversation"><div class="mascot mascot-large" aria-hidden="true"><i></i><b></b><em></em></div><h2>开始一个 Codex 会话</h2><p>从左侧选择已有线程,或新建一个会话开始工作。</p><button id="empty-new-task" class="primary">新建会话</button></div>
37
+ <div id="conversation-view" class="conversation-view" hidden>
38
+ <header class="conversation-head"><div class="conversation-title"><button id="mobile-sessions" class="icon-action mobile-only" title="打开会话列表">☰</button><div><div class="conversation-title-line"><h2 id="conversation-name">Codex 会话</h2><button id="conversation-title-toggle" class="title-toggle" type="button" hidden>展开</button></div><p id="conversation-subtitle"></p></div></div><div class="conversation-actions"><button id="conversation-rename" class="icon-action" title="重命名">✎</button><button id="conversation-fork" class="icon-action" title="复制会话">⑂</button><button id="conversation-more" class="icon-action" title="更多操作">⋯</button></div></header>
39
+ <div id="chat-log" class="chat-stream"></div>
40
+ <div class="conversation-status-row">
41
+ <div id="turn-progress" class="turn-progress" aria-hidden="true"><div class="turn-progress-inner"><span class="codex-working-mark" aria-hidden="true"><i></i></span><span class="turn-progress-copy" role="status" aria-live="polite"><small id="turn-progress-tip"></small></span><span class="turn-progress-rail" aria-hidden="true"></span></div></div>
42
+ <section id="goal-bar" class="goal-bar" hidden aria-label="Goal 控制"><div class="goal-bar-main"><div class="goal-bar-heading"><span class="eyebrow">GOAL</span><span id="goal-task-status" class="goal-task-status"></span></div><div id="goal-text" class="goal-text"></div><form id="goal-edit-form" class="goal-edit-form" hidden><input id="goal-input" maxlength="2000" autocomplete="off" placeholder="输入要持续完成的目标"/><div class="goal-edit-actions"><button type="button" class="goal-button danger-small" id="goal-clear">清空</button><button type="button" class="goal-button subtle" id="goal-edit-cancel">取消</button><button class="goal-button primary-small">保存</button></div></form></div><div class="goal-bar-controls"><button type="button" id="goal-edit" class="goal-button subtle" title="修改 Goal" aria-label="修改 Goal">✎</button><button type="button" id="goal-run-toggle" class="goal-button" title="暂停 Goal" aria-label="暂停 Goal"><span id="goal-run-icon">Ⅱ</span><span id="goal-run-label" class="sr-only">暂停 Goal</span></button><button type="button" id="goal-retry-toggle" class="goal-button retry" title="打开 Goal 自动续跑" aria-label="切换 Goal 自动续跑"><span>↻</span><strong id="goal-retry-label">关</strong></button></div></section>
43
+ </div>
44
+ <div class="composer-dock"><div id="queued-messages" class="queued-messages" hidden aria-live="polite"></div><form id="message-form" class="composer"><div id="composer-context" class="composer-context"></div><div id="command-palette" class="command-palette" hidden role="listbox" aria-label="Codex 斜杠命令"></div><textarea id="message-input" rows="1" placeholder="给 Codex 发送消息…"></textarea><div class="composer-bottom"><div class="composer-send-actions"><button type="submit" class="send-codex-button" id="send-codex" title="发送到 Codex(Enter)"><span id="send-codex-label">发送</span><span aria-hidden="true">↑</span></button></div></div><div id="composer-meta" class="composer-meta" aria-label="当前会话控制"><div class="composer-meta-tools"><button type="button" class="tool-button" id="attach-button" title="添加本地文本文件">+</button><input id="attach-file" type="file" hidden /><button type="button" class="tool-button" id="command-button" title="打开 Codex 斜杠命令">/</button></div><span id="composer-live-state" class="composer-live-state"><i></i><span>待命</span></span><button type="button" class="tool-button composer-yolo" id="permission-toggle" title="切换 YOLO/受控模式">✦ <span>YOLO</span></button><label class="composer-model-control"><span>模型</span><select id="composer-model-select" class="composer-select" aria-label="选择模型"><option value="">Provider 默认模型</option></select></label><span id="composer-context-usage" class="composer-context-usage"><span class="context-usage-copy">Context --</span><span class="context-usage-track" aria-hidden="true"><i></i></span></span><label class="composer-effort-control"><span>思考</span><select id="composer-effort-select" class="composer-select" aria-label="选择思考程度"><option value="">Provider 默认</option><option value="minimal">最小</option><option value="low">低</option><option value="medium">中</option><option value="high">高</option><option value="xhigh">很高</option><option value="ultra">极高</option></select></label><span id="composer-goal-meta" class="meta-pill goal-pill" hidden></span></div></form></div>
45
+ </div>
46
+ </section>
47
+ <aside id="inspector" class="inspector" aria-label="会话详情"><div class="inspector-head"><h2>工作区</h2><button id="close-inspector" class="icon-action" title="关闭详情">×</button></div><div id="inspector-content"></div></aside>
48
+ </main>
49
+ <aside id="drawer" class="drawer" aria-hidden="true"><div class="drawer-inner"><button id="close-drawer" class="close">×</button><div id="drawer-content"></div></div></aside>
50
+ <div id="panel" class="panel" aria-hidden="true"><div class="panel-inner"><button id="close-panel" class="close">×</button><div id="panel-content"></div></div></div>
51
+ <div id="terminal-window" class="terminal-window" aria-hidden="true"><section class="terminal-shell" role="dialog" aria-modal="true" aria-labelledby="terminal-title"><header class="terminal-head"><div><span id="terminal-eyebrow" class="eyebrow">远程 Shell</span><h2 id="terminal-title">远程终端</h2><small id="terminal-cwd">未连接</small></div><div class="terminal-head-actions"><span id="terminal-status" class="terminal-status">未连接</span><button id="terminal-close" class="icon-action" title="关闭终端">×</button></div></header><div id="terminal-screen" class="terminal-screen" aria-label="远程终端输出"></div><footer class="terminal-foot"><span id="terminal-hint">点击终端后输入 · Enter 执行 · Ctrl+C 中断</span><button id="terminal-reconnect" class="secondary">重连</button></footer></section></div>
52
+ <input id="workspace-upload-input" type="file" multiple hidden /><input id="user-avatar-file" type="file" accept="image/*" hidden />
53
+ <dialog id="app-dialog" class="app-dialog" aria-labelledby="app-dialog-title"><form id="app-dialog-form"><div class="app-dialog-heading"><span class="mascot mascot-inline" aria-hidden="true"><i></i><b></b><em></em></span><div><h2 id="app-dialog-title"></h2><p id="app-dialog-message"></p></div></div><label id="app-dialog-field" hidden><span id="app-dialog-label"></span><input id="app-dialog-input" autocomplete="off" /></label><div class="app-dialog-actions"><button type="button" class="secondary" id="app-dialog-cancel"></button><button type="submit" class="primary" id="app-dialog-confirm"></button></div></form></dialog>
54
+ <div id="toast" class="toast" role="status"></div>
55
+ <script src="/vendor/xterm/xterm.js"></script>
56
+ <script src="/vendor/xterm/addons/fit.js"></script>
57
+ <script src="/vendor/xterm/addons/unicode11.js"></script>
58
+ <script src="/vendor/xterm/addons/web-links.js"></script>
59
+ <script src="/vendor/toastui-editor/toastui-editor.js"></script>
60
+ <script src="/vendor/toastui-editor/zh-cn.js"></script>
61
+ <script src="/vendor/i18next/i18next.min.js"></script>
62
+ <script src="/vendor/ace/ace.js"></script>
63
+ <script src="/vendor/ace/ext-modelist.js"></script>
64
+ <script src="/core.js?v=20260816-game-sprites"></script>
65
+ <script src="/mascot-dance.js?v=20260816-game-sprites"></script>
66
+ <script src="/editor.js?v=20260816-game-sprites"></script>
67
+ <script src="/conversation.js?v=20260816-game-sprites"></script>
68
+ <script src="/settings.js?v=20260816-game-sprites"></script>
69
+ <script src="/app.js?v=20260816-game-sprites"></script>
70
+ </body>
71
+ </html>
@@ -0,0 +1,100 @@
1
+ /* Layered SVG game sprites for the keyboard dance easter egg. */
2
+ .mascot-dance { position: fixed; z-index: 95; inset: 0; overflow: hidden; pointer-events: none; }
3
+ .mascot-dance-stage { position: absolute; bottom: 7%; left: 50%; width: min(520px, 100vw); height: 250px; transform: translateX(-50%); }
4
+ .mascot-dance-stage::before { content: ""; position: absolute; right: 5%; bottom: 5px; left: 5%; height: 48px; border: 1px solid #99e97842; border-radius: 50%; background: radial-gradient(ellipse, #a3ff7135 0, #81d95d14 45%, transparent 72%); box-shadow: 0 0 50px #80ff5830; animation: dance-stage-breathe 1.25s ease-in-out infinite alternate; }
5
+ .mascot-dance-stage::after { content: ""; position: absolute; right: 13%; bottom: 18px; left: 13%; height: 1px; background: linear-gradient(90deg, transparent, #d9ffacaa, transparent); box-shadow: 0 0 14px #9cff76; }
6
+ .dance-beam { position: absolute; z-index: 0; bottom: 20px; width: 150px; height: 225px; opacity: .28; clip-path: polygon(42% 0, 58% 0, 100% 100%, 0 100%); background: linear-gradient(180deg, #dfffad55, #8cff7208); filter: blur(2px); }
7
+ .beam-left { left: 38px; transform: rotate(-8deg); }.beam-right { right: 38px; transform: rotate(8deg); }
8
+ .dance-figure { position: absolute; z-index: 2; bottom: 22px; width: 150px; height: 193px; transform-origin: 50% 92%; will-change: transform, opacity; }
9
+ .dance-sprite { display: block; width: 100%; height: 100%; overflow: visible; filter: drop-shadow(0 10px 8px #0008); }
10
+ .dance-partner-figure { left: 58px; animation: partner-game-entry 10s cubic-bezier(.42, 0, .25, 1) both; }
11
+ .dance-scout-figure { right: 58px; animation: scout-game-entry 10s cubic-bezier(.42, 0, .25, 1) both; }
12
+ .sprite-shadow { fill: #07120c99; transform-origin: center; animation: sprite-shadow-pulse .7s ease-in-out infinite alternate; }
13
+ .sprite-head, .sprite-body, .sprite-arm, .sprite-leg { transform-box: fill-box; }
14
+ .partner-head { transform-origin: 50% 85%; animation: partner-head-dance 2.2s ease-in-out infinite; }
15
+ .partner-body { transform-origin: 50% 20%; animation: partner-body-dance 1.4s ease-in-out infinite; }
16
+ .partner-arm-left { transform-origin: 88% 16%; animation: partner-arm-left-dance 2.8s ease-in-out infinite; }
17
+ .partner-arm-right { transform-origin: 12% 16%; animation: partner-arm-right-dance 2.8s ease-in-out infinite; }
18
+ .partner-leg-left { transform-origin: 55% 10%; animation: partner-step-left .7s ease-in-out infinite alternate; }
19
+ .partner-leg-right { transform-origin: 45% 10%; animation: partner-step-right .7s ease-in-out infinite alternate; }
20
+ .partner-eyes { transform-box: fill-box; transform-origin: center; animation: partner-game-blink 3.6s steps(1, end) infinite; }
21
+ .scout-head { transform-origin: 50% 88%; animation: scout-head-dance 1.15s steps(2, end) infinite; }
22
+ .scout-body { transform-origin: center; animation: scout-body-hit .8s steps(2, end) infinite; }
23
+ .scout-arm-left { transform-origin: 90% 15%; animation: scout-arm-left-dance 3.2s steps(1, end) infinite; }
24
+ .scout-arm-right { transform-origin: 10% 15%; animation: scout-arm-right-dance 3.2s steps(1, end) infinite; }
25
+ .scout-leg-left { transform-origin: 55% 10%; animation: scout-step-left .8s steps(2, end) infinite; }
26
+ .scout-leg-right { transform-origin: 45% 10%; animation: scout-step-right .8s steps(2, end) infinite; }
27
+ .scout-eyes { transform-box: fill-box; transform-origin: center; animation: scout-optics 1.6s steps(2, end) infinite; }
28
+ .scout-core, .scout-signal { animation: scout-light-pulse .8s ease-in-out infinite alternate; }
29
+ .scout-scan { animation: scout-visor-scan 1.6s linear infinite; }
30
+ .dance-particles { position: absolute; z-index: 3; inset: 0; }
31
+ .dance-particles > * { position: absolute; display: block; width: 8px; height: 8px; border: 2px solid #bcff91; opacity: 0; box-shadow: 0 0 9px #8cff72; animation: dance-particle 2.4s ease-out infinite; }
32
+ .dance-particles i { top: 34%; left: 44%; transform: rotate(45deg); }.dance-particles b { top: 22%; left: 55%; border-radius: 50%; animation-delay: .55s; }.dance-particles em { top: 47%; left: 52%; width: 5px; height: 13px; border-width: 0 2px; animation-delay: 1.1s; }.dance-particles strong { top: 30%; left: 48%; border-radius: 50%; animation-delay: 1.65s; }
33
+
34
+ @keyframes partner-game-entry {
35
+ 0% { opacity: 0; transform: translateX(-62vw) translateY(8px) rotate(-8deg) scale(.88); }
36
+ 4% { opacity: 1; }
37
+ 7% { transform: translateX(-50vw) translateY(-15px) rotate(5deg) scale(.92); }
38
+ 11% { transform: translateX(-40vw) translateY(2px) rotate(-3deg) scale(.95); }
39
+ 15% { transform: translateX(-30vw) translateY(-18px) rotate(6deg); }
40
+ 19% { transform: translateX(-20vw) translateY(0) rotate(-4deg); }
41
+ 23% { transform: translateX(-10vw) translateY(-13px) rotate(4deg); }
42
+ 28% { transform: translateX(0) translateY(0); }
43
+ 34% { transform: translate(-14px, -10px) rotate(-6deg); }
44
+ 42% { transform: translate(18px, -2px) rotate(7deg); }
45
+ 50% { transform: translate(30px, -15px) rotate(-5deg); }
46
+ 58% { transform: translate(-6px, 0) rotate(4deg); }
47
+ 66% { transform: translate(-22px, -12px) rotate(-8deg); }
48
+ 74% { transform: translate(12px, -3px) rotate(6deg); }
49
+ 82% { transform: translate(26px, -18px) rotate(-7deg) scale(1.04); }
50
+ 89% { transform: translateY(-25px) rotate(8deg) scale(1.08); }
51
+ 94% { opacity: 1; transform: translateY(0) rotate(-3deg); }
52
+ 100% { opacity: 0; transform: translateX(-24vw) translateY(-10px) rotate(-12deg) scale(.94); }
53
+ }
54
+ @keyframes scout-game-entry {
55
+ 0% { opacity: 0; transform: translateX(62vw) translateY(8px) rotate(8deg) scale(.88); }
56
+ 4% { opacity: 1; }
57
+ 7% { transform: translateX(50vw) translateY(-12px) rotate(-4deg) scale(.92); }
58
+ 11% { transform: translateX(40vw) translateY(2px) rotate(3deg) scale(.95); }
59
+ 15% { transform: translateX(30vw) translateY(-16px) rotate(-5deg); }
60
+ 19% { transform: translateX(20vw) translateY(0) rotate(4deg); }
61
+ 23% { transform: translateX(10vw) translateY(-12px) rotate(-3deg); }
62
+ 28% { transform: translateX(0) translateY(0); }
63
+ 34% { transform: translate(14px, -2px) rotate(5deg); }
64
+ 42% { transform: translate(-18px, -11px) rotate(-7deg); }
65
+ 50% { transform: translate(-30px, 0) rotate(5deg); }
66
+ 58% { transform: translate(6px, -15px) rotate(-4deg); }
67
+ 66% { transform: translate(22px, 0) rotate(8deg); }
68
+ 74% { transform: translate(-12px, -12px) rotate(-6deg); }
69
+ 82% { transform: translate(-26px, -4px) rotate(7deg) scale(1.04); }
70
+ 89% { transform: translateY(-25px) rotate(-8deg) scale(1.08); }
71
+ 94% { opacity: 1; transform: translateY(0) rotate(3deg); }
72
+ 100% { opacity: 0; transform: translateX(24vw) translateY(-10px) rotate(12deg) scale(.94); }
73
+ }
74
+ @keyframes partner-head-dance { 0%, 100% { transform: rotate(-4deg) translateY(1px); } 50% { transform: rotate(6deg) translateY(-4px); } }
75
+ @keyframes partner-body-dance { 0%, 100% { transform: skewX(-2deg) scaleY(.97); } 50% { transform: skewX(3deg) scaleY(1.04); } }
76
+ @keyframes partner-arm-left-dance { 0%, 100% { transform: rotate(8deg); } 25% { transform: rotate(-38deg) translateY(-3px); } 50% { transform: rotate(18deg); } 75% { transform: rotate(-58deg) translateY(-5px); } }
77
+ @keyframes partner-arm-right-dance { 0%, 100% { transform: rotate(-8deg); } 25% { transform: rotate(42deg) translateY(-4px); } 50% { transform: rotate(-20deg); } 75% { transform: rotate(54deg) translateY(-3px); } }
78
+ @keyframes partner-step-left { to { transform: translate(5px, -8px) rotate(7deg); } }
79
+ @keyframes partner-step-right { to { transform: translate(-5px, 7px) rotate(-6deg); } }
80
+ @keyframes partner-game-blink { 0%, 45%, 49%, 100% { transform: scaleY(1); } 47% { transform: scaleY(.12); } }
81
+ @keyframes scout-head-dance { 0%, 49% { transform: translateX(-3px) rotate(-3deg); } 50%, 100% { transform: translateX(3px) rotate(4deg); } }
82
+ @keyframes scout-body-hit { 0%, 49% { transform: scale(1); } 50%, 100% { transform: scale(1.04, .96); } }
83
+ @keyframes scout-arm-left-dance { 0%, 24% { transform: rotate(8deg); } 25%, 49% { transform: rotate(-70deg) translateY(-4px); } 50%, 74% { transform: rotate(-18deg); } 75%, 100% { transform: rotate(-92deg) translateY(-7px); } }
84
+ @keyframes scout-arm-right-dance { 0%, 24% { transform: rotate(-8deg); } 25%, 49% { transform: rotate(15deg); } 50%, 74% { transform: rotate(72deg) translateY(-4px); } 75%, 100% { transform: rotate(92deg) translateY(-7px); } }
85
+ @keyframes scout-step-left { 0%, 49% { transform: translateY(-7px) rotate(5deg); } 50%, 100% { transform: translateY(3px); } }
86
+ @keyframes scout-step-right { 0%, 49% { transform: translateY(3px); } 50%, 100% { transform: translateY(-7px) rotate(-5deg); } }
87
+ @keyframes scout-optics { 50% { filter: brightness(1.8); transform: scaleY(.88); } }
88
+ @keyframes scout-light-pulse { to { filter: brightness(1.5); opacity: .55; } }
89
+ @keyframes scout-visor-scan { 0% { transform: translateY(0); opacity: 0; } 15%, 80% { opacity: .8; } 100% { transform: translateY(28px); opacity: 0; } }
90
+ @keyframes sprite-shadow-pulse { to { transform: scaleX(.82); opacity: .55; } }
91
+ @keyframes dance-particle { 0%, 20% { opacity: 0; transform: translateY(12px) rotate(0) scale(.5); } 35% { opacity: .9; } 100% { opacity: 0; transform: translateY(-55px) rotate(135deg) scale(1.15); } }
92
+ @keyframes dance-stage-breathe { to { opacity: .6; transform: scaleX(.9); } }
93
+
94
+ @media (max-width: 600px) {
95
+ .mascot-dance-stage { bottom: 9%; height: 215px; }
96
+ .dance-figure { width: 122px; height: 157px; }
97
+ .dance-partner-figure { left: 16px; }.dance-scout-figure { right: 16px; }
98
+ .dance-beam { width: 110px; height: 185px; }.beam-left { left: 14px; }.beam-right { right: 14px; }
99
+ }
100
+ @media (prefers-reduced-motion: reduce) { .mascot-dance { display: none; } }
static/mascot-dance.js ADDED
@@ -0,0 +1,46 @@
1
+ // Self-contained SVG sprites and lifecycle for the keyboard dance easter egg.
2
+ function partnerDanceSprite() {
3
+ return `<svg class="dance-sprite partner-sprite" viewBox="0 0 140 180" aria-hidden="true">
4
+ <defs>
5
+ <linearGradient id="partner-head" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#e8ff9d"/><stop offset=".55" stop-color="#bfe866"/><stop offset="1" stop-color="#83b94d"/></linearGradient>
6
+ <linearGradient id="partner-body" x1="0" y1="0" x2="0" y2="1"><stop stop-color="#b9e967"/><stop offset="1" stop-color="#719e45"/></linearGradient>
7
+ <linearGradient id="partner-shoe" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#efffc0"/><stop offset="1" stop-color="#94bd55"/></linearGradient>
8
+ </defs>
9
+ <ellipse class="sprite-shadow" cx="70" cy="169" rx="46" ry="8"/>
10
+ <g class="sprite-leg partner-leg-left"><rect x="48" y="119" width="17" height="38" rx="8" fill="url(#partner-body)" stroke="#254432" stroke-width="5"/><circle cx="56" cy="123" r="7" fill="#d9f886" stroke="#254432" stroke-width="4"/><path d="M38 151c8-6 22-5 28 2l-1 10H37c-5 0-7-7 1-12Z" fill="url(#partner-shoe)" stroke="#254432" stroke-width="5"/></g>
11
+ <g class="sprite-leg partner-leg-right"><rect x="76" y="119" width="17" height="38" rx="8" fill="url(#partner-body)" stroke="#254432" stroke-width="5"/><circle cx="84" cy="123" r="7" fill="#d9f886" stroke="#254432" stroke-width="4"/><path d="M77 153c8-7 23-7 30 0 7 7 4 11-2 11H77Z" fill="url(#partner-shoe)" stroke="#254432" stroke-width="5"/></g>
12
+ <g class="sprite-body partner-body"><path d="M45 75c6-9 44-9 50 0l8 45c-13 12-53 12-66 0Z" fill="url(#partner-body)" stroke="#254432" stroke-width="6"/><path d="M53 85c9 5 25 5 34 0" fill="none" stroke="#eaffaa" stroke-width="4" stroke-linecap="round" opacity=".75"/><path d="m70 91 5 9 10 2-7 7 2 10-10-5-10 5 2-10-7-7 10-2Z" fill="#ecffad" stroke="#476b38" stroke-width="3"/></g>
13
+ <g class="sprite-arm partner-arm-left"><path d="M47 79c-15 1-25 12-30 26" fill="none" stroke="#254432" stroke-width="17" stroke-linecap="round"/><path d="M47 79c-15 2-23 12-29 27" fill="none" stroke="#a9da5e" stroke-width="10" stroke-linecap="round"/><circle cx="16" cy="109" r="11" fill="#dafa85" stroke="#254432" stroke-width="5"/><path d="M11 105c4-3 9-3 13 0" fill="none" stroke="#f5ffc7" stroke-width="3" stroke-linecap="round"/></g>
14
+ <g class="sprite-arm partner-arm-right"><path d="M93 79c15 1 25 12 30 26" fill="none" stroke="#254432" stroke-width="17" stroke-linecap="round"/><path d="M93 79c15 2 23 12 29 27" fill="none" stroke="#a9da5e" stroke-width="10" stroke-linecap="round"/><circle cx="124" cy="109" r="11" fill="#dafa85" stroke="#254432" stroke-width="5"/><path d="M117 105c4-3 9-3 13 0" fill="none" stroke="#f5ffc7" stroke-width="3" stroke-linecap="round"/></g>
15
+ <g class="sprite-head partner-head"><path d="M68 18V8" fill="none" stroke="#31513a" stroke-width="5" stroke-linecap="round"/><circle cx="68" cy="6" r="7" fill="#e7ff99" stroke="#31513a" stroke-width="4"/><rect x="28" y="18" width="82" height="68" rx="25" fill="url(#partner-head)" stroke="#254432" stroke-width="6"/><path d="M39 33c16-13 43-15 60-3" fill="none" stroke="#f3ffc5" stroke-width="7" stroke-linecap="round" opacity=".65"/><g class="partner-eyes"><ellipse cx="52" cy="51" rx="8" ry="11" fill="#183127"/><ellipse cx="86" cy="51" rx="8" ry="11" fill="#183127"/><circle cx="49" cy="47" r="2.5" fill="#f5ffd4"/><circle cx="83" cy="47" r="2.5" fill="#f5ffd4"/></g><path d="M51 66c9 9 27 9 36 0" fill="none" stroke="#183127" stroke-width="5" stroke-linecap="round"/><circle cx="43" cy="63" r="4" fill="#f5b9a7" opacity=".65"/><circle cx="96" cy="63" r="4" fill="#f5b9a7" opacity=".65"/></g>
16
+ </svg>`;
17
+ }
18
+
19
+ function scoutDanceSprite() {
20
+ return `<svg class="dance-sprite scout-sprite" viewBox="0 0 140 180" aria-hidden="true">
21
+ <defs>
22
+ <linearGradient id="scout-armor" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#84905c"/><stop offset=".5" stop-color="#566343"/><stop offset="1" stop-color="#303b31"/></linearGradient>
23
+ <linearGradient id="scout-brass" x1="0" y1="0" x2="0" y2="1"><stop stop-color="#f2c65c"/><stop offset="1" stop-color="#9a6d26"/></linearGradient>
24
+ <radialGradient id="scout-eye"><stop stop-color="#e8ffc7"/><stop offset=".35" stop-color="#9cff76"/><stop offset="1" stop-color="#3b7e3d"/></radialGradient>
25
+ </defs>
26
+ <ellipse class="sprite-shadow" cx="70" cy="169" rx="47" ry="8"/>
27
+ <g class="sprite-leg scout-leg-left"><path d="M47 118h20l-3 39H44Z" fill="url(#scout-armor)" stroke="#2a332b" stroke-width="6"/><circle cx="57" cy="122" r="7" fill="#d5a443" stroke="#2a332b" stroke-width="4"/><path d="M38 151h28l4 12H35c-6 0-5-8 3-12Z" fill="url(#scout-brass)" stroke="#2a332b" stroke-width="5"/><path d="M43 154h18" stroke="#f4d17b" stroke-width="3" opacity=".6"/></g>
28
+ <g class="sprite-leg scout-leg-right"><path d="M74 118h20l3 39H77Z" fill="url(#scout-armor)" stroke="#2a332b" stroke-width="6"/><circle cx="84" cy="122" r="7" fill="#d5a443" stroke="#2a332b" stroke-width="4"/><path d="M75 151h28c8 4 9 12 3 12H72Z" fill="url(#scout-brass)" stroke="#2a332b" stroke-width="5"/><path d="M80 154h18" stroke="#f4d17b" stroke-width="3" opacity=".6"/></g>
29
+ <g class="sprite-body scout-body"><path d="M40 72 52 64h39l11 10-5 48-12 10H54l-12-10Z" fill="url(#scout-armor)" stroke="#2a332b" stroke-width="6"/><path d="M51 82h40v31H51Z" fill="#1b2924" stroke="#d3a443" stroke-width="4"/><circle cx="71" cy="97" r="10" fill="#233a2d" stroke="#d3a443" stroke-width="4"/><circle class="scout-core" cx="71" cy="97" r="5" fill="#9cff76"/><path d="M55 118h32" stroke="#d3a443" stroke-width="4" stroke-linecap="round"/><circle cx="48" cy="75" r="3" fill="#f4d17b"/><circle cx="95" cy="75" r="3" fill="#f4d17b"/></g>
30
+ <g class="sprite-arm scout-arm-left"><path d="M43 78 24 91 15 111" fill="none" stroke="#29322b" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/><path d="M43 78 25 92 16 111" fill="none" stroke="#65734c" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><circle cx="25" cy="91" r="7" fill="#d2a142" stroke="#29322b" stroke-width="4"/><path d="m14 103-8 8 7 12 12-4 1-12Z" fill="url(#scout-brass)" stroke="#29322b" stroke-width="5"/></g>
31
+ <g class="sprite-arm scout-arm-right"><path d="m99 78 18 14 10 19" fill="none" stroke="#29322b" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/><path d="m99 78 17 15 10 18" fill="none" stroke="#65734c" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><circle cx="116" cy="92" r="7" fill="#d2a142" stroke="#29322b" stroke-width="4"/><path d="m126 103 9 8-7 12-12-4-1-12Z" fill="url(#scout-brass)" stroke="#29322b" stroke-width="5"/></g>
32
+ <g class="sprite-head scout-head"><path d="m77 19 6-13" stroke="#d4a542" stroke-width="5" stroke-linecap="round"/><circle class="scout-signal" cx="84" cy="5" r="6" fill="#9cff76" stroke="#29322b" stroke-width="4"/><path d="m31 29 15-14h55l11 17-6 43-14 12H43L28 73Z" fill="url(#scout-armor)" stroke="#29322b" stroke-width="6"/><path d="m36 35 13-11h45" fill="none" stroke="#d7b35c" stroke-width="5" stroke-linecap="round" opacity=".75"/><path d="M39 39h58l5 27-10 10H45L35 65Z" fill="#12231d" stroke="#d1a343" stroke-width="4"/><g class="scout-eyes"><rect x="46" y="46" width="17" height="17" rx="4" fill="url(#scout-eye)" stroke="#8fcf69" stroke-width="3"/><rect x="75" y="46" width="17" height="17" rx="4" fill="url(#scout-eye)" stroke="#8fcf69" stroke-width="3"/></g><path class="scout-scan" d="M41 41h55" stroke="#c8ff9d" stroke-width="2" opacity=".75"/><path d="M52 70h34" stroke="#d1a343" stroke-width="4" stroke-linecap="round" stroke-dasharray="3 5"/><path d="M105 38h11v25h-12" fill="#4b573e" stroke="#29322b" stroke-width="5"/></g>
33
+ </svg>`;
34
+ }
35
+
36
+ function triggerMascotDance() {
37
+ if (document.querySelector("#mascot-dance")) return;
38
+ const scene = document.createElement("div");
39
+ scene.id = "mascot-dance";
40
+ scene.className = "mascot-dance";
41
+ scene.setAttribute("role", "status");
42
+ scene.setAttribute("aria-label", uiLabel("mascotDance"));
43
+ scene.innerHTML = `<div class="mascot-dance-stage"><span class="dance-beam beam-left"></span><span class="dance-beam beam-right"></span><span class="dance-particles" aria-hidden="true"><i></i><b></b><em></em><strong></strong></span><span class="dance-figure dance-partner-figure">${partnerDanceSprite()}</span><span class="dance-figure dance-scout-figure">${scoutDanceSprite()}</span></div>`;
44
+ document.body.append(scene);
45
+ window.setTimeout(() => scene.remove(), 10400);
46
+ }