···
192
+
background-color: rgba(0, 0, 0, 0.7);
195
+
transition: opacity 0.2s ease-in-out;
199
+
background-color: #ffffff;
200
+
border: 2px solid #808080;
202
+
inset -2px -2px #c0c0c0,
203
+
inset 2px 2px #404040,
204
+
0 0 10px rgba(0, 0, 0, 0.5);
208
+
animation: fadeIn 0.2s ease-in-out;
209
+
position: relative; /* Ensure proper positioning of close button */
212
+
@keyframes fadeIn {
215
+
transform: translateY(-20px);
219
+
transform: translateY(0);
233
+
.input-group label {
234
+
display: inline-block;
238
+
.input-group input {
239
+
font-family: "Courier Prime", "Courier New", monospace;
241
+
background: #ffffff;
242
+
border: 2px inset #c0c0c0;
248
+
border: 1px solid #c0c0c0;
255
+
justify-content: space-between;
258
+
border-bottom: 1px solid #c0c0c0;
259
+
background-color: #f0f0f0;
266
+
border: 1px solid #c0c0c0;
267
+
border-radius: 3px;
271
+
background-color: #c0c0c0;
275
+
position: absolute;
282
+
background: #d0d0d0;
283
+
border: 1px solid #808080;
284
+
border-radius: 3px;
287
+
text-align: center;
290
+
inset -1px -1px #404040,
291
+
inset 1px 1px #ffffff;
294
+
.modal-close:hover {
295
+
background-color: #c0c0c0;
299
+
.modal-close:active {
301
+
inset 1px 1px #404040,
302
+
inset -1px -1px #ffffff;
185
-
<div class="terminal">
186
-
<div class="title-bar">
187
-
C:\PLASTIC.EXE - [Self-Modifying System v2.1]
307
+
<!-- Help Modal -->
308
+
<div id="help-modal" class="modal">
309
+
<div class="modal-content">
310
+
<div class="title-bar">
313
+
class="modal-close"
314
+
onclick="hideModal('help-modal')"
317
+
aria-label="Close help modal"
321
+
<div class="modal-body">
322
+
<h2>Keyboard Shortcuts:</h2>
324
+
<li><strong>F1</strong>: Show this help screen</li>
326
+
<strong>F2</strong>: Save the current page state
328
+
<li><strong>F3</strong>: Load a saved page state</li>
329
+
<li><strong>Esc</strong>: Close modals</li>
331
+
<strong>Ctrl+Enter</strong>: Execute code in the
190
-
<div class="content">
191
-
<p class="prompt">C:\PLASTIC> DIR</p>
336
+
<h2>How to Use PLASTIC:</h2>
339
+
Type a description of what you want to change in the
343
+
Press Ctrl+Enter or click the "GENERATE & EXECUTE"
347
+
PLASTIC will modify itself according to your
350
+
<li>Use F2 to save your modified system</li>
351
+
<li>Use F3 to load a previously saved state</li>
193
-
<div class="file-listing">
194
-
<div class="file-line">
195
-
Volume in drive C is PLASTIC-SYS
354
+
<h2>About PLASTIC:</h2>
356
+
PLASTIC.EXE is a self-modifying system that lets you
357
+
transform its interface and functionality through
358
+
natural language commands.
361
+
<div style="text-align: center; margin-top: 15px">
362
+
<div class="button" onclick="hideModal('help-modal')">
197
-
<div class="file-line">Directory of C:\PLASTIC</div>
198
-
<div class="file-line"></div>
199
-
<div class="file-line">
200
-
<span class="dir">SYSTEM <DIR></span> 12-15-95
369
+
<!-- Save Modal -->
370
+
<div id="save-modal" class="modal">
371
+
<div class="modal-content">
372
+
<div class="title-bar">
375
+
class="modal-close"
376
+
onclick="hideModal('save-modal')"
379
+
aria-label="Close save modal"
383
+
<div class="modal-body">
384
+
<h2>Save Current State</h2>
385
+
<div class="input-group">
386
+
<label for="save-name">Name:</label>
390
+
placeholder="Enter save name"
203
-
<div class="file-line">
204
-
<span class="exe">PLASTIC EXE</span> 24,576 12-15-95
396
+
justify-content: center;
401
+
<div class="button" onclick="saveCurrentHTML()">
404
+
<div class="button" onclick="hideModal('save-modal')">
207
-
<div class="file-line">
208
-
<span class="txt">README TXT</span> 1,024 12-15-95 3:42p
210
-
<div class="file-line">
211
-
<span class="txt">CONFIG SYS</span> 512 12-15-95 3:42p
408
+
<div id="save-message"></div>
410
+
<h2>Saved States</h2>
411
+
<div id="saved-list" class="save-list"></div>
416
+
<!-- Load Modal -->
417
+
<div id="load-modal" class="modal">
418
+
<div class="modal-content">
419
+
<div class="title-bar">
422
+
class="modal-close"
423
+
onclick="hideModal('load-modal')"
426
+
aria-label="Close load modal"
430
+
<div class="modal-body">
431
+
<h2>Load Saved State</h2>
432
+
<div id="load-list" class="save-list"></div>
433
+
<div id="load-message"></div>
434
+
<div style="text-align: center; margin-top: 15px">
435
+
<div class="button" onclick="hideModal('load-modal')">
213
-
<div class="file-line">4 File(s) 26,112 bytes</div>
214
-
<div class="file-line">524,288 bytes free</div>
442
+
<div class="terminal">
443
+
<div class="title-bar">
444
+
C:\PLASTIC.EXE - [Self-Modifying System v3.0]
447
+
<div class="content">
<p class="prompt">C:\PLASTIC> PLASTIC.EXE</p>
···
<h2>System Information:</h2>
229
-
<p>PLASTIC v2.1 - Self-Modifying Code System</p>
230
-
<p>Copyright (C) 1995 DUNKIRK Corp.</p>
460
+
<p>PLASTIC v3.0 - Self-Modifying Code System</p>
463
+
<a href="https://dunkirk.sh">DUNKIRK Corp (Kieran Klukas)</a
All rights reserved. Licensed to: REGISTERED USER under MIT
···
<li>Supports EGA/VGA graphics modes</li>
275
-
<h2>System Commands:</h2>
277
-
<span class="command">HELP</span>
278
-
<span class="command">DIR</span>
279
-
<span class="command">EDIT</span>
280
-
<span class="command">RUN</span>
281
-
<span class="command">EXIT</span>
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
288
-
<p>Runtime: 00:13:37 | Memory: 589K free | CPU: 80486DX-33</p>
290
-
WARNING: Unauthorized modification may corrupt system files
515
+
Runtime: <span id="runtime">00:13:37</span> | Memory:
516
+
<span id="memory">589K free</span> | CPU: 80486DX-33
293
-
<p class="prompt">C:\PLASTIC> <span class="cursor">_</span></p>
297
-
<span>F1=Help F2=Save F3=Load F10=Menu</span>
298
-
<span>12:34 PM</span>
522
+
><span class="hotkey" onclick="showHelp()">F1=Help</span>
523
+
<span class="hotkey" onclick="showSaveModal()"
526
+
<span class="hotkey" onclick="showLoadModal()"
531
+
<span id="time">12:00</span>
// Tool call system for AI to interact with the page
305
-
replaceElement: function(selector, newHTML) {
538
+
replaceElement: function (selector, newHTML) {
const element = document.querySelector(selector);
element.outerHTML = newHTML;
309
-
return { success: true, message: `Replaced element: ${selector}` };
544
+
message: `Replaced element: ${selector}`,
311
-
return { success: false, message: `Element not found: ${selector}` };
549
+
message: `Element not found: ${selector}`,
314
-
updateElement: function(selector, newContent) {
553
+
updateElement: function (selector, newContent) {
const element = document.querySelector(selector);
element.innerHTML = newContent;
318
-
return { success: true, message: `Updated element: ${selector}` };
559
+
message: `Updated element: ${selector}`,
320
-
return { success: false, message: `Element not found: ${selector}` };
564
+
message: `Element not found: ${selector}`,
323
-
addElement: function(parentSelector, newHTML, position = 'beforeend') {
568
+
addElement: function (
571
+
position = "beforeend",
const parent = document.querySelector(parentSelector);
parent.insertAdjacentHTML(position, newHTML);
327
-
return { success: true, message: `Added element to: ${parentSelector}` };
578
+
message: `Added element to: ${parentSelector}`,
329
-
return { success: false, message: `Parent not found: ${parentSelector}` };
583
+
message: `Parent not found: ${parentSelector}`,
332
-
removeElement: function(selector) {
587
+
removeElement: function (selector) {
const element = document.querySelector(selector);
336
-
return { success: true, message: `Removed element: ${selector}` };
593
+
message: `Removed element: ${selector}`,
338
-
return { success: false, message: `Element not found: ${selector}` };
598
+
message: `Element not found: ${selector}`,
341
-
updateStyle: function(selector, styleObj) {
602
+
updateStyle: function (selector, styleObj) {
const element = document.querySelector(selector);
Object.assign(element.style, styleObj);
345
-
return { success: true, message: `Updated styles for: ${selector}` };
608
+
message: `Updated styles for: ${selector}`,
347
-
return { success: false, message: `Element not found: ${selector}` };
613
+
message: `Element not found: ${selector}`,
350
-
executeJS: function(code) {
617
+
executeJS: function (code) {
const result = eval(code);
353
-
return { success: true, message: 'JavaScript executed', result: result };
622
+
message: "JavaScript executed",
355
-
return { success: false, message: `JS Error: ${error.message}` };
628
+
message: `JS Error: ${error.message}`,
function executeToolCall(toolCall) {
const { function: func, arguments: args } = toolCall;
362
-
console.log('Executing tool call:', func, args);
636
+
console.log("Executing tool call:", func, args);
if (window.toolCallbacks[func]) {
// Handle different function signatures
367
-
if (func === 'removeElement') {
641
+
if (func === "removeElement") {
// removeElement expects just a selector
369
-
const selector = args.selector || Object.keys(args)[0] || Object.values(args)[0];
645
+
Object.keys(args)[0] ||
646
+
Object.values(args)[0];
return window.toolCallbacks[func](selector);
371
-
} else if (func === 'updateStyle' && args.styleObj) {
372
-
return window.toolCallbacks[func](args.selector, args.styleObj);
373
-
} else if (func === 'executeJS') {
648
+
} else if (func === "updateStyle" && args.styleObj) {
649
+
return window.toolCallbacks[func](
653
+
} else if (func === "executeJS") {
return window.toolCallbacks[func](args.code);
375
-
} else if (func === 'updateElement') {
376
-
return window.toolCallbacks[func](args.selector, args.newContent);
377
-
} else if (func === 'replaceElement') {
378
-
return window.toolCallbacks[func](args.selector, args.newHTML);
379
-
} else if (func === 'addElement') {
380
-
return window.toolCallbacks[func](args.parentSelector, args.newHTML, args.position);
655
+
} else if (func === "updateElement") {
656
+
return window.toolCallbacks[func](
660
+
} else if (func === "replaceElement") {
661
+
return window.toolCallbacks[func](
665
+
} else if (func === "addElement") {
666
+
return window.toolCallbacks[func](
667
+
args.parentSelector,
382
-
// Fallback: use all argument values in order
383
-
return window.toolCallbacks[func](...Object.values(args));
672
+
return window.toolCallbacks[func](
673
+
...Object.values(args),
386
-
return { success: false, message: `Error executing ${func}: ${error.message}` };
679
+
message: `Error executing ${func}: ${error.message}`,
return { success: false, message: `Unknown tool: ${func}` };
···
// Clean up response and extract JSON if present
let cleanResponse = generatedContent.trim();
// Remove markdown formatting
cleanResponse = cleanResponse
.replace(/```json\n?/g, "")
// Try to extract JSON from mixed content
463
-
const jsonMatch = cleanResponse.match(/\{[\s\S]*"tool_calls"[\s\S]*\}/);
757
+
const jsonMatch = cleanResponse.match(
758
+
/\{[\s\S]*"tool_calls"[\s\S]*\}/,
cleanResponse = jsonMatch[0];
468
-
console.log('Cleaned response:', cleanResponse);
764
+
console.log("Cleaned response:", cleanResponse);
766
+
// For safety, preprocess JavaScript code in JSON to escape problematic characters
767
+
cleanResponse = cleanResponse.replace(
768
+
/"code"\s*:\s*(`|")([^`"]*?)(`|")/g,
769
+
function (match, q1, code, q3) {
770
+
// Replace all literal backslashes with double backslashes in the code string
771
+
const escapedCode = code.replace(/\\/g, "\\\\");
772
+
return `"code":${q1}${escapedCode}${q3}`;
// Check if response contains tool calls
472
-
const toolResponse = JSON.parse(cleanResponse);
473
-
if (toolResponse.tool_calls && Array.isArray(toolResponse.tool_calls)) {
778
+
const toolResponse = safeJsonParse(cleanResponse);
780
+
toolResponse.tool_calls &&
781
+
Array.isArray(toolResponse.tool_calls)
for (const toolCall of toolResponse.tool_calls) {
const result = executeToolCall(toolCall);
479
-
console.log('Tool call result:', result);
788
+
console.log("Tool call result:", result);
statusDiv.textContent = `EXECUTED ${results.length} COMMANDS`;
// Send feedback to AI about tool results
484
-
setTimeout(() => sendToolFeedback(userPrompt, results), 100);
794
+
() => sendToolFeedback(userPrompt, results),
throw new Error("Invalid tool call format");
489
-
console.log('JSON parse error:', jsonError);
490
-
console.log('Raw response:', generatedContent);
491
-
console.log('Attempting to parse as HTML...');
801
+
console.log("JSON parse error:", jsonError);
802
+
console.log("Raw response:", generatedContent);
803
+
console.log("Attempting to parse as HTML...");
// Not JSON, treat as HTML code
let cleanCode = generatedContent
.replace(/```html\n?/g, "")
statusDiv.textContent = "INJECTING CODE...";
499
-
document.querySelector(".content").innerHTML += cleanCode;
811
+
document.querySelector(".content").innerHTML +=
statusDiv.textContent = "CODE EXECUTION SUCCESSFUL";
···
async function sendToolFeedback(originalPrompt, toolResults) {
const statusDiv = document.getElementById("statusDisplay");
statusDiv.textContent = "SENDING FEEDBACK TO AI...";
const currentPageHTML = document.documentElement.outerHTML;
527
-
const resultsText = toolResults.map(r =>
528
-
`${r.success ? 'โ' : 'โ'} ${r.message}${r.result ? ` (result: ${r.result})` : ''}`
840
+
const resultsText = toolResults
843
+
`${r.success ? "โ" : "โ"} ${r.message}${r.result ? ` (result: ${r.result})` : ""}`,
const response = await fetch(
"https://ai.hackclub.com/chat/completions",
···
550
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
867
+
`HTTP ${response.status}: ${response.statusText}`,
const data = await response.json();
556
-
if (data.choices && data.choices[0] && data.choices[0].message) {
877
+
data.choices[0].message
followUpContent = data.choices[0].message.content;
} else if (data.content) {
followUpContent = data.content;
···
followUpContent = followUpContent.trim();
567
-
console.log('Follow-up response:', followUpContent);
889
+
console.log("Follow-up response:", followUpContent);
569
-
if (followUpContent === "COMPLETE") {
892
+
followUpContent === "COMPLETE" ||
893
+
followUpContent === '"COMPLETE"'
statusDiv.textContent = "AI SATISFIED - TASK COMPLETE";
571
-
setTimeout(() => statusDiv.textContent = "", 3000);
896
+
setTimeout(() => (statusDiv.textContent = ""), 3000);
// Process follow-up commands
statusDiv.textContent = "AI MAKING ADJUSTMENTS...";
// Try to parse as tool calls
580
-
const jsonMatch = followUpContent.match(/\{[\s\S]*"tool_calls"[\s\S]*\}/);
905
+
const jsonMatch = followUpContent.match(
906
+
/\{[\s\S]*"tool_calls"[\s\S]*\}/,
582
-
const toolResponse = JSON.parse(jsonMatch[0]);
583
-
if (toolResponse.tool_calls && Array.isArray(toolResponse.tool_calls)) {
909
+
// Preprocess JavaScript code in JSON to escape problematic characters
910
+
let cleanJson = jsonMatch[0].replace(
911
+
/"code"\s*:\s*(`|")([^`"]*?)(`|")/g,
912
+
function (match, q1, code, q3) {
913
+
// Replace all literal backslashes with double backslashes in the code string
914
+
const escapedCode = code.replace(
918
+
return `"code":${q1}${escapedCode}${q3}`;
922
+
const toolResponse = safeJsonParse(cleanJson);
924
+
toolResponse.tool_calls &&
925
+
Array.isArray(toolResponse.tool_calls)
const followUpResults = [];
for (const toolCall of toolResponse.tool_calls) {
const result = executeToolCall(toolCall);
followUpResults.push(result);
588
-
console.log('Follow-up tool result:', result);
932
+
"Follow-up tool result:",
statusDiv.textContent = `AI EXECUTED ${followUpResults.length} ADJUSTMENTS`;
···
let cleanCode = followUpContent
.replace(/```html\n?/g, "")
597
-
document.querySelector(".content").innerHTML += cleanCode;
598
-
statusDiv.textContent = "AI ADDED FOLLOW-UP CONTENT";
943
+
document.querySelector(".content").innerHTML +=
945
+
statusDiv.textContent =
946
+
"AI ADDED FOLLOW-UP CONTENT";
601
-
console.log('Follow-up parsing error:', error);
949
+
console.log("Follow-up parsing error:", error);
statusDiv.textContent = "AI FEEDBACK ERROR";
605
-
setTimeout(() => statusDiv.textContent = "", 4000);
953
+
setTimeout(() => (statusDiv.textContent = ""), 4000);
955
+
statusDiv.textContent = `FEEDBACK ERROR: ${error.message}`;
956
+
console.error("Feedback error:", error);
957
+
setTimeout(() => (statusDiv.textContent = ""), 3000);
961
+
// Debug helper for JSON parsing issues
962
+
function safeJsonParse(jsonString) {
964
+
return JSON.parse(jsonString);
608
-
statusDiv.textContent = `FEEDBACK ERROR: ${error.message}`;
609
-
console.error('Feedback error:', error);
610
-
setTimeout(() => statusDiv.textContent = "", 3000);
966
+
console.error("JSON parse error:", error);
967
+
console.error("Problem JSON:", jsonString);
968
+
// Try to escape any unescaped control characters
969
+
const escapedJson = jsonString.replace(
970
+
/[\u0000-\u001F]/g,
975
+
"0000" + match.charCodeAt(0).toString(16)
981
+
return JSON.parse(escapedJson);
982
+
} catch (secondError) {
984
+
"Second parse attempt failed:",
987
+
throw error; // Throw the original error
992
+
function deleteSave(key) {
993
+
if (confirm("Are you sure you want to delete this save?")) {
994
+
localStorage.removeItem(key);
995
+
// Refresh the load modal to show updated list
1000
+
// Handle keyboard shortcuts
1001
+
function setupKeyboardHandlers() {
1002
+
const codeEditor = document.getElementById("codeEditor");
1004
+
// Remove any previous event listeners first (to avoid duplicates)
1005
+
codeEditor.removeEventListener("keydown", editorKeyHandler);
1006
+
// Add a new event listener
1007
+
codeEditor.addEventListener("keydown", editorKeyHandler);
1009
+
console.error("codeEditor element not found, will retry");
1010
+
// Retry after a short delay
1011
+
setTimeout(setupKeyboardHandlers, 100);
1014
+
// Global keyboard shortcuts
1015
+
document.addEventListener("keydown", function (e) {
1017
+
if (e.key === "F1") {
1018
+
e.preventDefault();
1022
+
else if (e.key === "F2") {
1023
+
e.preventDefault();
1027
+
else if (e.key === "F3") {
1028
+
e.preventDefault();
1031
+
// Escape key - Close any open modal
1032
+
else if (e.key === "Escape") {
1033
+
document.querySelectorAll(".modal").forEach((modal) => {
1034
+
if (modal.style.display === "block") {
1035
+
hideModal(modal.id);
1036
+
// Return focus to the editor after closing modal
1038
+
document.getElementById("codeEditor");
1048
+
// Define the editor key handler function
1049
+
function editorKeyHandler(e) {
1050
+
if (e.ctrlKey && e.key === "Enter") {
1051
+
e.preventDefault();
1052
+
generateAndExecute();
614
-
// Handle Ctrl+Enter in textarea
1056
+
// Set up the handlers immediately
1057
+
setupKeyboardHandlers();
1059
+
// Also ensure they're set up when the DOM is fully loaded
1060
+
// Modal functions
1061
+
function showModal(modalId) {
1062
+
const modal = document.getElementById(modalId);
1064
+
modal.style.display = "block";
1065
+
// Prevent body scrolling when modal is open
1066
+
document.body.style.overflow = "hidden";
1068
+
// Make the modal appear with a nice fade-in effect
1069
+
modal.style.opacity = "0";
1070
+
setTimeout(() => {
1071
+
modal.style.opacity = "1";
1074
+
// Add/refresh click handler for closing when clicking outside
1075
+
const outsideClickHandler = function (e) {
1076
+
if (e.target === modal) {
1077
+
hideModal(modalId);
1080
+
// Remove any existing handlers to avoid duplicates
1081
+
modal.removeEventListener("click", outsideClickHandler);
1082
+
modal.addEventListener("click", outsideClickHandler);
1084
+
// Trap focus within the modal
1085
+
const focusableElements = modal.querySelectorAll(
1086
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), .modal-close, .button',
1088
+
if (focusableElements.length > 0) {
1089
+
const firstElement = focusableElements[0];
1090
+
const lastElement =
1091
+
focusableElements[focusableElements.length - 1];
1093
+
// Focus the first element
1094
+
setTimeout(() => {
1095
+
firstElement.focus();
1098
+
// Add key event handler for tab key to trap focus
1099
+
const handleTabKey = function (e) {
1100
+
if (e.key === "Tab") {
1103
+
document.activeElement === firstElement
1105
+
e.preventDefault();
1106
+
lastElement.focus();
1109
+
document.activeElement === lastElement
1111
+
e.preventDefault();
1112
+
firstElement.focus();
1117
+
// Remove any existing handler first
1118
+
modal.removeEventListener("keydown", handleTabKey);
1119
+
modal.addEventListener("keydown", handleTabKey);
1121
+
// Store the handler on the modal object for later cleanup
1122
+
modal._tabHandler = handleTabKey;
1127
+
function hideModal(modalId) {
1128
+
const modal = document.getElementById(modalId);
1130
+
// Fade out effect
1131
+
modal.style.opacity = "0";
1133
+
// Hide the modal after animation
1134
+
setTimeout(() => {
1135
+
modal.style.display = "none";
1136
+
// Restore body scrolling when modal is closed
1137
+
document.body.style.overflow = "auto";
1140
+
// Return focus to editor when modal is closed
1141
+
const editor = document.getElementById("codeEditor");
1146
+
// Clear any error messages
1147
+
const messageElements = document.querySelectorAll(
1148
+
"#save-message, #load-message",
1150
+
messageElements.forEach((el) => {
1151
+
if (el) el.textContent = "";
1156
+
function showHelp() {
1157
+
showModal("help-modal");
1160
+
function showSaveModal() {
1161
+
document.getElementById("save-name").value = "";
1162
+
showModal("save-modal");
1163
+
// Focus the input field and select any existing text
1164
+
setTimeout(() => {
1165
+
const saveNameInput = document.getElementById("save-name");
1166
+
saveNameInput.focus();
1167
+
saveNameInput.select();
1171
+
function showLoadModal() {
1172
+
// Populate the load list
1173
+
const loadList = document.getElementById("load-list");
1174
+
loadList.innerHTML = "";
1176
+
// Get all keys in localStorage that start with 'plastic-save-'
1178
+
for (let i = 0; i < localStorage.length; i++) {
1179
+
const key = localStorage.key(i);
1180
+
if (key && key.startsWith("plastic-save-")) {
1181
+
const saveName = key.replace("plastic-save-", "");
1182
+
let timestamp = null;
1184
+
// Try to extract timestamp if it's a JSON object
1186
+
const saveData = JSON.parse(
1187
+
localStorage.getItem(key),
1189
+
timestamp = saveData.timestamp;
1191
+
// Old format, no timestamp available
1197
+
timestamp: timestamp,
1202
+
if (saves.length === 0) {
1203
+
loadList.innerHTML =
1204
+
'<div class="save-item">No saves found</div>';
1206
+
// Sort saves by timestamp (newest first) or alphabetically if no timestamp
1207
+
saves.sort((a, b) => {
1208
+
if (a.timestamp && b.timestamp) {
1209
+
return b.timestamp - a.timestamp; // Newest first
1210
+
} else if (a.timestamp) {
1211
+
return -1; // a comes first
1212
+
} else if (b.timestamp) {
1213
+
return 1; // b comes first
1215
+
return a.name.localeCompare(b.name); // Alphabetically
1219
+
saves.forEach((save) => {
1220
+
const saveItem = document.createElement("div");
1221
+
saveItem.className = "save-item";
1223
+
// Format timestamp if available
1225
+
if (save.timestamp) {
1226
+
const date = new Date(save.timestamp);
1227
+
dateStr = ` <span style="font-size: 12px; color: #808080;">(${date.toLocaleString()})</span>`;
1230
+
saveItem.innerHTML = `
1231
+
<span>${save.name}${dateStr}</span>
1233
+
<span class="hotkey" onclick="loadSave('${save.key}')" tabindex="0" role="button" aria-label="Load save ${save.name}">Load</span>
1234
+
<span class="separator">|</span>
1235
+
<span class="hotkey" onclick="deleteSave('${save.key}')" tabindex="0" role="button" aria-label="Delete save ${save.name}">Delete</span>
1238
+
// Add double-click support to load the save directly
1239
+
saveItem.addEventListener("dblclick", () =>
1240
+
loadSave(save.key),
1242
+
loadList.appendChild(saveItem);
1246
+
showModal("load-modal");
1249
+
function saveCurrentHTML() {
1250
+
const saveName = document
1251
+
.getElementById("save-name")
1254
+
const saveMessage = document.getElementById("save-message");
1255
+
saveMessage.textContent =
1256
+
"Please enter a name for your save";
1257
+
saveMessage.style.color = "#ff0000";
1258
+
setTimeout(() => {
1259
+
saveMessage.textContent = "";
1265
+
// Ensure all modals are closed in the saved HTML
1266
+
// First make a clone of the current document state
1267
+
const tempDiv = document.createElement("div");
1268
+
tempDiv.innerHTML = document.documentElement.outerHTML;
1270
+
// Find and hide all modals in the clone
1271
+
const modalElements = tempDiv.querySelectorAll(".modal");
1272
+
modalElements.forEach((modal) => {
1273
+
modal.style.display = "none";
1276
+
// Save the current state
1277
+
const saveData = {
1278
+
html: tempDiv.innerHTML,
1279
+
timestamp: new Date().getTime(),
1283
+
localStorage.setItem(
1284
+
`plastic-save-${saveName}`,
1285
+
JSON.stringify(saveData),
1288
+
const saveMessage = document.getElementById("save-message");
1289
+
saveMessage.textContent = `Saved as "${saveName}"`;
1290
+
saveMessage.style.color = "#008000";
1291
+
setTimeout(() => {
1292
+
saveMessage.textContent = "";
1293
+
hideModal("save-modal");
1296
+
const saveMessage = document.getElementById("save-message");
1297
+
saveMessage.textContent = `Error saving: ${e.message}`;
1298
+
saveMessage.style.color = "#ff0000";
1299
+
setTimeout(() => {
1300
+
saveMessage.textContent = "";
1305
+
// Add keyboard support for modals
document.addEventListener("DOMContentLoaded", function () {
617
-
.getElementById("codeEditor")
618
-
.addEventListener("keydown", function (e) {
619
-
if (e.ctrlKey && e.key === "Enter") {
1307
+
const saveNameInput = document.getElementById("save-name");
1308
+
if (saveNameInput) {
1309
+
// Remove any existing listeners to prevent duplicates
1310
+
const saveInputHandler = function (e) {
1311
+
if (e.key === "Enter") {
621
-
generateAndExecute();
1313
+
saveCurrentHTML();
1317
+
saveNameInput.removeEventListener(
1321
+
saveNameInput.addEventListener("keydown", saveInputHandler);
1325
+
.querySelectorAll(".modal-close")
1326
+
.forEach((closeBtn) => {
1327
+
closeBtn.addEventListener("keydown", function (e) {
1328
+
if (e.key === "Enter" || e.key === " ") {
1329
+
e.preventDefault();
1330
+
const modalId = this.closest(".modal").id;
1331
+
hideModal(modalId);
626
-
// Update time in status bar
627
-
setInterval(() => {
628
-
const now = new Date();
629
-
const timeStr = now.toLocaleTimeString([], {
633
-
const statusBarTime = document.querySelector(
634
-
".status-bar span:last-child",
636
-
if (statusBarTime) {
637
-
statusBarTime.textContent = timeStr;
1337
+
function loadSave(key) {
1339
+
const savedData = localStorage.getItem(key);
1344
+
saveObj = JSON.parse(savedData);
1346
+
saveObj = { html: savedData };
1351
+
"Loading will replace the current page. Continue?",
1354
+
hideModal("load-modal");
1356
+
const scriptToAdd = `
1358
+
document.addEventListener('DOMContentLoaded', function() {
1359
+
// Ensure all modals are hidden
1360
+
document.querySelectorAll('.modal').forEach(modal => {
1361
+
modal.style.display = "none";
1364
+
// Re-setup keyboard handlers
1365
+
if (typeof setupKeyboardHandlers === 'function') {
1366
+
setupKeyboardHandlers();
1372
+
// Load the saved HTML with the added script
1374
+
document.write(saveObj.html + scriptToAdd);
1378
+
const loadMessage =
1379
+
document.getElementById("load-message");
1380
+
loadMessage.textContent = "Save not found";
1381
+
loadMessage.style.color = "#ff0000";
1382
+
setTimeout(() => {
1383
+
loadMessage.textContent = "";
1387
+
const loadMessage = document.getElementById("load-message");
1388
+
loadMessage.textContent = `Error loading: ${e.message}`;
1389
+
loadMessage.style.color = "#ff0000";
1390
+
setTimeout(() => {
1391
+
loadMessage.textContent = "";