+688
-78
index.html
+688
-78
index.html
···
179
179
white-space: pre;
180
180
margin: 10px 0;
181
181
}
182
+
183
+
/* Modal styles */
184
+
.modal {
185
+
display: none;
186
+
position: fixed;
187
+
z-index: 9999;
188
+
left: 0;
189
+
top: 0;
190
+
width: 100%;
191
+
height: 100%;
192
+
background-color: rgba(0, 0, 0, 0.7);
193
+
overflow: auto;
194
+
opacity: 1;
195
+
transition: opacity 0.2s ease-in-out;
196
+
}
197
+
198
+
.modal-content {
199
+
background-color: #ffffff;
200
+
border: 2px solid #808080;
201
+
box-shadow:
202
+
inset -2px -2px #c0c0c0,
203
+
inset 2px 2px #404040,
204
+
0 0 10px rgba(0, 0, 0, 0.5);
205
+
margin: 10% auto;
206
+
width: 80%;
207
+
max-width: 600px;
208
+
animation: fadeIn 0.2s ease-in-out;
209
+
position: relative; /* Ensure proper positioning of close button */
210
+
}
211
+
212
+
@keyframes fadeIn {
213
+
from {
214
+
opacity: 0;
215
+
transform: translateY(-20px);
216
+
}
217
+
to {
218
+
opacity: 1;
219
+
transform: translateY(0);
220
+
}
221
+
}
222
+
223
+
.modal-body {
224
+
padding: 15px;
225
+
max-height: 70vh;
226
+
overflow-y: auto;
227
+
}
228
+
229
+
.input-group {
230
+
margin: 10px 0;
231
+
}
232
+
233
+
.input-group label {
234
+
display: inline-block;
235
+
width: 80px;
236
+
}
237
+
238
+
.input-group input {
239
+
font-family: "Courier Prime", "Courier New", monospace;
240
+
padding: 5px;
241
+
background: #ffffff;
242
+
border: 2px inset #c0c0c0;
243
+
}
244
+
245
+
.save-list {
246
+
max-height: 200px;
247
+
overflow-y: auto;
248
+
border: 1px solid #c0c0c0;
249
+
margin: 10px 0;
250
+
padding: 5px;
251
+
}
252
+
253
+
.save-item {
254
+
display: flex;
255
+
justify-content: space-between;
256
+
margin: 5px 0;
257
+
padding: 8px;
258
+
border-bottom: 1px solid #c0c0c0;
259
+
background-color: #f0f0f0;
260
+
}
261
+
262
+
.hotkey {
263
+
cursor: pointer;
264
+
padding: 2px 5px;
265
+
margin: 0 2px;
266
+
border: 1px solid #c0c0c0;
267
+
border-radius: 3px;
268
+
}
269
+
270
+
.hotkey:hover {
271
+
background-color: #c0c0c0;
272
+
}
273
+
274
+
.modal-close {
275
+
position: absolute;
276
+
right: 10px;
277
+
top: 2px;
278
+
cursor: pointer;
279
+
font-size: 16px;
280
+
font-weight: bold;
281
+
color: #000000;
282
+
background: #d0d0d0;
283
+
border: 1px solid #808080;
284
+
border-radius: 3px;
285
+
width: 20px;
286
+
height: 20px;
287
+
text-align: center;
288
+
line-height: 18px;
289
+
box-shadow:
290
+
inset -1px -1px #404040,
291
+
inset 1px 1px #ffffff;
292
+
}
293
+
294
+
.modal-close:hover {
295
+
background-color: #c0c0c0;
296
+
color: #ff0000;
297
+
}
298
+
299
+
.modal-close:active {
300
+
box-shadow:
301
+
inset 1px 1px #404040,
302
+
inset -1px -1px #ffffff;
303
+
}
182
304
</style>
183
305
</head>
184
306
<body>
307
+
<!-- Help Modal -->
308
+
<div id="help-modal" class="modal">
309
+
<div class="modal-content">
310
+
<div class="title-bar">
311
+
PLASTIC.EXE - Help
312
+
<span
313
+
class="modal-close"
314
+
onclick="hideModal('help-modal')"
315
+
tabindex="0"
316
+
role="button"
317
+
aria-label="Close help modal"
318
+
>✕</span
319
+
>
320
+
</div>
321
+
<div class="modal-body">
322
+
<h2>Keyboard Shortcuts:</h2>
323
+
<ul>
324
+
<li><strong>F1</strong>: Show this help screen</li>
325
+
<li>
326
+
<strong>F2</strong>: Save the current page state
327
+
</li>
328
+
<li><strong>F3</strong>: Load a saved page state</li>
329
+
<li><strong>Esc</strong>: Close modals</li>
330
+
<li>
331
+
<strong>Ctrl+Enter</strong>: Execute code in the
332
+
editor
333
+
</li>
334
+
</ul>
335
+
336
+
<h2>How to Use PLASTIC:</h2>
337
+
<ol>
338
+
<li>
339
+
Type a description of what you want to change in the
340
+
editor
341
+
</li>
342
+
<li>
343
+
Press Ctrl+Enter or click the "GENERATE & EXECUTE"
344
+
button
345
+
</li>
346
+
<li>
347
+
PLASTIC will modify itself according to your
348
+
description
349
+
</li>
350
+
<li>Use F2 to save your modified system</li>
351
+
<li>Use F3 to load a previously saved state</li>
352
+
</ol>
353
+
354
+
<h2>About PLASTIC:</h2>
355
+
<p>
356
+
PLASTIC.EXE is a self-modifying system that lets you
357
+
transform its interface and functionality through
358
+
natural language commands.
359
+
</p>
360
+
</div>
361
+
<div style="text-align: center; margin-top: 15px">
362
+
<div class="button" onclick="hideModal('help-modal')">
363
+
CLOSE
364
+
</div>
365
+
</div>
366
+
</div>
367
+
</div>
368
+
369
+
<!-- Save Modal -->
370
+
<div id="save-modal" class="modal">
371
+
<div class="modal-content">
372
+
<div class="title-bar">
373
+
PLASTIC.EXE - Save
374
+
<span
375
+
class="modal-close"
376
+
onclick="hideModal('save-modal')"
377
+
tabindex="0"
378
+
role="button"
379
+
aria-label="Close save modal"
380
+
>✕</span
381
+
>
382
+
</div>
383
+
<div class="modal-body">
384
+
<h2>Save Current State</h2>
385
+
<div class="input-group">
386
+
<label for="save-name">Name:</label>
387
+
<input
388
+
type="text"
389
+
id="save-name"
390
+
placeholder="Enter save name"
391
+
/>
392
+
</div>
393
+
<div
394
+
style="
395
+
display: flex;
396
+
justify-content: center;
397
+
gap: 10px;
398
+
margin-top: 15px;
399
+
"
400
+
>
401
+
<div class="button" onclick="saveCurrentHTML()">
402
+
SAVE
403
+
</div>
404
+
<div class="button" onclick="hideModal('save-modal')">
405
+
CANCEL
406
+
</div>
407
+
</div>
408
+
<div id="save-message"></div>
409
+
410
+
<h2>Saved States</h2>
411
+
<div id="saved-list" class="save-list"></div>
412
+
</div>
413
+
</div>
414
+
</div>
415
+
416
+
<!-- Load Modal -->
417
+
<div id="load-modal" class="modal">
418
+
<div class="modal-content">
419
+
<div class="title-bar">
420
+
PLASTIC.EXE - Load
421
+
<span
422
+
class="modal-close"
423
+
onclick="hideModal('load-modal')"
424
+
tabindex="0"
425
+
role="button"
426
+
aria-label="Close load modal"
427
+
>✕</span
428
+
>
429
+
</div>
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')">
436
+
CANCEL
437
+
</div>
438
+
</div>
439
+
</div>
440
+
</div>
441
+
</div>
185
442
<div class="terminal">
186
443
<div class="title-bar">
187
-
C:\PLASTIC.EXE - [Self-Modifying System v2.1]
444
+
C:\PLASTIC.EXE - [Self-Modifying System v3.0]
188
445
</div>
189
446
190
447
<div class="content">
···
200
457
>
201
458
202
459
<h2>System Information:</h2>
203
-
<p>PLASTIC v2.1 - Self-Modifying Code System</p>
460
+
<p>PLASTIC v3.0 - Self-Modifying Code System</p>
204
461
<p>Copyright (C) 1995 DUNKIRK Corp.</p>
205
462
<p>
206
463
All rights reserved. Licensed to: REGISTERED USER under MIT
···
257
514
</div>
258
515
259
516
<div class="status-bar">
260
-
<span>F1=Help F2=Save F3=Load F10=Menu</span>
261
-
<span>12:00</span>
517
+
<span
518
+
><span class="hotkey" onclick="showHelp()">F1=Help</span>
519
+
<span class="hotkey" onclick="showSaveModal()"
520
+
>F2=Save</span
521
+
>
522
+
<span class="hotkey" onclick="showLoadModal()"
523
+
>F3=Load</span
524
+
>
525
+
F10=Menu</span
526
+
>
527
+
<span id="time">12:00</span>
262
528
</div>
263
529
</div>
264
530
···
399
665
args.position,
400
666
);
401
667
} else {
402
-
// Fallback: use all argument values in order
403
668
return window.toolCallbacks[func](
404
669
...Object.values(args),
405
670
);
···
495
760
console.log("Cleaned response:", cleanResponse);
496
761
497
762
// For safety, preprocess JavaScript code in JSON to escape problematic characters
498
-
cleanResponse = cleanResponse.replace(/"code"\s*:\s*(`|")([^`"]*?)(`|")/g, function(match, q1, code, q3) {
499
-
// Replace all literal backslashes with double backslashes in the code string
500
-
const escapedCode = code.replace(/\\/g, "\\\\");
501
-
return `"code":${q1}${escapedCode}${q3}`;
502
-
});
763
+
cleanResponse = cleanResponse.replace(
764
+
/"code"\s*:\s*(`|")([^`"]*?)(`|")/g,
765
+
function (match, q1, code, q3) {
766
+
// Replace all literal backslashes with double backslashes in the code string
767
+
const escapedCode = code.replace(/\\/g, "\\\\");
768
+
return `"code":${q1}${escapedCode}${q3}`;
769
+
},
770
+
);
503
771
504
772
// Check if response contains tool calls
505
773
try {
···
635
903
);
636
904
if (jsonMatch) {
637
905
// Preprocess JavaScript code in JSON to escape problematic characters
638
-
let cleanJson = jsonMatch[0].replace(/"code"\s*:\s*(`|")([^`"]*?)(`|")/g, function(match, q1, code, q3) {
639
-
// Replace all literal backslashes with double backslashes in the code string
640
-
const escapedCode = code.replace(/\\/g, "\\\\");
641
-
return `"code":${q1}${escapedCode}${q3}`;
642
-
});
643
-
906
+
let cleanJson = jsonMatch[0].replace(
907
+
/"code"\s*:\s*(`|")([^`"]*?)(`|")/g,
908
+
function (match, q1, code, q3) {
909
+
// Replace all literal backslashes with double backslashes in the code string
910
+
const escapedCode = code.replace(
911
+
/\\/g,
912
+
"\\\\",
913
+
);
914
+
return `"code":${q1}${escapedCode}${q3}`;
915
+
},
916
+
);
917
+
644
918
const toolResponse = safeJsonParse(cleanJson);
645
919
if (
646
920
toolResponse.tool_calls &&
···
688
962
console.error("JSON parse error:", error);
689
963
console.error("Problem JSON:", jsonString);
690
964
// Try to escape any unescaped control characters
691
-
const escapedJson = jsonString.replace(/[\u0000-\u001F]/g, match => {
692
-
return '\\u' + ('0000' + match.charCodeAt(0).toString(16)).slice(-4);
693
-
});
965
+
const escapedJson = jsonString.replace(
966
+
/[\u0000-\u001F]/g,
967
+
(match) => {
968
+
return (
969
+
"\\u" +
970
+
(
971
+
"0000" + match.charCodeAt(0).toString(16)
972
+
).slice(-4)
973
+
);
974
+
},
975
+
);
694
976
try {
695
977
return JSON.parse(escapedJson);
696
978
} catch (secondError) {
697
-
console.error("Second parse attempt failed:", secondError);
979
+
console.error(
980
+
"Second parse attempt failed:",
981
+
secondError,
982
+
);
698
983
throw error; // Throw the original error
699
984
}
700
985
}
701
986
}
702
-
703
-
// Handle Ctrl+Enter in textarea
704
-
function setupCtrlEnterHandler() {
987
+
988
+
function deleteSave(key) {
989
+
if (confirm("Are you sure you want to delete this save?")) {
990
+
localStorage.removeItem(key);
991
+
// Refresh the load modal to show updated list
992
+
showLoadModal();
993
+
}
994
+
}
995
+
996
+
// Handle keyboard shortcuts
997
+
function setupKeyboardHandlers() {
705
998
const codeEditor = document.getElementById("codeEditor");
706
999
if (codeEditor) {
707
1000
// Remove any previous event listeners first (to avoid duplicates)
708
-
codeEditor.removeEventListener("keydown", ctrlEnterHandler);
1001
+
codeEditor.removeEventListener("keydown", editorKeyHandler);
709
1002
// Add a new event listener
710
-
codeEditor.addEventListener("keydown", ctrlEnterHandler);
1003
+
codeEditor.addEventListener("keydown", editorKeyHandler);
711
1004
} else {
712
1005
console.error("codeEditor element not found, will retry");
713
1006
// Retry after a short delay
714
-
setTimeout(setupCtrlEnterHandler, 100);
1007
+
setTimeout(setupKeyboardHandlers, 100);
715
1008
}
1009
+
1010
+
// Global keyboard shortcuts
1011
+
document.addEventListener("keydown", function (e) {
1012
+
// F1 key - Help
1013
+
if (e.key === "F1") {
1014
+
e.preventDefault();
1015
+
showHelp();
1016
+
}
1017
+
// F2 key - Save
1018
+
else if (e.key === "F2") {
1019
+
e.preventDefault();
1020
+
showSaveModal();
1021
+
}
1022
+
// F3 key - Load
1023
+
else if (e.key === "F3") {
1024
+
e.preventDefault();
1025
+
showLoadModal();
1026
+
}
1027
+
// Escape key - Close any open modal
1028
+
else if (e.key === "Escape") {
1029
+
document.querySelectorAll(".modal").forEach((modal) => {
1030
+
if (modal.style.display === "block") {
1031
+
hideModal(modal.id);
1032
+
// Return focus to the editor after closing modal
1033
+
const editor =
1034
+
document.getElementById("codeEditor");
1035
+
if (editor) {
1036
+
editor.focus();
1037
+
}
1038
+
}
1039
+
});
1040
+
}
1041
+
});
716
1042
}
717
-
718
-
// Define the handler function separately so it can be added/removed consistently
719
-
function ctrlEnterHandler(e) {
1043
+
1044
+
// Define the editor key handler function
1045
+
function editorKeyHandler(e) {
720
1046
if (e.ctrlKey && e.key === "Enter") {
721
1047
e.preventDefault();
722
1048
generateAndExecute();
723
1049
}
724
1050
}
725
-
726
-
// Set up the handler immediately
727
-
setupCtrlEnterHandler();
728
-
729
-
// Also ensure it's set up when the DOM is fully loaded
730
-
window.addEventListener("DOMContentLoaded", setupCtrlEnterHandler);
731
-
window.addEventListener("load", setupCtrlEnterHandler);
1051
+
1052
+
// Set up the handlers immediately
1053
+
setupKeyboardHandlers();
1054
+
1055
+
// Also ensure they're set up when the DOM is fully loaded
1056
+
// Modal functions
1057
+
function showModal(modalId) {
1058
+
const modal = document.getElementById(modalId);
1059
+
if (modal) {
1060
+
modal.style.display = "block";
1061
+
// Prevent body scrolling when modal is open
1062
+
document.body.style.overflow = "hidden";
1063
+
1064
+
// Make the modal appear with a nice fade-in effect
1065
+
modal.style.opacity = "0";
1066
+
setTimeout(() => {
1067
+
modal.style.opacity = "1";
1068
+
}, 10);
1069
+
1070
+
// Add/refresh click handler for closing when clicking outside
1071
+
const outsideClickHandler = function (e) {
1072
+
if (e.target === modal) {
1073
+
hideModal(modalId);
1074
+
}
1075
+
};
1076
+
// Remove any existing handlers to avoid duplicates
1077
+
modal.removeEventListener("click", outsideClickHandler);
1078
+
modal.addEventListener("click", outsideClickHandler);
1079
+
1080
+
// Trap focus within the modal
1081
+
const focusableElements = modal.querySelectorAll(
1082
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), .modal-close, .button',
1083
+
);
1084
+
if (focusableElements.length > 0) {
1085
+
const firstElement = focusableElements[0];
1086
+
const lastElement =
1087
+
focusableElements[focusableElements.length - 1];
1088
+
1089
+
// Focus the first element
1090
+
setTimeout(() => {
1091
+
firstElement.focus();
1092
+
}, 100);
1093
+
1094
+
// Add key event handler for tab key to trap focus
1095
+
const handleTabKey = function (e) {
1096
+
if (e.key === "Tab") {
1097
+
if (
1098
+
e.shiftKey &&
1099
+
document.activeElement === firstElement
1100
+
) {
1101
+
e.preventDefault();
1102
+
lastElement.focus();
1103
+
} else if (
1104
+
!e.shiftKey &&
1105
+
document.activeElement === lastElement
1106
+
) {
1107
+
e.preventDefault();
1108
+
firstElement.focus();
1109
+
}
1110
+
}
1111
+
};
1112
+
1113
+
// Remove any existing handler first
1114
+
modal.removeEventListener("keydown", handleTabKey);
1115
+
modal.addEventListener("keydown", handleTabKey);
1116
+
1117
+
// Store the handler on the modal object for later cleanup
1118
+
modal._tabHandler = handleTabKey;
1119
+
}
1120
+
}
1121
+
}
1122
+
1123
+
function hideModal(modalId) {
1124
+
const modal = document.getElementById(modalId);
1125
+
if (modal) {
1126
+
// Fade out effect
1127
+
modal.style.opacity = "0";
1128
+
1129
+
// Hide the modal after animation
1130
+
setTimeout(() => {
1131
+
modal.style.display = "none";
1132
+
// Restore body scrolling when modal is closed
1133
+
document.body.style.overflow = "auto";
1134
+
}, 200);
1135
+
1136
+
// Return focus to editor when modal is closed
1137
+
const editor = document.getElementById("codeEditor");
1138
+
if (editor) {
1139
+
editor.focus();
1140
+
}
1141
+
1142
+
// Clear any error messages
1143
+
const messageElements = document.querySelectorAll(
1144
+
"#save-message, #load-message",
1145
+
);
1146
+
messageElements.forEach((el) => {
1147
+
if (el) el.textContent = "";
1148
+
});
1149
+
}
1150
+
}
1151
+
1152
+
function showHelp() {
1153
+
showModal("help-modal");
1154
+
}
1155
+
1156
+
function showSaveModal() {
1157
+
document.getElementById("save-name").value = "";
1158
+
showModal("save-modal");
1159
+
// Focus the input field and select any existing text
1160
+
setTimeout(() => {
1161
+
const saveNameInput = document.getElementById("save-name");
1162
+
saveNameInput.focus();
1163
+
saveNameInput.select();
1164
+
}, 100);
1165
+
}
1166
+
1167
+
function showLoadModal() {
1168
+
// Populate the load list
1169
+
const loadList = document.getElementById("load-list");
1170
+
loadList.innerHTML = "";
1171
+
1172
+
// Get all keys in localStorage that start with 'plastic-save-'
1173
+
const saves = [];
1174
+
for (let i = 0; i < localStorage.length; i++) {
1175
+
const key = localStorage.key(i);
1176
+
if (key && key.startsWith("plastic-save-")) {
1177
+
const saveName = key.replace("plastic-save-", "");
1178
+
let timestamp = null;
1179
+
1180
+
// Try to extract timestamp if it's a JSON object
1181
+
try {
1182
+
const saveData = JSON.parse(
1183
+
localStorage.getItem(key),
1184
+
);
1185
+
timestamp = saveData.timestamp;
1186
+
} catch (e) {
1187
+
// Old format, no timestamp available
1188
+
}
1189
+
1190
+
saves.push({
1191
+
key,
1192
+
name: saveName,
1193
+
timestamp: timestamp,
1194
+
});
1195
+
}
1196
+
}
1197
+
1198
+
if (saves.length === 0) {
1199
+
loadList.innerHTML =
1200
+
'<div class="save-item">No saves found</div>';
1201
+
} else {
1202
+
// Sort saves by timestamp (newest first) or alphabetically if no timestamp
1203
+
saves.sort((a, b) => {
1204
+
if (a.timestamp && b.timestamp) {
1205
+
return b.timestamp - a.timestamp; // Newest first
1206
+
} else if (a.timestamp) {
1207
+
return -1; // a comes first
1208
+
} else if (b.timestamp) {
1209
+
return 1; // b comes first
1210
+
} else {
1211
+
return a.name.localeCompare(b.name); // Alphabetically
1212
+
}
1213
+
});
1214
+
1215
+
saves.forEach((save) => {
1216
+
const saveItem = document.createElement("div");
1217
+
saveItem.className = "save-item";
1218
+
1219
+
// Format timestamp if available
1220
+
let dateStr = "";
1221
+
if (save.timestamp) {
1222
+
const date = new Date(save.timestamp);
1223
+
dateStr = ` <span style="font-size: 12px; color: #808080;">(${date.toLocaleString()})</span>`;
1224
+
}
1225
+
1226
+
saveItem.innerHTML = `
1227
+
<span>${save.name}${dateStr}</span>
1228
+
<div>
1229
+
<span class="hotkey" onclick="loadSave('${save.key}')" tabindex="0" role="button" aria-label="Load save ${save.name}">Load</span>
1230
+
<span class="separator">|</span>
1231
+
<span class="hotkey" onclick="deleteSave('${save.key}')" tabindex="0" role="button" aria-label="Delete save ${save.name}">Delete</span>
1232
+
</div>
1233
+
`;
1234
+
// Add double-click support to load the save directly
1235
+
saveItem.addEventListener("dblclick", () =>
1236
+
loadSave(save.key),
1237
+
);
1238
+
loadList.appendChild(saveItem);
1239
+
});
1240
+
}
1241
+
1242
+
showModal("load-modal");
1243
+
}
732
1244
733
-
// Update time in status bar
734
-
setInterval(() => {
735
-
const now = new Date();
736
-
const timeStr = now.toLocaleTimeString([], {
737
-
hour: "2-digit",
738
-
minute: "2-digit",
739
-
hour12: false,
740
-
});
741
-
const statusBarTime = document.querySelector(
742
-
".status-bar span:last-child",
743
-
);
744
-
if (statusBarTime) {
745
-
statusBarTime.textContent = timeStr;
1245
+
function saveCurrentHTML() {
1246
+
const saveName = document
1247
+
.getElementById("save-name")
1248
+
.value.trim();
1249
+
if (!saveName) {
1250
+
const saveMessage = document.getElementById("save-message");
1251
+
saveMessage.textContent =
1252
+
"Please enter a name for your save";
1253
+
saveMessage.style.color = "#ff0000";
1254
+
setTimeout(() => {
1255
+
saveMessage.textContent = "";
1256
+
}, 3000);
1257
+
return;
746
1258
}
747
1259
748
-
// Update memory in status bar based on actual tab memory usage
749
-
const memoryUsage = Math.round(
750
-
performance.memory
751
-
? performance.memory.usedJSHeapSize / 1024
752
-
: 0,
753
-
);
1260
+
try {
1261
+
// Ensure all modals are closed in the saved HTML
1262
+
// First make a clone of the current document state
1263
+
const tempDiv = document.createElement("div");
1264
+
tempDiv.innerHTML = document.documentElement.outerHTML;
754
1265
755
-
const memoryStr = `${589 - Math.min(Math.round(memoryUsage / 1024), 500)}K free`;
756
-
const memory = document.getElementById("memory");
757
-
if (memory) {
758
-
memory.textContent = memoryStr;
1266
+
// Find and hide all modals in the clone
1267
+
const modalElements = tempDiv.querySelectorAll(".modal");
1268
+
modalElements.forEach((modal) => {
1269
+
modal.style.display = "none";
1270
+
});
1271
+
1272
+
// Save the current state
1273
+
const saveData = {
1274
+
html: tempDiv.innerHTML,
1275
+
timestamp: new Date().getTime(),
1276
+
name: saveName,
1277
+
};
1278
+
1279
+
localStorage.setItem(
1280
+
`plastic-save-${saveName}`,
1281
+
JSON.stringify(saveData),
1282
+
);
1283
+
1284
+
const saveMessage = document.getElementById("save-message");
1285
+
saveMessage.textContent = `Saved as "${saveName}"`;
1286
+
saveMessage.style.color = "#008000";
1287
+
setTimeout(() => {
1288
+
saveMessage.textContent = "";
1289
+
hideModal("save-modal");
1290
+
}, 1500);
1291
+
} catch (e) {
1292
+
const saveMessage = document.getElementById("save-message");
1293
+
saveMessage.textContent = `Error saving: ${e.message}`;
1294
+
saveMessage.style.color = "#ff0000";
1295
+
setTimeout(() => {
1296
+
saveMessage.textContent = "";
1297
+
}, 3000);
1298
+
}
1299
+
}
1300
+
1301
+
// Add keyboard support for modals
1302
+
document.addEventListener("DOMContentLoaded", function () {
1303
+
const saveNameInput = document.getElementById("save-name");
1304
+
if (saveNameInput) {
1305
+
// Remove any existing listeners to prevent duplicates
1306
+
const saveInputHandler = function (e) {
1307
+
if (e.key === "Enter") {
1308
+
e.preventDefault();
1309
+
saveCurrentHTML();
1310
+
}
1311
+
};
1312
+
1313
+
saveNameInput.removeEventListener(
1314
+
"keydown",
1315
+
saveInputHandler,
1316
+
);
1317
+
saveNameInput.addEventListener("keydown", saveInputHandler);
759
1318
}
760
1319
761
-
// Update runtime in status bar - based on how long the page has been open
762
-
const pageOpenTime = performance.timing
763
-
? performance.timing.navigationStart || Date.now()
764
-
: Date.now();
765
-
const timeOpen = Math.floor((Date.now() - pageOpenTime) / 1000);
766
-
const hours = Math.floor(timeOpen / 3600)
767
-
.toString()
768
-
.padStart(2, "0");
769
-
const minutes = Math.floor((timeOpen % 3600) / 60)
770
-
.toString()
771
-
.padStart(2, "0");
772
-
const seconds = Math.floor(timeOpen % 60)
773
-
.toString()
774
-
.padStart(2, "0");
775
-
const runtimeStr = `${hours}:${minutes}:${seconds}`;
776
-
const runtime = document.getElementById("runtime");
777
-
if (runtime) {
778
-
runtime.textContent = runtimeStr;
1320
+
document
1321
+
.querySelectorAll(".modal-close")
1322
+
.forEach((closeBtn) => {
1323
+
closeBtn.addEventListener("keydown", function (e) {
1324
+
if (e.key === "Enter" || e.key === " ") {
1325
+
e.preventDefault();
1326
+
const modalId = this.closest(".modal").id;
1327
+
hideModal(modalId);
1328
+
}
1329
+
});
1330
+
});
1331
+
});
1332
+
1333
+
function loadSave(key) {
1334
+
try {
1335
+
const savedData = localStorage.getItem(key);
1336
+
if (savedData) {
1337
+
let saveObj;
1338
+
1339
+
try {
1340
+
saveObj = JSON.parse(savedData);
1341
+
} catch (e) {
1342
+
saveObj = { html: savedData };
1343
+
}
1344
+
1345
+
if (
1346
+
confirm(
1347
+
"Loading will replace the current page. Continue?",
1348
+
)
1349
+
) {
1350
+
hideModal("load-modal");
1351
+
1352
+
const scriptToAdd = `
1353
+
<script>
1354
+
document.addEventListener('DOMContentLoaded', function() {
1355
+
// Ensure all modals are hidden
1356
+
document.querySelectorAll('.modal').forEach(modal => {
1357
+
modal.style.display = "none";
1358
+
});
1359
+
1360
+
// Re-setup keyboard handlers
1361
+
if (typeof setupKeyboardHandlers === 'function') {
1362
+
setupKeyboardHandlers();
1363
+
}
1364
+
});
1365
+
<\/script>
1366
+
`;
1367
+
1368
+
// Load the saved HTML with the added script
1369
+
document.open();
1370
+
document.write(saveObj.html + scriptToAdd);
1371
+
document.close();
1372
+
}
1373
+
} else {
1374
+
const loadMessage =
1375
+
document.getElementById("load-message");
1376
+
loadMessage.textContent = "Save not found";
1377
+
loadMessage.style.color = "#ff0000";
1378
+
setTimeout(() => {
1379
+
loadMessage.textContent = "";
1380
+
}, 3000);
1381
+
}
1382
+
} catch (e) {
1383
+
const loadMessage = document.getElementById("load-message");
1384
+
loadMessage.textContent = `Error loading: ${e.message}`;
1385
+
loadMessage.style.color = "#ff0000";
1386
+
setTimeout(() => {
1387
+
loadMessage.textContent = "";
1388
+
}, 3000);
779
1389
}
780
-
}, 1000);
1390
+
}
781
1391
</script>
782
1392
</body>
783
1393
</html>