diff --git a/sites/labs/public/FlatLaf-Demo/FlatLaf/flatlaf-demo-3.7.jar b/sites/labs/public/FlatLaf-Demo/FlatLaf/flatlaf-demo-3.7.jar new file mode 100644 index 00000000..87033a76 Binary files /dev/null and b/sites/labs/public/FlatLaf-Demo/FlatLaf/flatlaf-demo-3.7.jar differ diff --git a/sites/labs/public/FlatLaf-Demo/FlatLaf/flatlaf-theme-editor-3.7.jar b/sites/labs/public/FlatLaf-Demo/FlatLaf/flatlaf-theme-editor-3.7.jar new file mode 100644 index 00000000..a37d51bb Binary files /dev/null and b/sites/labs/public/FlatLaf-Demo/FlatLaf/flatlaf-theme-editor-3.7.jar differ diff --git a/sites/labs/public/FlatLaf-Demo/index.html b/sites/labs/public/FlatLaf-Demo/index.html new file mode 100644 index 00000000..eaa6729b --- /dev/null +++ b/sites/labs/public/FlatLaf-Demo/index.html @@ -0,0 +1,96 @@ + + + + + FlatLaf Demo & Theme Editor – CheerpJ + Java 17 + + + + + +
+
+
+
+ + + +
+ +
+
+
FlatLaf – Browser Demo
+
Java 17 via CheerpJ
+
+
+ Switch between the FlatLaf Demo and Theme Editor, both running + unchanged JARs inside the browser via CheerpJ (Java 17). +
+
+ +
+ +
+ + +
+
+ +
+
+ + +
+
+
+ + + + diff --git a/sites/labs/public/FlatLaf-Demo/main.js b/sites/labs/public/FlatLaf-Demo/main.js new file mode 100644 index 00000000..13f810cf --- /dev/null +++ b/sites/labs/public/FlatLaf-Demo/main.js @@ -0,0 +1,125 @@ +// Jar location inside the CheerpJ VFS +const VFS_DIR = "/files/FlatLaf-Demo"; +const VFS_JARS = { + demo: `${VFS_DIR}/flatlaf-demo-3.7.jar`, + editor: `${VFS_DIR}/flatlaf-theme-editor-3.7.jar`, +}; + +// Where the jars live the website +const WEB_JARS = { + demo: new URL( + "./FlatLaf/flatlaf-demo-3.7.jar", + window.location.href + ).toString(), + editor: new URL( + "./FlatLaf/flatlaf-theme-editor-3.7.jar", + window.location.href + ).toString(), +}; + +const btnDemo = document.getElementById("btn-demo"); +const btnEditor = document.getElementById("btn-editor"); +const statusLabel = document.getElementById("status-label"); +const statusAppName = document.getElementById("status-app-name"); + +let currentApp = null; + +// Library-mode to handle filesystem work +let lib = null; + +(async () => { + statusLabel.textContent = "Starting CheerpJ"; + + await cheerpjInit({ + version: 17, + }); + + cheerpjCreateDisplay(-1, -1, document.getElementById("container")); + + // Library mode to write files in the VFS + lib = await cheerpjRunLibrary(""); + + statusLabel.textContent = "Preparing demo files"; + + // Ensure jars exist in VFS + await ensureJarInVfs("demo"); + await ensureJarInVfs("editor"); + + statusLabel.textContent = "Launching Demo"; + runApp("demo"); + + btnDemo.addEventListener("click", () => runApp("demo")); + btnEditor.addEventListener("click", () => runApp("editor")); +})().catch((err) => { + console.error("Init failed:", err); + statusLabel.textContent = "Failed to start"; +}); + +function labelFor(which) { + return which === "editor" ? "Theme Editor" : "Demo"; +} + +async function ensureJarInVfs(which) { + const targetPath = VFS_JARS[which]; + const sourceUrl = WEB_JARS[which]; + + const Files = await lib.java.nio.file.Files; + const Paths = await lib.java.nio.file.Paths; + + await Files.createDirectories(await Paths.get(VFS_DIR)); + + const exists = await Files.exists(await Paths.get(targetPath)); + if (exists) { + console.log(`[VFS] ${which} jar already present: ${targetPath}`); + return; + } + + console.log(`[WEB] Downloading ${which} jar: ${sourceUrl}`); + statusLabel.textContent = `Downloading ${labelFor(which)} JAR`; + + const resp = await fetch(sourceUrl); + if (!resp.ok) { + throw new Error( + `Failed to fetch ${sourceUrl}: ${resp.status} ${resp.statusText}` + ); + } + + const buf = await resp.arrayBuffer(); + const byteArr = Array.from(new Int8Array(buf)); + + console.log( + `[VFS] Writing ${which} jar to: ${targetPath} (${byteArr.length} bytes)` + ); + + const FileOutputStream = await lib.java.io.FileOutputStream; + const fos = await new FileOutputStream(targetPath); + await fos.write(byteArr); + await fos.close(); + + console.log(`[VFS] Done: ${targetPath}`); +} + +function runApp(which) { + if (which === currentApp) return; + + currentApp = which; + + btnDemo.classList.toggle("active", which === "demo"); + btnEditor.classList.toggle("active", which === "editor"); + + const label = labelFor(which); + statusLabel.textContent = `Launching ${label}…`; + statusAppName.textContent = label; + + // Run the jar from VFS path + const jarPath = VFS_JARS[which]; + + console.log(`Starting FlatLaf ${which} from ${jarPath}`); + + cheerpjRunJar(jarPath) + .then((code) => console.log(`${which} exited with code`, code)) + .catch((err) => { + console.error(`${which} failed:`, err); + statusLabel.textContent = `Error starting ${label}`; + }); +} diff --git a/sites/labs/public/FlatLaf-Demo/style.css b/sites/labs/public/FlatLaf-Demo/style.css new file mode 100644 index 00000000..83bd36f2 --- /dev/null +++ b/sites/labs/public/FlatLaf-Demo/style.css @@ -0,0 +1,302 @@ +/* Basic theme colors */ +:root { + --bg-main: #05060a; + --bg-panel: #0d0f16; + --border-soft: rgba(255, 255, 255, 0.08); + --accent: #4f8cff; + --accent-soft: rgba(79, 140, 255, 0.16); + --text-main: #f5f5f7; + --text-muted: #a0a3ad; + --radius-xl: 18px; + --radius-pill: 999px; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + background: #05060a; + color: var(--text-main); +} + +/* Center the main shell */ +body { + display: flex; + align-items: stretch; + justify-content: center; +} + +/* Top-level layout */ +.page { + flex: 1; + display: flex; + min-height: 100vh; + padding: 16px; +} + +/* Main card / shell */ +.shell { + position: relative; + flex: 1 1 auto; + min-width: 0; + border-radius: var(--radius-xl); + background: linear-gradient( + 145deg, + rgba(255, 255, 255, 0.03), + rgba(0, 0, 0, 0.7) + ); + border: 1px solid var(--border-soft); + box-shadow: 0 18px 45px rgba(0, 0, 0, 0.45); + overflow: hidden; + display: flex; + flex-direction: column; +} + +/* Header / title bar */ +.shell-header { + padding: 10px 14px; + display: flex; + align-items: center; + gap: 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(7, 10, 20, 0.95); +} + +/* Mac-style dots */ +.shell-dots { + display: flex; + gap: 6px; + margin-right: 6px; +} + +.shell-dot { + width: 10px; + height: 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.15); +} + +.shell-dot:nth-child(1) { + background: #ff5f57; +} +.shell-dot:nth-child(2) { + background: #febc2e; +} +.shell-dot:nth-child(3) { + background: #28c840; +} + +/* Title block */ +.shell-title-block { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.shell-title-row { + display: flex; + align-items: baseline; + gap: 8px; + min-width: 0; +} + +.shell-title { + font-size: 0.95rem; + font-weight: 600; + white-space: nowrap; +} + +.shell-pill { + font-size: 0.7rem; + padding: 2px 8px; + border-radius: var(--radius-pill); + border: 1px solid rgba(255, 255, 255, 0.18); + background: rgba(255, 255, 255, 0.03); + color: var(--text-muted); +} + +.shell-subtitle { + font-size: 0.78rem; + color: var(--text-muted); +} + +/* Spacer to push switcher to the right */ +.shell-spacer { + flex: 1 1 auto; +} + +/* App switcher */ +.switcher { + display: inline-flex; + border-radius: var(--radius-pill); + padding: 2px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.16); +} + +.switcher button { + border: none; + border-radius: var(--radius-pill); + padding: 4px 12px; + font-size: 0.8rem; + background: transparent; + color: var(--text-muted); + cursor: pointer; + white-space: nowrap; + transition: + background 0.15s ease, + color 0.15s ease; +} + +.switcher button.active { + background: var(--accent-soft); + color: var(--text-main); +} + +/* Main body: CheerpJ display + side panel */ +.shell-body { + position: relative; + flex: 1 1 auto; + min-height: 0; + display: flex; + overflow: hidden; +} + +/* CheerpJ canvas target */ +#container { + flex: 1 1 auto; + width: 100%; + height: 100%; +} + +/* Right-hand info panel */ +.side-panel { + width: 20%; + max-width: 32%; + padding: 10px 12px 12px; + border-left: 1px solid rgba(255, 255, 255, 0.06); + background: #0b0d16; + display: flex; + flex-direction: column; + gap: 10px; +} + +/* Side panel content */ +.side-heading { + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-muted); +} + +.step-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 6px; + font-size: 0.8rem; + color: var(--text-muted); +} + +.step-list li { + display: flex; + gap: 6px; + align-items: flex-start; +} + +.step-badge { + flex-shrink: 0; + width: 18px; + height: 18px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.22); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.7rem; + color: var(--text-main); + background: rgba(0, 0, 0, 0.4); +} + +/* Hint box */ +.hint { + font-size: 0.75rem; + color: var(--text-muted); + border-radius: 10px; + padding: 8px 9px; + background: #080a12; + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.hint code { + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + font-size: 0.75rem; + background: rgba(255, 255, 255, 0.06); + padding: 1px 4px; + border-radius: 4px; +} + +/* Status bar at bottom of side panel */ +.status { + font-size: 0.75rem; + margin-top: auto; + padding-top: 6px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + color: var(--text-muted); + display: flex; + justify-content: space-between; + gap: 8px; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 999px; + background: #17c964; +} + +.status-text { + display: flex; + align-items: center; + gap: 6px; +} + +.status-app { + text-align: right; +} + +.status-app span { + color: var(--text-main); +} + +/* Responsive: hide side panel on narrow screens */ +@media (max-width: 900px) { + .side-panel { + display: none; + } + + .shell { + border-radius: 0; + } + + .page { + padding: 0; + } +}