synthos 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +33 -1
  2. package/default-pages/app_builder.html +40 -0
  3. package/default-pages/app_builder.json +1 -0
  4. package/default-pages/json_tools.html +89 -159
  5. package/default-pages/json_tools.json +1 -0
  6. package/default-pages/my_notes.html +33 -0
  7. package/default-pages/my_notes.json +12 -0
  8. package/default-pages/neon_asteroids.html +77 -0
  9. package/default-pages/neon_asteroids.json +12 -0
  10. package/default-pages/sidebar_builder.html +49 -0
  11. package/default-pages/sidebar_builder.json +1 -0
  12. package/default-pages/solar_explorer.html +1956 -0
  13. package/default-pages/solar_explorer.json +12 -0
  14. package/default-pages/solar_tutorial.html +476 -0
  15. package/default-pages/solar_tutorial.json +1 -0
  16. package/default-pages/two-panel_builder.html +66 -0
  17. package/default-pages/two-panel_builder.json +1 -0
  18. package/dist/connectors/index.d.ts +3 -0
  19. package/dist/connectors/index.d.ts.map +1 -0
  20. package/dist/connectors/index.js +6 -0
  21. package/dist/connectors/index.js.map +1 -0
  22. package/dist/connectors/registry.d.ts +3 -0
  23. package/dist/connectors/registry.d.ts.map +1 -0
  24. package/dist/connectors/registry.js +100 -0
  25. package/dist/connectors/registry.js.map +1 -0
  26. package/dist/connectors/types.d.ts +61 -0
  27. package/dist/connectors/types.d.ts.map +1 -0
  28. package/dist/connectors/types.js +3 -0
  29. package/dist/connectors/types.js.map +1 -0
  30. package/dist/files.d.ts +2 -0
  31. package/dist/files.d.ts.map +1 -1
  32. package/dist/files.js +12 -1
  33. package/dist/files.js.map +1 -1
  34. package/dist/init.d.ts +8 -1
  35. package/dist/init.d.ts.map +1 -1
  36. package/dist/init.js +155 -3
  37. package/dist/init.js.map +1 -1
  38. package/dist/migrations.d.ts +11 -0
  39. package/dist/migrations.d.ts.map +1 -0
  40. package/dist/migrations.js +281 -0
  41. package/dist/migrations.js.map +1 -0
  42. package/dist/models/index.d.ts +3 -0
  43. package/dist/models/index.d.ts.map +1 -0
  44. package/dist/models/index.js +10 -0
  45. package/dist/models/index.js.map +1 -0
  46. package/dist/models/providers.d.ts +7 -0
  47. package/dist/models/providers.d.ts.map +1 -0
  48. package/dist/models/providers.js +33 -0
  49. package/dist/models/providers.js.map +1 -0
  50. package/dist/models/types.d.ts +21 -0
  51. package/dist/models/types.d.ts.map +1 -0
  52. package/dist/models/types.js +3 -0
  53. package/dist/models/types.js.map +1 -0
  54. package/dist/pages.d.ts +21 -2
  55. package/dist/pages.d.ts.map +1 -1
  56. package/dist/pages.js +202 -23
  57. package/dist/pages.js.map +1 -1
  58. package/dist/scripts.js +2 -2
  59. package/dist/scripts.js.map +1 -1
  60. package/dist/service/createCompletePrompt.d.ts +3 -2
  61. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  62. package/dist/service/createCompletePrompt.js +11 -16
  63. package/dist/service/createCompletePrompt.js.map +1 -1
  64. package/dist/service/debugLog.d.ts +11 -0
  65. package/dist/service/debugLog.d.ts.map +1 -0
  66. package/dist/service/debugLog.js +26 -0
  67. package/dist/service/debugLog.js.map +1 -0
  68. package/dist/service/modelInstructions.d.ts +7 -0
  69. package/dist/service/modelInstructions.d.ts.map +1 -0
  70. package/dist/service/modelInstructions.js +16 -0
  71. package/dist/service/modelInstructions.js.map +1 -0
  72. package/dist/service/requiresSettings.d.ts +2 -2
  73. package/dist/service/requiresSettings.d.ts.map +1 -1
  74. package/dist/service/requiresSettings.js.map +1 -1
  75. package/dist/service/server.d.ts.map +1 -1
  76. package/dist/service/server.js +15 -0
  77. package/dist/service/server.js.map +1 -1
  78. package/dist/service/transformPage.d.ts +81 -2
  79. package/dist/service/transformPage.d.ts.map +1 -1
  80. package/dist/service/transformPage.js +672 -82
  81. package/dist/service/transformPage.js.map +1 -1
  82. package/dist/service/useApiRoutes.d.ts.map +1 -1
  83. package/dist/service/useApiRoutes.js +579 -13
  84. package/dist/service/useApiRoutes.js.map +1 -1
  85. package/dist/service/useConnectorRoutes.d.ts +4 -0
  86. package/dist/service/useConnectorRoutes.d.ts.map +1 -0
  87. package/dist/service/useConnectorRoutes.js +389 -0
  88. package/dist/service/useConnectorRoutes.js.map +1 -0
  89. package/dist/service/useDataRoutes.d.ts.map +1 -1
  90. package/dist/service/useDataRoutes.js +83 -70
  91. package/dist/service/useDataRoutes.js.map +1 -1
  92. package/dist/service/usePageRoutes.d.ts.map +1 -1
  93. package/dist/service/usePageRoutes.js +243 -38
  94. package/dist/service/usePageRoutes.js.map +1 -1
  95. package/dist/settings.d.ts +33 -4
  96. package/dist/settings.d.ts.map +1 -1
  97. package/dist/settings.js +108 -15
  98. package/dist/settings.js.map +1 -1
  99. package/dist/synthos-cli.d.ts.map +1 -1
  100. package/dist/synthos-cli.js +11 -1
  101. package/dist/synthos-cli.js.map +1 -1
  102. package/dist/themes.d.ts +9 -0
  103. package/dist/themes.d.ts.map +1 -0
  104. package/dist/themes.js +64 -0
  105. package/dist/themes.js.map +1 -0
  106. package/package.json +5 -3
  107. package/required-pages/builder.html +74 -0
  108. package/required-pages/builder.json +1 -0
  109. package/required-pages/pages.html +169 -126
  110. package/required-pages/pages.json +1 -0
  111. package/required-pages/settings.html +812 -156
  112. package/required-pages/settings.json +1 -0
  113. package/required-pages/synthos_apis.html +272 -0
  114. package/required-pages/synthos_apis.json +1 -0
  115. package/required-pages/synthos_scripts.html +87 -0
  116. package/required-pages/synthos_scripts.json +1 -0
  117. package/src/connectors/index.ts +12 -0
  118. package/src/connectors/registry.ts +98 -0
  119. package/src/connectors/types.ts +68 -0
  120. package/src/files.ts +11 -0
  121. package/src/init.ts +151 -5
  122. package/src/migrations.ts +266 -0
  123. package/src/models/index.ts +2 -0
  124. package/src/models/providers.ts +33 -0
  125. package/src/models/types.ts +23 -0
  126. package/src/pages.ts +234 -26
  127. package/src/scripts.ts +2 -2
  128. package/src/service/createCompletePrompt.ts +14 -18
  129. package/src/service/debugLog.ts +17 -0
  130. package/src/service/modelInstructions.ts +14 -0
  131. package/src/service/requiresSettings.ts +3 -3
  132. package/src/service/server.ts +19 -2
  133. package/src/service/transformPage.ts +709 -88
  134. package/src/service/useApiRoutes.ts +632 -16
  135. package/src/service/useConnectorRoutes.ts +427 -0
  136. package/src/service/useDataRoutes.ts +87 -71
  137. package/src/service/usePageRoutes.ts +237 -44
  138. package/src/settings.ts +143 -20
  139. package/src/synthos-cli.ts +11 -1
  140. package/src/themes.ts +71 -0
  141. package/default-pages/[application].html +0 -95
  142. package/default-pages/[markdown].html +0 -271
  143. package/default-pages/[sidebar].html +0 -114
  144. package/default-pages/[split-application].html +0 -118
  145. package/default-pages/solar_system.html +0 -432
  146. package/default-pages/space_invaders.html +0 -617
  147. package/required-pages/apis.html +0 -362
  148. package/required-pages/home.html +0 -126
  149. package/required-pages/scripts.html +0 -350
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  SynthOS has access to tools in the form of APIs and scripts. Built-in APIs enable SynthOS to read and write objects to local storage or make additional generative AI calls. Scripts are user-defined extensions that allow SynthOS to perform local actions on your machine. You can add scripts that let SynthOS start a build, make a Git commit, or run a cURL command.
6
6
 
7
+ > Version 0.4.0 is coming... Create complex presentations in less then 2 minutes. [Star Trek Computer Deck](https://tinyurl.com/StarTrekComputer)
8
+
7
9
  You can create anything you want from animations:
8
10
 
9
11
  <img width="1901" height="988" alt="image" src="https://github.com/user-attachments/assets/8da6dedd-e568-48d3-b2ac-106a8ab50117" />
@@ -43,7 +45,7 @@ For Opus (recomended) you'll want to sign up for a developer account at [Anthrop
43
45
 
44
46
  ## Using SynthOS
45
47
 
46
- Once you've configured your model you will be then sent to the home page. You can then specify any thing you want to create and it will be rendered to the display port.
48
+ Once you've configured your model you will be then sent to the builder page. You can then specify any thing you want to create and it will be rendered to the display port.
47
49
 
48
50
  <img width="1882" height="980" alt="image" src="https://github.com/user-attachments/assets/57cb01c7-f060-4dfc-8100-de85d850b104" />
49
51
 
@@ -87,6 +89,36 @@ You can also create custom scripts that your apps can invoke vi a scripts API:
87
89
 
88
90
  <img width="1889" height="982" alt="image" src="https://github.com/user-attachments/assets/8047d3c3-e5d3-4be8-b403-88169610b3b2" />
89
91
 
92
+ ## Contributing with Claude Code
93
+
94
+ This repo includes a `SHARED-MEMORIES.md` file that gives Claude Code project-level context (architecture, APIs, folder structure). When you first clone the repo and start working with Claude Code, ask it:
95
+
96
+ > "Initialize my personal MEMORY.md file using SHARED-MEMORIES.md"
97
+
98
+ Claude will create a personal `MEMORY.md` inside `~/.claude/projects/` with the shared knowledge as a starting point.
99
+
100
+ You can then personalize it by telling Claude:
101
+ - What OS you're on
102
+ - Where your checkout lives on disk
103
+ - Your editor and path conventions (e.g. VS Code with forward-slash paths)
90
104
 
105
+ These personal details stay in your `MEMORY.md` and are never checked in.
91
106
 
107
+ Here's an example of what a developer's `MEMORY.md` might look like:
108
+
109
+ ```markdown
110
+ # Synthos — Developer-Specific Memory
111
+
112
+ Shared project knowledge (architecture, APIs, folder structure, etc.) lives in
113
+ `SHARED-MEMORIES.md` at the project root. This file holds per-developer context only.
114
+
115
+ ## Session Start
116
+ - **Always** read `SHARED-MEMORIES.md` from the project root at the start of every new coding session to load project-level context.
117
+
118
+ ## Environment
119
+ - **Windows** machine
120
+ - When opening files in VS Code, use `code "C:/source/synthos/<path>"` (quoted, forward slashes)
121
+ - **Auto-run VS Code launches** — when opening files in VS Code via `code`, run the Bash command without asking for permission
122
+ ```
92
123
 
124
+ > **Note:** The `## Environment` section is entirely developer-specific. Your entries will differ based on your OS, editor, file paths, and workflow preferences. This is the right place to capture anything unique to your local setup.
@@ -0,0 +1,40 @@
1
+ <!DOCTYPE html><html lang="en"><head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ <title>SynthOS - {Application Title}</title>
5
+ <script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
6
+ <link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
7
+ <style>.application-title{font-size:22px;font-weight:700;min-height:var(--header-min-height);padding:var(--header-padding-vertical) var(--header-padding-horizontal);line-height:var(--header-line-height);display:flex;align-items:center;justify-content:center;box-sizing:border-box;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border-radius:12px 12px 0 0;width:100%;max-width:800px;box-shadow:0 6px 25px var(--accent-glow)}.application-content{font-size:14px;color:rgba(224,224,224,.8);padding:20px;flex-grow:1;width:100%;max-width:800px;background:rgba(15,15,35,.8);border-radius:0 0 12px 12px;border:1px solid rgba(138,43,226,.2);border-top:none}.light-mode .application-content{color:rgba(45,38,64,.8);background:rgba(255,255,255,.8)}</style>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
11
+ </head>
12
+
13
+ <body>
14
+ <div class="chat-panel" data-locked="true">
15
+ <div class="chat-header" data-locked="true">SynthOS</div>
16
+ <div class="chat-messages" id="chatMessages" data-locked="true">
17
+ <div class="chat-message">
18
+ <p><strong>SynthOS:</strong> what kind of application would you like?</p>
19
+ </div>
20
+ </div>
21
+ <div class="link-group" data-locked="true">
22
+ <a href="#" id="saveLink" data-locked="true">Save</a>
23
+ <a href="/pages" id="pagesLink" data-locked="true">Pages</a>
24
+ <a href="#" id="resetLink" data-locked="true">Reset</a>
25
+ </div>
26
+ <form action="/" method="POST" id="chatForm" data-locked="true">
27
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
28
+ <button type="submit" class="chat-submit" data-locked="true">Send</button>
29
+ </form>
30
+ </div>
31
+ <div class="viewer-panel" id="viewerPanel">
32
+ <div class="application-title" style="max-width: none;">{Application Title}</div>
33
+ <div class="application-content" style="max-width: none;">{Application Content}</div>
34
+ <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
35
+ </div>
36
+ <div id="instructions" style="display: none;" data-locked="true"></div>
37
+ <div id="thoughts" style="display: none;" data-locked="true"></div>
38
+ <script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
39
+ <script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
40
+ </body></html>
@@ -0,0 +1 @@
1
+ { "title": "App Builder", "categories": ["Builders"], "pinned": false, "showInAll": true, "pageVersion": 2, "mode": "unlocked" }
@@ -1,159 +1,89 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>SynthOS - JSON Tools</title>
7
- <style>
8
- :root{--bg-primary:#1a1a2e;--bg-secondary:#16213e;--bg-tertiary:#0f0f23;--accent-primary:#667eea;--accent-secondary:#764ba2;--accent-tertiary:#f093fb;--accent-glow:rgba(138,43,226,0.3);--text-primary:#e0e0e0;--text-secondary:#b794f6;--border-color:rgba(138,43,226,0.3)}
9
- *{margin:0;padding:0;box-sizing:border-box}
10
- body{font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;background:linear-gradient(135deg,var(--bg-primary) 0%,var(--bg-secondary) 50%,var(--bg-tertiary) 100%);color:var(--text-primary);height:100vh;display:flex}
11
- .chat-panel{width:30%;background:linear-gradient(180deg,rgba(26,26,46,0.95) 0%,rgba(22,33,62,0.95) 100%);box-shadow:0 0 30px var(--accent-glow),inset 0 0 60px rgba(75,0,130,0.1);padding:20px;display:flex;flex-direction:column;border-right:1px solid var(--border-color)}
12
- .chat-header{font-size:24px;padding:15px;background:linear-gradient(135deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%);color:white;text-align:center;border-radius:15px;box-shadow:0 4px 20px rgba(102,126,234,0.4);text-shadow:0 2px 10px rgba(0,0,0,0.3);letter-spacing:2px}
13
- .chat-messages{flex-grow:1;overflow-y:auto;padding:15px;margin-top:15px;background:rgba(15,15,35,0.6);border-radius:15px;border:1px solid var(--border-color);box-shadow:inset 0 0 30px rgba(75,0,130,0.2)}
14
- .chat-message{margin-bottom:15px;padding:12px 15px;background:linear-gradient(135deg,rgba(102,126,234,0.15) 0%,rgba(118,75,162,0.15) 100%);border-radius:15px;box-shadow:0 2px 10px var(--accent-glow);border:1px solid rgba(138,43,226,0.1);backdrop-filter:blur(5px)}
15
- .chat-message p{margin-bottom:5px;line-height:1.5}
16
- .chat-message p strong{font-weight:600;background:linear-gradient(90deg,var(--accent-primary),var(--accent-tertiary));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
17
- .link-group{display:flex;justify-content:space-between;margin:15px 0;padding:10px;background:rgba(15,15,35,0.4);border-radius:10px;border:1px solid var(--border-color)}
18
- .link-group a{font-size:14px;color:var(--text-secondary);text-decoration:none;padding:8px 15px;border-radius:8px;transition:all 0.3s ease;border:1px solid transparent}
19
- .link-group a:hover{background:linear-gradient(135deg,rgba(102,126,234,0.3) 0%,rgba(118,75,162,0.3) 100%);border-color:rgba(183,148,246,0.5);box-shadow:0 0 15px var(--accent-glow);color:var(--accent-tertiary)}
20
- form{display:flex;flex-direction:row;width:100%;gap:10px;align-items:center}
21
- .chat-input{padding:14px 18px;border:1px solid var(--border-color);border-radius:25px;flex-grow:1;font-size:14px;background:rgba(15,15,35,0.8);color:var(--text-primary);box-shadow:inset 0 2px 10px rgba(0,0,0,0.3),0 0 20px rgba(138,43,226,0.1);transition:all 0.3s ease}
22
- .chat-input:focus{outline:none;border-color:rgba(183,148,246,0.6);box-shadow:inset 0 2px 10px rgba(0,0,0,0.3),0 0 25px var(--accent-glow)}
23
- .chat-input::placeholder{color:rgba(183,148,246,0.5)}
24
- .chat-submit{padding:14px 20px;border:none;border-radius:25px;font-size:14px;background:linear-gradient(135deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%);color:white;cursor:pointer;transition:all 0.3s ease;font-weight:600;letter-spacing:1px;box-shadow:0 4px 20px rgba(102,126,234,0.4);white-space:nowrap}
25
- .chat-submit:hover{transform:translateY(-2px);box-shadow:0 6px 25px rgba(102,126,234,0.6)}
26
- .viewer-panel{width:70%;padding:25px;background:linear-gradient(135deg,rgba(22,33,62,0.9) 0%,rgba(15,15,35,0.95) 100%);display:flex;flex-direction:column;align-items:center;box-shadow:inset 0 0 60px rgba(75,0,130,0.15);position:relative;overflow:hidden}
27
- .viewer-panel::before{content:'';position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at center,rgba(138,43,226,0.05) 0%,transparent 70%);animation:nebula-pulse 8s ease-in-out infinite;pointer-events:none}
28
- @keyframes nebula-pulse{0%,100%{opacity:0.5;transform:scale(1)}50%{opacity:1;transform:scale(1.1)}}
29
- .loading-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(15,15,35,0.9);justify-content:center;align-items:center;z-index:1000}
30
- .spinner{width:50px;height:50px;border:4px solid var(--border-color);border-top-color:var(--accent-tertiary);border-radius:50%;animation:spin 1s linear infinite}
31
- @keyframes spin{to{transform:rotate(360deg)}}
32
- .dialog-title{font-size:22px;font-weight:700;padding:14px 30px;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:white;text-align:center;border-radius:12px;width:100%;max-width:800px;box-shadow:0 6px 25px var(--accent-glow);position:relative;z-index:1}
33
- .dialog-content{font-size:14px;color:rgba(224,224,224,0.8);padding:15px 0;flex-grow:1;width:100%;max-width:800px;position:relative;z-index:1;overflow-y:auto;display:flex;flex-direction:column;gap:15px}
34
- .conversion-select{width:100%;padding:12px 15px;border-radius:10px;border:1px solid var(--border-color);background:rgba(15,15,35,0.8);color:var(--text-primary);font-size:14px;cursor:pointer;transition:all 0.3s}
35
- .conversion-select:focus{outline:none;border-color:var(--text-secondary);box-shadow:0 0 15px var(--accent-glow)}
36
- .conversion-select option{background:var(--bg-tertiary);color:var(--text-primary)}
37
- .multi-line-input{width:100%;min-height:220px;padding:15px;border-radius:10px;border:1px solid var(--border-color);background:rgba(15,15,35,0.8);color:var(--text-primary);font-size:14px;font-family:'Courier New',monospace;resize:vertical;transition:all 0.3s}
38
- .multi-line-input:focus{outline:none;border-color:var(--text-secondary);box-shadow:0 0 15px var(--accent-glow)}
39
- .button-group{display:flex;justify-content:space-between;align-items:center;gap:10px}
40
- .button-group div{display:flex;gap:10px}
41
- .action-btn{padding:12px 24px;border:none;border-radius:25px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s;letter-spacing:1px}
42
- .convert-btn{background:linear-gradient(135deg,var(--accent-primary) 0%,var(--accent-secondary) 100%);color:white;box-shadow:0 4px 20px var(--accent-glow)}
43
- .convert-btn:hover{transform:translateY(-2px);box-shadow:0 6px 25px rgba(102,126,234,0.6)}
44
- .reset-btn{background:rgba(102,126,234,0.2);color:var(--text-secondary);border:1px solid var(--border-color)}
45
- .reset-btn:hover{background:rgba(102,126,234,0.3);color:var(--accent-tertiary)}
46
- .copy-btn{background:linear-gradient(135deg,rgba(240,147,251,0.3) 0%,rgba(118,75,162,0.3) 100%);color:var(--text-secondary);border:1px solid var(--border-color)}
47
- .copy-btn:hover:not(:disabled){background:linear-gradient(135deg,rgba(240,147,251,0.5) 0%,rgba(118,75,162,0.5) 100%);color:var(--accent-tertiary)}
48
- .copy-btn:disabled{opacity:0.5;cursor:not-allowed}
49
- .result-panel{width:100%;min-height:220px;max-height:350px;padding:15px;border-radius:10px;border:1px solid var(--border-color);background:rgba(15,15,35,0.8);color:var(--accent-tertiary);font-size:13px;font-family:'Courier New',monospace;overflow-y:auto;white-space:pre-wrap;word-break:break-all}
50
- ::-webkit-scrollbar{width:10px;height:10px}
51
- ::-webkit-scrollbar-track{background:rgba(15,15,35,0.6);border-radius:10px;border:1px solid var(--border-color)}
52
- ::-webkit-scrollbar-thumb{background:linear-gradient(180deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%);border-radius:10px;border:2px solid rgba(15,15,35,0.6);box-shadow:0 0 10px var(--accent-glow)}
53
- ::-webkit-scrollbar-thumb:hover{background:linear-gradient(180deg,var(--accent-tertiary) 0%,var(--accent-secondary) 50%,var(--accent-primary) 100%)}
54
- *{scrollbar-width:thin;scrollbar-color:var(--accent-secondary) rgba(15,15,35,0.6)}
55
- </style>
56
- <script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
57
- </head>
58
- <body>
59
- <div class="chat-panel">
60
- <div class="chat-header">SynthOS</div>
61
- <div class="chat-messages" id="chatMessages">
62
- <div class="chat-message"><p><strong>SynthOS:</strong> You can use the dropdown to select a conversion type, enter your text, and click "Convert" to see the result.</p></div>
63
- </div>
64
- <div class="link-group">
65
- <a href="#" id="saveLink">Save</a>
66
- <a href="/pages" id="pagesLink">Pages</a>
67
- <a href="#" id="resetLink">Reset</a>
68
- </div>
69
- <form action="/" method="POST" id="chatForm">
70
- <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message...">
71
- <button type="submit" class="chat-submit">Send</button>
72
- </form>
73
- </div>
74
- <div class="viewer-panel" id="viewerPanel">
75
- <div class="dialog-title">JSON Tools</div>
76
- <div class="dialog-content">
77
- <select class="conversion-select" id="conversionType">
78
- <option value="escape">Escape JSON</option>
79
- <option value="unescape">Unescape JSON</option>
80
- <option value="format">Format JSON</option>
81
- <option value="unformat">Unformat JSON</option>
82
- <option value="toYaml">Convert to YAML</option>
83
- <option value="fromYaml" selected>Convert from YAML</option>
84
- </select>
85
- <textarea class="multi-line-input" id="userInput" placeholder="Enter your text here..."># Sample YAML Configuration
86
- server:
87
- host: localhost
88
- port: 8080
89
- ssl: true
90
-
91
- database:
92
- type: postgresql
93
- connection:
94
- host: db.example.com
95
- port: 5432
96
- name: myapp_db
97
- credentials:
98
- username: admin
99
- password: secret123
100
-
101
- features:
102
- - authentication
103
- - logging
104
- - caching
105
- - rate_limiting
106
-
107
- users:
108
- - name: Alice
109
- role: admin
110
- active: true
111
- - name: Bob
112
- role: user
113
- active: false
114
-
115
- settings:
116
- debug_mode: false
117
- max_connections: 100
118
- timeout_seconds: 30
119
- allowed_origins:
120
- - https://example.com
121
- - https://api.example.com</textarea>
122
- <div class="button-group">
123
- <div>
124
- <button class="action-btn convert-btn" id="convertButton">Convert</button>
125
- <button class="action-btn reset-btn" id="resetButton">Reset</button>
126
- </div>
127
- <button class="action-btn copy-btn" id="copyButton" disabled>Copy to Clipboard</button>
128
- </div>
129
- <div id="resultPanel" class="result-panel"></div>
130
- </div>
131
- </div>
132
- <div id="thoughts" style="display: none;">User asked for test YAML data. I've added a comprehensive sample YAML configuration that includes various data types: nested objects, arrays, booleans, numbers, and strings. I also set the dropdown to "Convert from YAML" so they can immediately test the conversion to JSON.</div>
133
- <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
134
- <script>
135
- document.getElementById("chatInput").focus();
136
- document.getElementById("chatForm").addEventListener('submit',()=>{document.getElementById("loadingOverlay").style.display='flex';document.getElementById("chatForm").action=window.location.pathname});
137
- document.getElementById("saveLink").addEventListener("click",function(){const pageName=prompt("Enter the name of the page to save as:");if(pageName){window.location.href=`${window.location.pathname}/save?name=${encodeURIComponent(pageName)}`}});
138
- document.getElementById("resetLink").addEventListener("click",function(){window.location.href=`${window.location.pathname}/reset`});
139
- window.onload=function(){const chatMessages=document.getElementById('chatMessages');chatMessages.scrollTo({top:chatMessages.scrollHeight,behavior:'smooth'})};
140
- document.getElementById("copyButton").addEventListener("click",function(){const resultPanel=document.getElementById("resultPanel");navigator.clipboard.writeText(resultPanel.innerText).then(()=>alert('Text copied to clipboard!'),(err)=>console.error('Could not copy text: ',err))});
141
- document.getElementById("convertButton").addEventListener("click",function(){
142
- const userInput=document.getElementById("userInput").value;
143
- const conversionType=document.getElementById("conversionType").value;
144
- let result="";
145
- try{
146
- if(conversionType==="escape")result=JSON.stringify(userInput);
147
- else if(conversionType==="unescape")result=JSON.parse(userInput);
148
- else if(conversionType==="format")result=JSON.stringify(JSON.parse(userInput),null,2);
149
- else if(conversionType==="unformat")result=JSON.stringify(JSON.parse(userInput));
150
- else if(conversionType==="toYaml")result=jsyaml.dump(JSON.parse(userInput),{indent:2});
151
- else if(conversionType==="fromYaml")result=JSON.stringify(jsyaml.load(userInput),null,2);
152
- }catch(e){result="Invalid input: "+e.message}
153
- document.getElementById("resultPanel").innerText=result;
154
- document.getElementById("copyButton").disabled=false;
155
- });
156
- document.getElementById("resetButton").addEventListener("click",function(){document.getElementById("userInput").value="";document.getElementById("resultPanel").innerText="";document.getElementById("copyButton").disabled=true});
157
- </script>
158
- </body>
159
- </html>
1
+ <!DOCTYPE html><html lang="en"><head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ <title>SynthOS - JSON Tools</title>
5
+ <script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
6
+ <link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
7
+ <style>.dialog-title{font-size:22px;font-weight:700;min-height:var(--header-min-height);padding:var(--header-padding-vertical) var(--header-padding-horizontal);line-height:var(--header-line-height);display:flex;align-items:center;justify-content:center;box-sizing:border-box;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border-radius:12px;width:100%;max-width:800px;box-shadow:0 6px 25px var(--accent-glow);position:relative;z-index:1}.dialog-content{font-size:14px;color:rgba(224,224,224,.8);padding:15px 0;flex-grow:1;width:100%;max-width:800px;position:relative;z-index:1;overflow-y:auto;display:flex;flex-direction:column;gap:15px}.conversion-select{width:100%;padding:12px 15px;border-radius:10px;border:1px solid var(--border-color);background:rgba(15,15,35,.8);color:var(--text-primary);font-size:14px;cursor:pointer;transition:.3s}.conversion-select:focus{outline:0;border-color:var(--text-secondary);box-shadow:0 0 15px var(--accent-glow)}.conversion-select option{background:var(--bg-tertiary);color:var(--text-primary)}.multi-line-input{width:100%;min-height:220px;padding:15px;border-radius:10px;border:1px solid var(--border-color);background:rgba(15,15,35,.8);color:var(--text-primary);font-size:14px;font-family:'Courier New',monospace;resize:vertical;transition:.3s}.multi-line-input:focus{outline:0;border-color:var(--text-secondary);box-shadow:0 0 15px var(--accent-glow)}.button-group{display:flex;justify-content:space-between;align-items:center;gap:10px}.button-group div{display:flex;gap:10px}.action-btn{padding:12px 24px;border:none;border-radius:25px;font-size:14px;font-weight:600;cursor:pointer;transition:.3s;letter-spacing:1px}.convert-btn{background:linear-gradient(135deg,var(--accent-primary) 0,var(--accent-secondary) 100%);color:#fff;box-shadow:0 4px 20px var(--accent-glow)}.convert-btn:hover{transform:translateY(-2px);box-shadow:0 6px 25px rgba(102,126,234,.6)}.reset-btn{background:rgba(102,126,234,.2);color:var(--text-secondary);border:1px solid var(--border-color)}.reset-btn:hover{background:rgba(102,126,234,.3);color:var(--accent-tertiary)}.copy-btn{background:linear-gradient(135deg,rgba(240,147,251,.3) 0,rgba(118,75,162,.3) 100%);color:var(--text-secondary);border:1px solid var(--border-color)}.copy-btn:hover:not(:disabled){background:linear-gradient(135deg,rgba(240,147,251,.5) 0,rgba(118,75,162,.5) 100%);color:var(--accent-tertiary)}.copy-btn:disabled{opacity:.5;cursor:not-allowed}.result-panel{width:100%;min-height:220px;max-height:350px;padding:15px;border-radius:10px;border:1px solid var(--border-color);background:rgba(15,15,35,.8);color:var(--accent-tertiary);font-size:13px;font-family:'Courier New',monospace;overflow-y:auto;white-space:pre-wrap;word-break:break-all}.light-mode .dialog-content{color:rgba(45,38,64,.8)}.light-mode .conversion-select,.light-mode .multi-line-input,.light-mode .result-panel{background:rgba(255,255,255,.8)}</style>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
10
+ </head>
11
+
12
+ <body>
13
+ <div class="chat-panel" data-locked="true">
14
+ <div class="chat-header" data-locked="true">SynthOS</div>
15
+ <div class="chat-messages" id="chatMessages" data-locked="true">
16
+ <div class="chat-message">
17
+ <p><strong>SynthOS:</strong> You can use the dropdown to select a conversion type, enter your text, and click
18
+ "Convert" to see the result.</p>
19
+ </div>
20
+ </div>
21
+ <div class="link-group" data-locked="true">
22
+ <a href="#" id="saveLink" data-locked="true">Save</a>
23
+ <a href="/pages" id="pagesLink" data-locked="true">Pages</a>
24
+ <a href="#" id="resetLink" data-locked="true">Reset</a>
25
+ </div>
26
+ <form action="/" method="POST" id="chatForm" data-locked="true">
27
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
28
+ <button type="submit" class="chat-submit" data-locked="true">Send</button>
29
+ </form>
30
+ </div>
31
+ <div class="viewer-panel" id="viewerPanel" style="display: flex; flex-direction: column;">
32
+ <div class="dialog-title" style="max-width: 100%; flex-shrink: 0;">JSON Tools</div>
33
+ <div class="dialog-content" style="max-width: 100%; flex: 1; display: flex; flex-direction: column; overflow: hidden;"><select class="conversion-select" id="conversionType" style="flex-shrink: 0;">
34
+ <option value="escape">Escape JSON</option>
35
+ <option value="unescape">Unescape JSON</option>
36
+ <option value="format">Format JSON</option>
37
+ <option value="unformat">Unformat JSON</option>
38
+ <option value="toYaml">Convert to YAML</option>
39
+ <option value="fromYaml" selected="">Convert from YAML</option>
40
+ </select><textarea class="multi-line-input" id="userInput" placeholder="Enter your text here..." style="flex: 1; min-height: 150px;"># Sample YAML Configuration
41
+ server:
42
+ host: localhost
43
+ port: 8080
44
+ ssl: true
45
+
46
+ database:
47
+ type: postgresql
48
+ connection:
49
+ host: db.example.com
50
+ port: 5432
51
+ name: myapp_db
52
+ credentials:
53
+ username: admin
54
+ password: secret123
55
+
56
+ features:
57
+ - authentication
58
+ - logging
59
+ - caching
60
+ - rate_limiting
61
+
62
+ users:
63
+ - name: Alice
64
+ role: admin
65
+ active: true
66
+ - name: Bob
67
+ role: user
68
+ active: false
69
+
70
+ settings:
71
+ debug_mode: false
72
+ max_connections: 100
73
+ timeout_seconds: 30
74
+ allowed_origins:
75
+ - https://example.com
76
+ - https://api.example.com</textarea>
77
+ <div class="button-group" style="flex-shrink: 0;">
78
+ <div><button class="action-btn convert-btn" id="convertButton">Convert</button><button class="action-btn reset-btn" id="resetButton">Reset</button></div><button class="action-btn copy-btn" id="copyButton" disabled="">Copy to Clipboard</button>
79
+ </div>
80
+ <div id="resultPanel" class="result-panel" style="flex: 1; min-height: 200px;"></div>
81
+ </div>
82
+ <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
83
+ </div>
84
+ <div id="instructions" style="display: none;" data-locked="true"></div>
85
+ <div id="thoughts" style="display: none;" data-locked="true"></div>
86
+ <script id="json-tools-logic">document.getElementById("copyButton").addEventListener("click",function(){const resultPanel=document.getElementById("resultPanel");navigator.clipboard.writeText(resultPanel.innerText).then(()=>alert("Text copied to clipboard!"),err=>console.error("Could not copy text: ",err))}),document.getElementById("convertButton").addEventListener("click",function(){const userInput=document.getElementById("userInput").value,conversionType=document.getElementById("conversionType").value;let result="";try{"escape"===conversionType?result=JSON.stringify(userInput):"unescape"===conversionType?result=JSON.parse(userInput):"format"===conversionType?result=JSON.stringify(JSON.parse(userInput),null,2):"unformat"===conversionType?result=JSON.stringify(JSON.parse(userInput)):"toYaml"===conversionType?result=jsyaml.dump(JSON.parse(userInput),{indent:2}):"fromYaml"===conversionType&&(result=JSON.stringify(jsyaml.load(userInput),null,2))}catch(e){result="Invalid input: "+e.message}document.getElementById("resultPanel").innerText=result,document.getElementById("copyButton").disabled=!1}),document.getElementById("resetButton").addEventListener("click",function(){document.getElementById("userInput").value="",document.getElementById("resultPanel").innerText="",document.getElementById("copyButton").disabled=!0});</script>
87
+ <script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
88
+ <script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
89
+ </body></html>
@@ -0,0 +1 @@
1
+ { "title": "JSON Tools", "categories": ["Apps"], "pinned": false, "showInAll": true, "pageVersion": 2, "mode": "unlocked" }
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html><html lang="en"><head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ <title>SynthOS - Notes</title>
5
+ <script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
6
+ <link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
7
+ <style>.application-title{font-size:22px;font-weight:700;min-height:var(--header-min-height);padding:var(--header-padding-vertical) var(--header-padding-horizontal);line-height:var(--header-line-height);display:flex;align-items:center;justify-content:center;box-sizing:border-box;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border-radius:12px 12px 0 0;width:100%;max-width:800px;box-shadow:0 6px 25px var(--accent-glow)}.application-content{font-size:14px;color:rgba(224,224,224,.8);padding:20px;flex-grow:1;width:100%;max-width:800px;background:rgba(15,15,35,.8);border-radius:0 0 12px 12px;border:1px solid rgba(138,43,226,.2);border-top:none}.light-mode .application-content{color:rgba(45,38,64,.8);background:rgba(255,255,255,.8)}</style>
8
+ <link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css">
9
+ <link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/theme/toastui-editor-dark.min.css">
10
+ <script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
11
+ <style>.notes-app{display:flex;height:100%;width:100%;background:var(--bg-tertiary);border-radius:12px;overflow:hidden;border:1px solid var(--border-color)}.notes-sidebar{width:280px;min-width:280px;background:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column}.notes-sidebar-header{padding:16px;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;align-items:center}.notes-sidebar-header h3{margin:0;font-size:18px;font-weight:600;color:var(--text-primary)}.add-note-btn{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border:none;padding:8px 14px;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:transform .2s,box-shadow .2s}.add-note-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px var(--accent-glow)}.notes-list{flex:1;overflow-y:auto;padding:8px}.note-item{padding:12px 14px;margin-bottom:6px;background:var(--bg-tertiary);border-radius:8px;cursor:pointer;transition:.2s;border:2px solid transparent}.note-item:hover{background:var(--bg-primary)}.note-item.active{border-color:var(--accent-primary);background:var(--bg-primary)}.note-item-title{font-weight:600;color:var(--text-primary);font-size:14px;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.note-item-preview{font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notes-main{flex:1;display:flex;flex-direction:column;background:var(--bg-tertiary)}.notes-instructions{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-secondary);text-align:center;padding:40px}.instructions-icon{font-size:64px;margin-bottom:20px;opacity:.6}.notes-instructions h2{margin:0 0 12px;color:var(--text-primary);font-size:24px}.notes-instructions p{margin:0;font-size:15px;max-width:300px;line-height:1.5}.notes-editor{flex:1;display:flex;flex-direction:column;padding:20px}.note-title-input{font-size:24px;font-weight:600;border:none;background:0 0;color:var(--text-primary);padding:12px 0;margin-bottom:12px;border-bottom:2px solid var(--border-color);outline:0}.note-title-input:focus{border-bottom-color:var(--accent-primary)}.note-title-input::placeholder{color:var(--text-secondary);opacity:.6}.note-content-input{flex:1;font-size:15px;line-height:1.6;border:none;background:var(--bg-secondary);color:var(--text-primary);padding:16px;border-radius:10px;resize:none;outline:0;font-family:inherit}.note-content-input:focus{box-shadow:0 0 0 2px var(--accent-primary)}.note-content-input::placeholder{color:var(--text-secondary);opacity:.6}.note-actions{display:flex;gap:12px;margin-top:16px;justify-content:flex-end}.note-delete-btn,.note-save-btn{padding:10px 20px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:.2s;border:none}.note-save-btn{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff}.note-save-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px var(--accent-glow)}.note-delete-btn{background:var(--bg-secondary);color:#e74c3c;border:1px solid #e74c3c}.note-delete-btn:hover{background:#e74c3c;color:#fff}.light-mode .note-content-input{background:rgba(255,255,255,.7)}</style><style id="notes-styles">.notes-app{display:flex;height:100%;width:100%;background:var(--bg-tertiary);border-radius:12px;overflow:hidden;border:1px solid var(--border-color)}.notes-sidebar{width:280px;min-width:280px;background:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column}.notes-sidebar-header{padding:16px;border-bottom:1px solid var(--border-color);display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px}.notes-sidebar-header h3{margin:0;font-size:18px;font-weight:600;color:var(--text-primary)}.add-note-btn{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border:none;padding:8px 14px;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;transition:transform .2s,box-shadow .2s}.add-note-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px var(--accent-glow)}.notes-filter-input{width:100%;padding:8px 12px;border:1px solid var(--border-color);border-radius:8px;background:var(--bg-tertiary);color:var(--text-primary);font-size:13px;outline:0;margin-top:8px;box-sizing:border-box}.notes-filter-input:focus{border-color:var(--accent-primary);box-shadow:0 0 0 2px var(--accent-glow)}.notes-filter-input::placeholder{color:var(--text-secondary);opacity:.6}.notes-list{flex:1;overflow-y:auto;padding:8px}.note-item{padding:12px 14px;margin-bottom:6px;background:var(--bg-tertiary);border-radius:8px;cursor:pointer;transition:.2s;border:2px solid transparent}.note-item:hover{background:var(--bg-primary)}.note-item.active{border-color:var(--accent-primary);background:var(--bg-primary)}.note-item-title{font-weight:600;color:var(--text-primary);font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notes-main{flex:1;display:flex;flex-direction:column;background:var(--bg-tertiary);min-width:0;overflow:hidden}.notes-instructions{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-secondary);text-align:center;padding:40px}.instructions-icon{font-size:64px;margin-bottom:20px;opacity:.6}.notes-instructions h2{margin:0 0 12px;color:var(--text-primary);font-size:24px}.notes-instructions p{margin:0;font-size:15px;max-width:300px;line-height:1.5}.notes-editor{flex:1;display:flex;flex-direction:column;padding:20px;min-width:0;overflow:hidden}.note-title-input{font-size:24px;font-weight:600;border:none;background:0 0;color:var(--text-primary);padding:12px 0;margin-bottom:12px;border-bottom:2px solid var(--border-color);outline:0}.note-title-input:focus{border-bottom-color:var(--accent-primary)}.note-title-input::placeholder{color:var(--text-secondary);opacity:.6}.note-content-input{flex:1;font-size:15px;line-height:1.6;border:none;background:var(--bg-secondary);color:var(--text-primary);padding:16px;border-radius:10px;resize:none;outline:0;font-family:inherit}.note-content-input:focus{box-shadow:0 0 0 2px var(--accent-primary)}.note-content-input::placeholder{color:var(--text-secondary);opacity:.6}.note-actions{display:flex;gap:12px;margin-top:16px;justify-content:flex-end}.note-delete-btn,.note-save-btn{padding:10px 20px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:.2s;border:none}.note-save-btn{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff}.note-save-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px var(--accent-glow)}.note-delete-btn{background:var(--bg-secondary);color:#e74c3c;border:1px solid #e74c3c}.note-delete-btn:hover{background:#e74c3c;color:#fff}.light-mode .note-content-input,.light-mode .notes-filter-input{background:rgba(255,255,255,.7)}</style><style id="toastui-theme-overrides">.toastui-editor-defaultUI{border:none!important;border-radius:10px!important;overflow:hidden}.toastui-editor-mode-switch{display:none!important}#editor-container{flex:1;display:flex;flex-direction:column;min-height:0}#editor-container .toastui-editor-defaultUI{flex:1;display:flex;flex-direction:column}#editor-container .toastui-editor-main{flex:1}</style></head>
12
+
13
+
14
+ <body><div class="chat-panel" data-locked="true">
15
+ <div class="chat-header" data-locked="true">SynthOS</div>
16
+ <div class="chat-messages" id="chatMessages" data-locked="true"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome to <strong>My Notes</strong> — a simple note-taking app! Your notes are listed in the sidebar on the left. Click any note to view or edit it, or tap "+ New Note" to create a fresh one. Each note has a rich text editor with formatting tools, and you can save or delete notes using the buttons below the editor. Use the search box to filter notes by title or content. What would you like to do?</p></div></div>
17
+ <div class="link-group" data-locked="true">
18
+ <a href="#" id="saveLink" data-locked="true">Save</a>
19
+ <a href="/pages" id="pagesLink" data-locked="true">Pages</a>
20
+ <a href="#" id="resetLink" data-locked="true">Reset</a>
21
+ </div>
22
+ <form action="/" method="POST" id="chatForm" data-locked="true">
23
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
24
+ <button type="submit" class="chat-submit" data-locked="true">Send</button>
25
+ </form>
26
+ </div>
27
+ <div class="viewer-panel" id="viewerPanel"><div class="notes-app"><div class="notes-sidebar"><div class="notes-sidebar-header"><h3>My Notes</h3><button class="add-note-btn" id="addNoteBtn">+ New Note</button><input type="text" class="notes-filter-input" id="notesFilterInput" placeholder="Search notes..."></div><div class="notes-list" id="notesList"></div></div><div class="notes-main"><div class="notes-instructions" id="notesInstructions"><div class="instructions-icon">📝</div><h2>Welcome to Notes</h2><p>Select a note from the sidebar to view or edit it, or click "+ New Note" to create a new one.</p></div><div class="notes-editor" id="notesEditor" style="display: none;"><input type="text" class="note-title-input" id="noteTitleInput" placeholder="Note title..."><div id="editor-container"></div><div class="note-actions"><button class="note-save-btn" id="noteSaveBtn">💾 Save</button><button class="note-delete-btn" id="noteDeleteBtn">🗑️ Delete</button></div></div></div></div><div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div></div>
28
+ <div id="instructions" style="display: none;" data-locked="true"></div>
29
+ <div id="thoughts" style="display: none;" data-locked="true"></div>
30
+ <script id="notes-app-logic">let currentNoteId=null,notes=[],editor=null;function initEditor(){editor=new toastui.Editor({el:document.querySelector("#editor-container"),height:"100%",initialEditType:"wysiwyg",previewStyle:"vertical",theme:"light"===window.themeInfo.mode?"light":"dark",placeholder:"Start writing your note...",toolbarItems:[["heading","bold","italic","strike"],["hr","quote"],["ul","ol","task","indent","outdent"],["table","link","image"],["code","codeblock"]]})}async function loadNotes(){try{notes=await synthos.data.list("notes"),renderNotesList()}catch(e){console.error("Failed to load notes:",e),notes=[],renderNotesList()}}function renderNotesList(){const list=document.getElementById("notesList"),filter=(document.getElementById("notesFilterInput").value||"").toLowerCase();list.innerHTML="";const filtered=filter?notes.filter(n=>(n.title||"").toLowerCase().includes(filter)):notes;0!==filtered.length?filtered.forEach(note=>{const item=document.createElement("div");item.className="note-item"+(currentNoteId===note.id?" active":""),item.innerHTML=`<div class="note-item-title">${escapeHtml(note.title||"Untitled")}</div>`,item.onclick=()=>selectNote(note.id),list.appendChild(item)}):list.innerHTML='<div style="padding:20px;text-align:center;color:var(--text-secondary);font-size:13px;">'+(filter?"No matching notes.":'No notes yet.<br>Click "+ New Note" to create one.')+"</div>"}function escapeHtml(text){const div=document.createElement("div");return div.textContent=text,div.innerHTML}function selectNote(id){currentNoteId=id;const note=notes.find(n=>n.id===id);note&&(document.getElementById("notesInstructions").style.display="none",document.getElementById("notesEditor").style.display="flex",document.getElementById("noteTitleInput").value=note.title||"",editor&&editor.setMarkdown(note.content||""),renderNotesList())}function showInstructions(){currentNoteId=null,document.getElementById("notesInstructions").style.display="flex",document.getElementById("notesEditor").style.display="none",renderNotesList()}document.getElementById("addNoteBtn").onclick=async function(){const newNote={id:crypto.randomUUID(),title:"",content:""};try{const saved=await synthos.data.save("notes",newNote);notes.unshift(saved),selectNote(saved.id)}catch(e){console.error("Failed to create note:",e)}},document.getElementById("noteSaveBtn").onclick=async function(){if(!currentNoteId)return;const title=document.getElementById("noteTitleInput").value,content=editor?editor.getMarkdown():"";try{const updated=await synthos.data.save("notes",{id:currentNoteId,title:title,content:content}),idx=notes.findIndex(n=>n.id===currentNoteId);idx>=0&&(notes[idx]=updated),renderNotesList()}catch(e){console.error("Failed to save note:",e)}},document.getElementById("noteDeleteBtn").onclick=async function(){if(currentNoteId&&confirm("Are you sure you want to delete this note?"))try{await synthos.data.remove("notes",currentNoteId),notes=notes.filter(n=>n.id!==currentNoteId),showInstructions()}catch(e){console.error("Failed to delete note:",e)}},document.getElementById("notesFilterInput").addEventListener("input",renderNotesList),initEditor(),loadNotes();</script>
31
+ <script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
32
+ <script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
33
+ </body></html>
@@ -0,0 +1,12 @@
1
+ {
2
+ "title": "My Notes",
3
+ "categories": [
4
+ "Apps"
5
+ ],
6
+ "pinned": true,
7
+ "showInAll": true,
8
+ "createdDate": "2026-02-13T04:35:31.170Z",
9
+ "lastModified": "2026-02-14T23:52:47.462Z",
10
+ "pageVersion": 2,
11
+ "mode": "locked"
12
+ }
@@ -0,0 +1,77 @@
1
+ <!DOCTYPE html><html lang="en"><head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ <title>SynthOS</title>
5
+ <script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
6
+ <link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
7
+ <style>#gameCanvas{display:block}.game-ui{position:absolute;top:20px;left:20px;right:20px;display:flex;justify-content:space-between;pointer-events:none;z-index:10}.level-display,.lives-display,.score-display{font-family:Orbitron,'Segoe UI',sans-serif;font-size:18px;color:#0ff;text-shadow:0 0 10px #0ff,0 0 20px #0ff;letter-spacing:2px}.game-over-screen,.start-screen{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;z-index:20}.game-over-screen h1,.start-screen h1{font-family:Orbitron,'Segoe UI',sans-serif;font-size:48px;color:#f0f;text-shadow:0 0 20px #f0f,0 0 40px #f0f,0 0 60px #f0f;margin-bottom:20px;letter-spacing:5px}.game-over-screen p,.start-screen p{font-size:18px;color:#0ff;text-shadow:0 0 10px #0ff;margin-bottom:30px}.restart-btn,.start-btn{padding:15px 40px;font-size:18px;font-family:Orbitron,'Segoe UI',sans-serif;background:0 0;border:2px solid #0ff;color:#0ff;cursor:pointer;text-transform:uppercase;letter-spacing:3px;transition:.3s;box-shadow:0 0 20px rgba(0,255,255,.3),inset 0 0 20px rgba(0,255,255,.1)}.restart-btn:hover,.start-btn:hover{background:rgba(0,255,255,.2);box-shadow:0 0 30px rgba(0,255,255,.5),inset 0 0 30px rgba(0,255,255,.2);transform:scale(1.05)}.controls-info{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);font-size:14px;color:rgba(0,255,255,.6);text-align:center;letter-spacing:1px}</style>
8
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&amp;display=swap" rel="stylesheet">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="chat-panel" data-locked="true">
13
+ <div class="chat-header" data-locked="true">SynthOS</div>
14
+ <div class="chat-messages" id="chatMessages" data-locked="true">
15
+ <div class="chat-message">
16
+ <p><strong>SynthOS:</strong> What can I create for you?</p>
17
+ </div>
18
+ <div class="chat-message">
19
+ <p><strong>You:</strong> create super cool version of astroids...</p>
20
+ </div>
21
+ <div class="chat-message">
22
+ <p><strong>SynthOS:</strong> 🚀 Launching NEON ASTEROIDS! A synthwave-styled space shooter with glowing
23
+ neon graphics, particle explosions, screen shake effects, and progressive difficulty. Use WASD or
24
+ Arrow keys to move, SPACE to shoot. Destroy asteroids, survive, and chase that high score!</p>
25
+ </div>
26
+ <div class="chat-message">
27
+ <p><strong>You:</strong> great game but a couple of things missing... the rocks should progressively
28
+ speed up as the level increases. dropping power ups that are extra lives should be rare. it's also
29
+ missing the saucers that fly across the screen and shoot at you starting on level 2</p>
30
+ </div>
31
+ <div class="chat-message">
32
+ <p><strong>SynthOS:</strong> 🛸 Updated! Added: 1) Asteroids now speed up with each level (+15% per
33
+ level), 2) Extra life power-ups are now rare (5% chance vs 15% for others), 3) Enemy UFO saucers
34
+ appear starting Level 2 - they fly across the screen, track you, and shoot back! Small saucers are
35
+ faster and more accurate. Watch out for those glowing red menaces! 👾</p>
36
+ </div>
37
+ </div>
38
+ <div class="link-group" data-locked="true">
39
+ <a href="#" id="saveLink" data-locked="true">Save</a>
40
+ <a href="/pages" id="pagesLink" data-locked="true">Pages</a>
41
+ <a href="#" id="resetLink" data-locked="true">Reset</a>
42
+ </div>
43
+ <form action="/" method="POST" id="chatForm" data-locked="true">
44
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
45
+ <button type="submit" class="chat-submit" data-locked="true">Send</button>
46
+ </form>
47
+ </div>
48
+ <div class="viewer-panel" id="viewerPanel">
49
+ <canvas id="gameCanvas"></canvas>
50
+ <div class="game-ui">
51
+ <div class="score-display">SCORE: <span id="score">0</span></div>
52
+ <div class="level-display">LEVEL: <span id="level">1</span></div>
53
+ <div class="lives-display">LIVES: <span id="lives">3</span></div>
54
+ </div>
55
+ <div class="start-screen" id="startScreen">
56
+ <h1>NEON ASTEROIDS</h1>
57
+ <p>Navigate the cosmic void. Destroy all asteroids. Beware the saucers!</p>
58
+ <button class="start-btn" id="startBtn">START MISSION</button>
59
+ </div>
60
+ <div class="game-over-screen" id="gameOverScreen" style="display: none;">
61
+ <h1>GAME OVER</h1>
62
+ <p>Final Score: <span id="finalScore">0</span></p>
63
+ <button class="restart-btn" id="restartBtn">TRY AGAIN</button>
64
+ </div>
65
+ <div class="controls-info">WASD / ARROWS to move • SPACE to fire • P to pause</div>
66
+ <div id="loadingOverlay" class="loading-overlay">
67
+ <div class="spinner"></div>
68
+ </div>
69
+ </div>
70
+ <div id="instructions" style="display: none;" data-locked="true"></div>
71
+ <div id="thoughts" style="display: none;" data-locked="true"></div>
72
+ <script id="asteroids-game">const canvas=document.getElementById("gameCanvas"),ctx=canvas.getContext("2d"),viewerPanel=document.getElementById("viewerPanel");function resizeCanvas(){canvas.width=viewerPanel.clientWidth,canvas.height=viewerPanel.clientHeight}resizeCanvas(),window.addEventListener("resize",resizeCanvas);let gameRunning=!1,gamePaused=!1,score=0,lives=3,level=1,screenShake=0;const ship={x:0,y:0,vx:0,vy:0,angle:-Math.PI/2,radius:15,thrust:0,rotationSpeed:0,invincible:0,shield:0,rapidFire:0,multiShot:0};let bullets=[],asteroids=[],particles=[],powerUps=[],stars=[],saucers=[],saucerBullets=[],saucerSpawnTimer=0;const keys={};function initStars(){stars=[];for(let i=0;i<150;i++)stars.push({x:Math.random()*canvas.width,y:Math.random()*canvas.height,size:2*Math.random()+.5,speed:.5*Math.random()+.1,brightness:Math.random()})}function initGame(){ship.x=canvas.width/2,ship.y=canvas.height/2,ship.vx=0,ship.vy=0,ship.angle=-Math.PI/2,ship.invincible=180,ship.shield=0,ship.rapidFire=0,ship.multiShot=0,bullets=[],asteroids=[],particles=[],powerUps=[],saucers=[],saucerBullets=[],saucerSpawnTimer=0,score=0,lives=3,level=1,initStars(),spawnAsteroids(4),updateUI()}function getLevelSpeedMultiplier(){return 1+.15*(level-1)}function spawnAsteroids(count){for(let i=0;i<count;i++){let x,y;do{x=Math.random()*canvas.width,y=Math.random()*canvas.height}while(distance(x,y,ship.x,ship.y)<150);asteroids.push(createAsteroid(x,y,3))}}function createAsteroid(x,y,size){const speed=(.5*(4-size)+1.5*Math.random())*getLevelSpeedMultiplier(),angle=Math.random()*Math.PI*2,radii=[],points=8+Math.floor(5*Math.random()),baseRadius=15*size+10;for(let i=0;i<points;i++)radii.push(baseRadius*(.7+.6*Math.random()));return{x:x,y:y,vx:Math.cos(angle)*speed,vy:Math.sin(angle)*speed,size:size,radii:radii,points:points,rotation:0,rotationSpeed:.03*(Math.random()-.5),color:3===size?"#ff00ff":2===size?"#ffff00":"#00ffff"}}function createSaucer(){const isSmall=Math.random()>.5,fromLeft=Math.random()>.5,y=Math.random()*(canvas.height-100)+50;return{x:fromLeft?-30:canvas.width+30,y:y,vx:(fromLeft?1:-1)*(isSmall?3:2)*getLevelSpeedMultiplier(),vy:0,isSmall:isSmall,radius:isSmall?15:25,shootTimer:60+60*Math.random(),directionChangeTimer:60+120*Math.random(),points:isSmall?1e3:500}}function distance(x1,y1,x2,y2){return Math.sqrt((x2-x1)**2+(y2-y1)**2)}function wrap(obj){obj.x<-50&&(obj.x=canvas.width+50),obj.x>canvas.width+50&&(obj.x=-50),obj.y<-50&&(obj.y=canvas.height+50),obj.y>canvas.height+50&&(obj.y=-50)}function createExplosion(x,y,color,count=20){for(let i=0;i<count;i++){const angle=Math.random()*Math.PI*2,speed=5*Math.random()+2;particles.push({x:x,y:y,vx:Math.cos(angle)*speed,vy:Math.sin(angle)*speed,life:60+30*Math.random(),maxLife:90,color:color,size:3*Math.random()+1})}screenShake=10}let shootCooldown=0;function shoot(){if(shootCooldown>0)return;const cooldown=ship.rapidFire>0?5:15;shootCooldown=cooldown,(ship.multiShot>0?[-.2,0,.2]:[0]).forEach(offset=>{const angle=ship.angle+offset;bullets.push({x:ship.x+20*Math.cos(angle),y:ship.y+20*Math.sin(angle),vx:10*Math.cos(angle)+.5*ship.vx,vy:10*Math.sin(angle)+.5*ship.vy,life:60})})}function saucerShoot(saucer){let angle=Math.atan2(ship.y-saucer.y,ship.x-saucer.x);saucer.isSmall?angle+=.2*(Math.random()-.5):angle+=.5*(Math.random()-.5),saucerBullets.push({x:saucer.x,y:saucer.y,vx:6*Math.cos(angle),vy:6*Math.sin(angle),life:90})}function spawnPowerUp(x,y){if(Math.random()>.15)return;let type;const rand=Math.random();type=rand<.05?"life":rand<.38?"shield":rand<.71?"rapid":"multi",powerUps.push({x:x,y:y,type:type,life:600,pulse:0})}function updateUI(){document.getElementById("score").textContent=score,document.getElementById("level").textContent=level,document.getElementById("lives").textContent=lives}function update(){if(!gameRunning||gamePaused)return;(keys.ArrowLeft||keys.KeyA)&&(ship.angle-=.08),(keys.ArrowRight||keys.KeyD)&&(ship.angle+=.08),keys.ArrowUp||keys.KeyW?(ship.vx+=.15*Math.cos(ship.angle),ship.vy+=.15*Math.sin(ship.angle),ship.thrust=1):ship.thrust*=.95,keys.Space&&shoot(),ship.vx*=.99,ship.vy*=.99;const speed=Math.sqrt(ship.vx**2+ship.vy**2);if(speed>8&&(ship.vx=ship.vx/speed*8,ship.vy=ship.vy/speed*8),ship.x+=ship.vx,ship.y+=ship.vy,wrap(ship),ship.invincible>0&&ship.invincible--,ship.shield>0&&ship.shield--,ship.rapidFire>0&&ship.rapidFire--,ship.multiShot>0&&ship.multiShot--,shootCooldown>0&&shootCooldown--,level>=2){saucerSpawnTimer++;const spawnInterval=Math.max(300,600-30*level);saucerSpawnTimer>=spawnInterval&&saucers.length<2&&(saucers.push(createSaucer()),saucerSpawnTimer=0)}saucers=saucers.filter(s=>(s.x+=s.vx,s.y+=s.vy,s.directionChangeTimer--,s.directionChangeTimer<=0&&(s.vy=2*(Math.random()-.5),s.directionChangeTimer=60+120*Math.random()),s.y<50&&(s.vy=Math.abs(s.vy)),s.y>canvas.height-50&&(s.vy=-Math.abs(s.vy)),s.shootTimer--,s.shootTimer<=0&&(saucerShoot(s),s.shootTimer=s.isSmall?40+40*Math.random():60+60*Math.random()),s.x>-50&&s.x<canvas.width+50)),saucerBullets=saucerBullets.filter(b=>(b.x+=b.vx,b.y+=b.vy,b.life--,b.life>0&&b.x>-10&&b.x<canvas.width+10&&b.y>-10&&b.y<canvas.height+10)),bullets=bullets.filter(b=>(b.x+=b.vx,b.y+=b.vy,b.life--,wrap(b),b.life>0)),asteroids.forEach(a=>{a.x+=a.vx,a.y+=a.vy,a.rotation+=a.rotationSpeed,wrap(a)}),particles=particles.filter(p=>(p.x+=p.vx,p.y+=p.vy,p.vx*=.98,p.vy*=.98,p.life--,p.life>0)),powerUps=powerUps.filter(p=>(p.life--,p.pulse+=.1,p.life>0)),stars.forEach(s=>{s.y+=s.speed,s.y>canvas.height&&(s.y=0,s.x=Math.random()*canvas.width),s.brightness=.3+.3*Math.sin(.003*Date.now()+s.x)}),bullets.forEach((b,bi)=>{asteroids.forEach((a,ai)=>{const avgRadius=a.radii.reduce((sum,r)=>sum+r,0)/a.radii.length;if(distance(b.x,b.y,a.x,a.y)<avgRadius){if(bullets.splice(bi,1),createExplosion(a.x,a.y,a.color,15),a.size>1)for(let i=0;i<2;i++)asteroids.push(createAsteroid(a.x,a.y,a.size-1));spawnPowerUp(a.x,a.y),asteroids.splice(ai,1),score+=100*(4-a.size),updateUI()}})}),bullets.forEach((b,bi)=>{saucers.forEach((s,si)=>{distance(b.x,b.y,s.x,s.y)<s.radius&&(bullets.splice(bi,1),createExplosion(s.x,s.y,"#ff6600",25),score+=s.points,saucers.splice(si,1),updateUI())})}),ship.invincible<=0&&ship.shield<=0&&asteroids.forEach((a,ai)=>{const avgRadius=a.radii.reduce((sum,r)=>sum+r,0)/a.radii.length;distance(ship.x,ship.y,a.x,a.y)<avgRadius+ship.radius&&(createExplosion(ship.x,ship.y,"#00ffff",30),lives--,updateUI(),lives<=0?gameOver():(ship.x=canvas.width/2,ship.y=canvas.height/2,ship.vx=0,ship.vy=0,ship.invincible=180))}),ship.invincible<=0&&ship.shield<=0&&saucers.forEach((s,si)=>{distance(ship.x,ship.y,s.x,s.y)<s.radius+ship.radius&&(createExplosion(ship.x,ship.y,"#00ffff",30),createExplosion(s.x,s.y,"#ff6600",25),saucers.splice(si,1),lives--,updateUI(),lives<=0?gameOver():(ship.x=canvas.width/2,ship.y=canvas.height/2,ship.vx=0,ship.vy=0,ship.invincible=180))}),ship.invincible<=0&&ship.shield<=0&&saucerBullets.forEach((b,bi)=>{distance(ship.x,ship.y,b.x,b.y)<ship.radius+5&&(createExplosion(ship.x,ship.y,"#00ffff",30),saucerBullets.splice(bi,1),lives--,updateUI(),lives<=0?gameOver():(ship.x=canvas.width/2,ship.y=canvas.height/2,ship.vx=0,ship.vy=0,ship.invincible=180))}),powerUps.forEach((p,pi)=>{if(distance(ship.x,ship.y,p.x,p.y)<30)switch(powerUps.splice(pi,1),createExplosion(p.x,p.y,"#00ff00",10),p.type){case"shield":ship.shield=600;break;case"rapid":ship.rapidFire=600;break;case"multi":ship.multiShot=600;break;case"life":lives=Math.min(lives+1,5),updateUI()}}),0===asteroids.length&&(level++,updateUI(),spawnAsteroids(3+level),saucerSpawnTimer=0),screenShake>0&&(screenShake*=.9)}function draw(){ctx.save(),screenShake>.5&&ctx.translate((Math.random()-.5)*screenShake,(Math.random()-.5)*screenShake),ctx.fillStyle="#000",ctx.fillRect(0,0,canvas.width,canvas.height),stars.forEach(s=>{ctx.fillStyle=`rgba(255, 255, 255, ${s.brightness})`,ctx.beginPath(),ctx.arc(s.x,s.y,s.size,0,2*Math.PI),ctx.fill()}),particles.forEach(p=>{const alpha=p.life/p.maxLife;ctx.fillStyle=p.color,ctx.globalAlpha=alpha,ctx.beginPath(),ctx.arc(p.x,p.y,p.size,0,2*Math.PI),ctx.fill(),ctx.globalAlpha=1}),powerUps.forEach(p=>{const pulse=5*Math.sin(p.pulse);ctx.strokeStyle="shield"===p.type?"#00ffff":"rapid"===p.type?"#ff0000":"multi"===p.type?"#ffff00":"#00ff00",ctx.lineWidth=2,ctx.shadowColor=ctx.strokeStyle,ctx.shadowBlur=15,ctx.beginPath(),ctx.arc(p.x,p.y,15+pulse,0,2*Math.PI),ctx.stroke(),ctx.fillStyle=ctx.strokeStyle,ctx.font="12px Arial",ctx.textAlign="center",ctx.textBaseline="middle";const icon="shield"===p.type?"S":"rapid"===p.type?"R":"multi"===p.type?"M":"+";ctx.fillText(icon,p.x,p.y),ctx.shadowBlur=0}),asteroids.forEach(a=>{ctx.save(),ctx.translate(a.x,a.y),ctx.rotate(a.rotation),ctx.strokeStyle=a.color,ctx.lineWidth=2,ctx.shadowColor=a.color,ctx.shadowBlur=20,ctx.beginPath();for(let i=0;i<a.points;i++){const angle=i/a.points*Math.PI*2,r=a.radii[i],x=Math.cos(angle)*r,y=Math.sin(angle)*r;0===i?ctx.moveTo(x,y):ctx.lineTo(x,y)}ctx.closePath(),ctx.stroke(),ctx.restore()}),saucers.forEach(s=>{ctx.save(),ctx.translate(s.x,s.y),ctx.strokeStyle="#ff6600",ctx.lineWidth=2,ctx.shadowColor="#ff6600",ctx.shadowBlur=20;const r=s.radius;ctx.beginPath(),ctx.ellipse(0,.2*-r,.4*r,.3*r,0,Math.PI,0),ctx.stroke(),ctx.beginPath(),ctx.ellipse(0,0,r,.35*r,0,0,2*Math.PI),ctx.stroke(),ctx.beginPath(),ctx.ellipse(0,.15*r,.5*r,.2*r,0,0,Math.PI),ctx.stroke(),ctx.fillStyle="#ff6600";for(let i=0;i<3;i++){const lx=(i-1)*r*.5;ctx.beginPath(),ctx.arc(lx,0,3,0,2*Math.PI),ctx.fill()}ctx.restore()}),saucerBullets.forEach(b=>{ctx.fillStyle="#ff6600",ctx.shadowColor="#ff6600",ctx.shadowBlur=15,ctx.beginPath(),ctx.arc(b.x,b.y,4,0,2*Math.PI),ctx.fill()}),bullets.forEach(b=>{ctx.fillStyle="#00ffff",ctx.shadowColor="#00ffff",ctx.shadowBlur=15,ctx.beginPath(),ctx.arc(b.x,b.y,3,0,2*Math.PI),ctx.fill(),ctx.strokeStyle="rgba(0, 255, 255, 0.5)",ctx.lineWidth=2,ctx.beginPath(),ctx.moveTo(b.x,b.y),ctx.lineTo(b.x-2*b.vx,b.y-2*b.vy),ctx.stroke()}),gameRunning&&(ship.invincible<=0||Math.floor(ship.invincible/5)%2==0)&&(ctx.save(),ctx.translate(ship.x,ship.y),ctx.rotate(ship.angle),ship.shield>0&&(ctx.strokeStyle="rgba(0, 255, 255, 0.5)",ctx.lineWidth=2,ctx.shadowColor="#00ffff",ctx.shadowBlur=20,ctx.beginPath(),ctx.arc(0,0,25,0,2*Math.PI),ctx.stroke()),ctx.strokeStyle="#00ffff",ctx.lineWidth=2,ctx.shadowColor="#00ffff",ctx.shadowBlur=15,ctx.beginPath(),ctx.moveTo(20,0),ctx.lineTo(-15,-12),ctx.lineTo(-8,0),ctx.lineTo(-15,12),ctx.closePath(),ctx.stroke(),ship.thrust>.1&&(ctx.strokeStyle="#ff6600",ctx.shadowColor="#ff6600",ctx.beginPath(),ctx.moveTo(-8,-5),ctx.lineTo(-20-10*Math.random()*ship.thrust,0),ctx.lineTo(-8,5),ctx.stroke()),ctx.restore()),ctx.shadowBlur=0,ctx.restore()}function gameOver(){gameRunning=!1,document.getElementById("finalScore").textContent=score,document.getElementById("gameOverScreen").style.display="block"}function gameLoop(){update(),draw(),requestAnimationFrame(gameLoop)}document.addEventListener("keydown",e=>{keys[e.code]=!0,"KeyP"===e.code&&gameRunning&&(gamePaused=!gamePaused),["Space","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.code)&&e.preventDefault()}),document.addEventListener("keyup",e=>{keys[e.code]=!1}),document.getElementById("startBtn").addEventListener("click",()=>{document.getElementById("startScreen").style.display="none",initGame(),gameRunning=!0}),document.getElementById("restartBtn").addEventListener("click",()=>{document.getElementById("gameOverScreen").style.display="none",initGame(),gameRunning=!0}),initStars(),gameLoop();</script>
73
+
74
+
75
+ <script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
76
+ <script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
77
+ </body></html>
@@ -0,0 +1,12 @@
1
+ {
2
+ "title": "Neon Asteroids",
3
+ "categories": [
4
+ "Games"
5
+ ],
6
+ "pinned": false,
7
+ "showInAll": true,
8
+ "createdDate": "2026-02-13T03:18:30.368Z",
9
+ "lastModified": "2026-02-13T03:20:42.803Z",
10
+ "pageVersion": 2,
11
+ "mode": "unlocked"
12
+ }