HTML2 Forever
by BoldPenguin14617 lines9.5 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML2 Forever</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background: #0e0e11;
color: #eee;
min-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
}
header {
min-height: 58px;
background: #18181d;
border-bottom: 1px solid #303036;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 18px;
position: sticky;
top: 0;
z-index: 10;
}
.logo {
font-size: 19px;
font-weight: bold;
}
.logo span {
color: #9275ff;
}
button {
background: #292930;
border: 1px solid #38383f;
color: white;
padding: 9px 14px;
border-radius: 6px;
cursor: pointer;
}
button:hover {
background: #393942;
}
.primary {
background: #7959ed;
border-color: #7959ed;
}
.actions {
display: flex;
gap: 8px;
}
.layout {
display: flex;
min-height: calc(100vh - 58px);
height: auto;
}
.sidebar {
width: 54px;
flex-shrink: 0;
background: #18181d;
border-right: 1px solid #303036;
display: flex;
align-items: center;
flex-direction: column;
padding: 15px 0;
gap: 22px;
}
.sideicon {
font-size: 20px;
color: #92929c;
cursor: pointer;
}
.sideicon:hover {
color: white;
}
.workspace {
flex: 1;
display: flex;
min-width: 0;
min-height: 0;
}
.left,
.right {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 500px;
}
.left {
border-right: 1px solid #303036;
}
.panelhead {
height: 49px;
min-height: 49px;
background: #18181d;
border-bottom: 1px solid #303036;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
font-size: 13px;
}
.tabs {
display: flex;
gap: 5px;
height: 100%;
align-items: center;
}
.tab {
padding: 9px 14px;
color: #999;
cursor: pointer;
border-radius: 5px;
font-size: 13px;
}
.tab.active {
background: #303039;
color: white;
}
.editor {
height: 600px;
min-height: 300px;
position: relative;
}
textarea {
width: 100%;
height: 100%;
background: #111115;
color: #e3e3ec;
border: 0;
outline: none;
resize: vertical;
overflow: auto;
padding: 20px;
font: 14px/1.7 Consolas, monospace;
tab-size: 2;
}
textarea::placeholder {
color: #555560;
}
iframe {
display: block;
width: 100%;
height: 700px;
min-height: 400px;
flex: none;
border: 0;
background: white;
}
.bottom {
height: 28px;
min-height: 28px;
background: #18181d;
border-top: 1px solid #303036;
padding: 6px 12px;
font-size: 11px;
color: #85858e;
display: flex;
justify-content: space-between;
}
.empty {
position: absolute;
pointer-events: none;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
color: #484850;
font-size: 13px;
display: none;
}
/* Desktop */
@media(min-width: 651px) {
.left,
.right {
width: 50%;
}
}
/* Mobile */
@media(max-width:650px) {
header {
padding: 0 9px;
flex-wrap: wrap;
gap: 8px;
min-height: 58px;
}
.logo {
font-size: 15px;
}
.actions {
gap: 5px;
}
.actions button {
padding: 8px;
font-size: 12px;
}
.sidebar {
width: 40px;
}
.workspace {
flex-direction: column;
}
.left,
.right {
width: 100%;
flex: none;
min-height: 0;
}
.left {
border-right: 0;
border-bottom: 1px solid #303036;
}
.editor {
height: 450px;
min-height: 300px;
}
iframe {
height: 650px;
min-height: 400px;
}
}
</style>
</head>
<body>
<header>
<div class="logo">HTML2<span>FOREVER</span></div>
<div class="actions">
<button onclick="newProject()">+ New</button>
<button onclick="saveProject()">Save</button>
<button class="primary" onclick="run()">▶ Run</button>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="sideicon" title="Files" onclick="showFiles()">▤</div>
<div class="sideicon" title="Editor" onclick="focusEditor()">⌘</div>
<div class="sideicon" title="Preview" onclick="scrollToPreview()">◫</div>
<div class="sideicon" title="Download" onclick="downloadProject()">⇩</div>
</aside>
<main class="workspace">
<section class="left">
<div class="panelhead">
<div class="tabs">
<div class="tab active" onclick="changeTab('html',this)">HTML</div>
<div class="tab" onclick="changeTab('css',this)">CSS</div>
<div class="tab" onclick="changeTab('js',this)">JS</div>
</div>
<span>● Editor</span>
</div>
<div class="editor">
<textarea
id="editor"
placeholder="Start coding here..."
spellcheck="false"
></textarea>
<div class="empty" id="empty">
Your workspace is empty
</div>
</div>
<div class="bottom">
<span id="fileName">index.html</span>
<span id="status">Ready</span>
</div>
</section>
<section class="right" id="previewSection">
<div class="panelhead">
<span>◉ Live Preview</span>
<div>
<button onclick="run()">↻</button>
<button onclick="fullScreen()">⛶</button>
</div>
</div>
<iframe
id="preview"
title="Live website preview"
sandbox="allow-scripts allow-forms allow-modals allow-popups"
></iframe>
<div class="bottom">
<span>Preview</span>
<span id="previewStatus">Waiting for code</span>
</div>
</section>
</main>
</div>
<script>
const editor = document.getElementById("editor");
const preview = document.getElementById("preview");
let active = "html";
let previewTimer;
let files = {
html: "",
css: "",
js: ""
};
// Switch editor tabs
function changeTab(tab, element) {
files[active] = editor.value;
active = tab;
editor.value = files[tab];
document.querySelectorAll(".tab").forEach(t => {
t.classList.remove("active");
});
element.classList.add("active");
document.getElementById("fileName").textContent =
tab === "js" ? "script.js" :
tab === "css" ? "style.css" :
"index.html";
updateEmpty();
schedulePreview();
}
// Automatic live preview
editor.addEventListener("input", () => {
files[active] = editor.value;
document.getElementById("status").textContent =
"Unsaved changes";
updateEmpty();
schedulePreview();
});
// Delay preview slightly
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(() => {
run();
}, 300);
}
// Update empty editor message
function updateEmpty() {
document.getElementById("empty").style.display =
editor.value.length === 0 ? "block" : "none";
}
// Run website
function run() {
files[active] = editor.value;
const html = files.html;
const css = files.css;
const js = files.js;
const safeCSS = css.replace(/<\/style/gi, "<\\/style");
const safeJS = js.replace(/<\/script/gi, "<\\/script");
let output = html;
if (output.match(/<\/head>/i)) {
output = output.replace(
/<\/head>/i,
`<style>${safeCSS}</style></head>`
);
} else {
output = `<style>${safeCSS}</style>` + output;
}
if (output.match(/<\/body>/i)) {
output = output.replace(
/<\/body>/i,
`<script>${safeJS}<\/script></body>`
);
} else {
output += `<script>${safeJS}<\/script>`;
}
preview.srcdoc = output;
document.getElementById("status").textContent =
"Preview updated";
document.getElementById("previewStatus").textContent =
"Live";
}
// Save project
function saveProject() {
files[active] = editor.value;
try {
localStorage.setItem(
"html2forever",
JSON.stringify(files)
);
document.getElementById("status").textContent =
"Saved successfully";
} catch(error) {
document.getElementById("status").textContent =
"Could not save project";
}
}
// Load saved project
function loadProject() {
try {
const saved = localStorage.getItem("html2forever");
if(saved) {
files = {...files, ...JSON.parse(saved)};
}
} catch(error) {
console.error("Could not load project", error);
}
}
// Create new project
function newProject() {
if(!confirm("Create a new empty project?")) return;
files = {
html: "",
css: "",
js: ""
};
editor.value = "";
localStorage.removeItem("html2forever");
changeTab(
"html",
document.querySelectorAll(".tab")[0]
);
run();
document.getElementById("status").textContent =
"New project";
}
// Download website
function downloadProject() {
files[active] = editor.value;
const html =
files.html +
`<style>${files.css}</style>` +
`<script>${files.js}<\/script>`;
const blob = new Blob(
[html],
{type:"text/html"}
);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "index.html";
a.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
// Full screen preview
function fullScreen() {
if(preview.requestFullscreen) {
preview.requestFullscreen();
}
}
// Scroll to preview
function scrollToPreview() {
document.getElementById("previewSection").scrollIntoView({
behavior: "smooth",
block: "start"
});
}
// Files menu
function showFiles() {
alert(
"PROJECT FILES\n\n" +
"index.html\n" +
"style.css\n" +
"script.js"
);
}
// Focus editor
function focusEditor() {
editor.focus();
}
// Initialize
loadProject();
editor.value = files.html;
updateEmpty();
run();
</script>
</body>
</html>
Game Source: HTML2 Forever
Creator: BoldPenguin14
Libraries: none
Complexity: complex (617 lines, 9.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: html2-forever-boldpenguin14" to link back to the original. Then publish at arcadelab.ai/publish.